単一のメソッドを持つクラス—最善のアプローチ?
-
03-07-2019 - |
質問
単一の機能を実行するためのクラスがあるとします。機能を実行した後、破棄することができます。これらのアプローチのいずれかを好む理由はありますか?
// Initialize arguments in constructor
MyClass myObject = new MyClass(arg1, arg2, arg3);
myObject.myMethod();
// Pass arguments to method
MyClass myObject = new MyClass();
myObject.myMethod(arg1, arg2, arg3);
// Pass arguments to static method
MyClass.myMethod(arg1, arg2, arg3);
私は意図的に詳細を曖昧にして、さまざまな状況のガイドラインを取得しようとしました。しかし、私は本当にMath.random()のような単純なライブラリー関数を念頭に置いていませんでした。特定の複雑なタスクを実行するクラスを考えていますが、そのために必要なのは1つの(パブリック)メソッドだけです。
解決
以前は、静的メソッドで満たされたユーティリティクラスが大好きでした。彼らは、そうでなければ冗長性と保守の地獄を引き起こすことを避けなければならないであろうヘルパーメソッドを大きく統合しました。それらは非常に使いやすく、インスタンス化も廃棄もされず、ただ忘れるだけです。これは、サービス指向アーキテクチャを作成するための最初の無意識の試みでした-ちょうど仕事をしただけで他には何もない多くのステートレスなサービスだと思います。しかし、システムが成長するにつれて、ドラゴンがやって来ます。
多態性
たとえば、幸運にも話題になっているUtilityClass.SomeMethodメソッドがあるとします。突然、機能をわずかに変更する必要があります。ほとんどの機能は同じですが、それでもいくつかの部分を変更する必要があります。静的メソッドではなかった場合、派生クラスを作成し、必要に応じてメソッドの内容を変更できます。静的メソッドなので、できません。もちろん、古いメソッドの前または後に機能を追加する必要がある場合は、新しいクラスを作成し、その内部で古いクラスを呼び出すことができます-しかし、それはただの大雑把です。
インターフェースの問題
静的なメソッドは、ロジック上の理由により、インターフェースを介して定義できません。また、静的メソッドはオーバーライドできないため、静的クラスはインターフェイスで渡す必要がある場合には役に立ちません。これにより、戦略パターンの一部として静的クラスを使用できなくなります。 によっていくつかの問題を修正する場合があります。インターフェイスの代わりにデリゲートを渡す。
テスト
これは基本的に、上記のインターフェイスの問題と密接に関連しています。実装を交換する能力は非常に限られているため、実稼働コードをテストコードに置き換えるのも困難です。繰り返しますが、それらをラップすることはできますが、実際のオブジェクトではなくラッパーを受け入れることができるようにするために、コードの大部分を変更する必要があります。
ブロブを育てる
通常、静的メソッドはユーティリティメソッドとして使用され、ユーティリティメソッドは通常異なる目的を持つため、すぐに非一貫性のある機能で満たされた大きなクラスになります-理想的には、各クラスはシステム内で単一の目的を持つべきです。目的が明確に定義されている限り、5倍のクラスが必要です。
パラメータクリープ
そもそも、そのかわいくて無邪気な静的メソッドは、1つのパラメーターを取るだけです。機能が大きくなると、いくつかの新しいパラメーターが追加されます。オプションのパラメータがすぐに追加されるため、メソッドのオーバーロードを作成します(または、それらをサポートする言語でデフォルト値を追加します)。やがて、10個のパラメーターを受け取るメソッドができました。最初の3つだけが実際に必要であり、パラメータ4〜7はオプションです。しかし、パラメーター6が指定されている場合、7-9も同様に入力する必要があります...この静的メソッドが行うことを行うという単一の目的でクラスを作成した場合は、必要なパラメーターをコンストラクタ、およびユーザーがプロパティを通じてオプションの値を設定できるようにするか、メソッドを使用して複数の相互依存する値を同時に設定できます。また、メソッドがこのほど複雑になった場合は、とにかく独自のクラスにする必要があります。
理由なくクラスのインスタンスを作成することを消費者に要求する
最も一般的な引数の1つは、クラスのコンシューマーがこの単一メソッドを呼び出すためのインスタンスを作成し、その後インスタンスを使用しないように要求する理由です。クラスのインスタンスの作成は、ほとんどの言語で非常に安価な操作であるため、速度は問題になりません。消費者に余分なコード行を追加することは、将来、はるかに保守性の高いソリューションの基盤を築くための低コストです。最後に、インスタンスの作成を避けたい場合は、シングルトンwraを作成します
他のヒント
私は静的な方法を好みます。クラスはオブジェクトを表していないため、インスタンスを作成しても意味がありません。
メソッドに対してのみ存在するクラスは静的なままにしてください。
関数を実行するためにクラスのインスタンスを作成する理由がない場合は、静的実装を使用します。このクラスのコンシューマーに、インスタンスが不要なときにインスタンスを作成させる理由。
オブジェクトの状態を保存する必要がない場合、そもそもインスタンス化する必要はありません。パラメーターを渡す単一の静的メソッドを使用します。
また、関連のない静的メソッドが多数ある巨大なUtilsクラスについても警告します。これは急いで混乱して扱いにくくなる可能性があります。多くのクラスを持ち、それぞれに関連するメソッドが少ない方が良いでしょう。
静的メソッド形式の方が良いオプションだと思います。また、クラスを静的にすることで、クラスのインスタンスを誤って作成することを心配する必要がなくなります。
ここでの状況は本当にわかりませんが、arg1、arg2、またはarg3が属するクラスの1つにメソッドとして配置することを検討します-これらのクラスの1つを意味的に言うことができる場合メソッドを所有します。
提供された情報に基づいて答えるのは難しいことをお勧めします。
私の直感は、メソッドを1つだけ使用する場合、クラスをすぐに破棄し、すべてのパラメーターを受け取る静的クラスにすることです。
もちろん、この1つのメソッドのためだけに1つのクラスを作成する必要がある理由を正確に伝えることは困難です。典型的な「ユーティリティクラス」ですか?ほとんどが想定している状況?または、何らかのルールクラスを実装していますか。将来、さらに多くのルールクラスが追加される可能性があります。
たとえば、そのクラスをプラグ可能にします。次に、1つのメソッドのインターフェイスを作成し、コンストラクターではなくインターフェイスにすべてのパラメーターを渡したいが、静的にしたくない場合があります。
クラスを静的にすることはできますか?
もしそうなら、それを「ユーティリティ」クラスにして、すべての1関数クラスを入れます。
このメソッドがステートレスであり、そのメソッドを渡す必要がない場合、静的メソッドとして定義するのが最も理にかなっています。メソッドを渡す必要がある場合は、他の提案されたアプローチの1つではなく、デリゲート。
単純なアプリケーションと internal
ヘルパーの場合、静的メソッドを使用します。コンポーネントを使用するアプリケーションの場合、 Managed Extensibility Framework が大好きです。これは、私のAPIで見つかるパターンを説明するために書いているドキュメントからの抜粋です。
- サービス
-
I [ServiceName] Service
インターフェースによって定義されます。 - インターフェースタイプごとにエクスポートおよびインポートされます。
- 単一の実装はホストアプリケーションによって提供され、内部および/または拡張機能によって消費されます。
- サービスインターフェイスのメソッドはスレッドセーフです。
-
不自然な例として:
public interface ISettingsService
{
string ReadSetting(string name);
void WriteSetting(string name, string value);
}
[Export]
public class ObjectRequiringSettings
{
[Import]
private ISettingsService SettingsService
{
get;
set;
}
private void Foo()
{
if (SettingsService.ReadSetting("PerformFooAction") == bool.TrueString)
{
// whatever
}
}
}
私は、コンストラクタ内ですべてを行うだけです。そのように:
new MyClass(arg1, arg2, arg3);// the constructor does everything.
または
MyClass my_object(arg1, arg2, arg3);
考慮すべきもう1つの重要な問題は、システムがマルチスレッド環境で実行されるかどうか、および静的メソッドまたは変数を持つことがスレッドセーフかどうかです...
システムの状態に注意する必要があります。
状況を一斉に回避できる場合があります。 arg1.myMethod1(arg2、arg3)
が得られるようにリファクタリングを試みてください。
理にかなっている場合は、arg1をarg2またはarg3と交換します。
arg1のクラスを制御できない場合は、装飾します:
class Arg1Decorator
private final T1 arg1;
public Arg1Decorator(T1 arg1) {
this.arg1 = arg1;
}
public T myMethod(T2 arg2, T3 arg3) {
...
}
}
arg1d = new Arg1Decorator(arg1)
arg1d.myMethod(arg2, arg3)
理由は、OOPでは、データとそのデータを処理するメソッドは一緒に属するということです。さらに、マークが言及したすべての利点が得られます。
私は、クラスまたはクラスのインスタンスのプロパティがコンストラクターまたはメソッドで使用されない場合、メソッドが「静的」パターンのように設計されることを示唆していないと思います。静的メソッドは常に「ヘルプ」のように考える必要があります。