何かを行うという手順があるとします。
void doStuff(initalParams) {
...
}
「物事を行う」ことはかなり複雑な操作であることがわかりました。プロシージャが大きくなり、それを複数の小さなプロシージャに分割したところ、何かの処理中に何らかのstateを使用すると便利であることがわかり、小さなプロシージャ間で渡すパラメータを少なくする必要があります。だから、私はそれを独自のクラスに分解します:
class StuffDoer {
private someInternalState;
public Start(initalParams) {
...
}
// some private helper procedures here
...
}
そして、私はそれを次のように呼びます:
new StuffDoer().Start(initialParams);
またはこのように:
new StuffDoer(initialParams).Start();
そして、これは間違っていると感じているものです。 .NETまたはJava APIを使用している場合、私は常にnew SomeApiClass().Start(...);
を呼び出さないため、間違っていると思われます。確かに、StuffDoerのコンストラクターをプライベートにして、静的ヘルパーメソッドを追加できます。
public static DoStuff(initalParams) {
new StuffDoer().Start(initialParams);
}
しかし、外部インターフェイスが1つの静的メソッドのみで構成されているクラスがあり、これも奇妙に感じられます。
したがって、私の質問:このタイプのクラスには確立されたパターンがありますか?
メソッドオブジェクト と呼ばれるパターンがあります。ここでは、多数の一時変数/引数を持つ単一の大きなメソッドを別のクラスに分解します。メソッドの一部を個別のメソッドに抽出するだけでなく、ローカル状態(パラメーターと一時変数)にアクセスする必要があるため、これを実行します。また、そのメソッドに対してローカルであるため、インスタンス変数を使用してローカル状態を共有できません。 (およびそこから抽出されたメソッド)のみで、オブジェクトの残りの部分では使用されません。
そのため、代わりにメソッドは独自のクラスになり、パラメーターと一時変数はこの新しいクラスのインスタンス変数になり、メソッドは新しいクラスの小さなメソッドに分割されます。結果のクラスは通常、クラスがカプセル化するタスクを実行する単一のパブリックインスタンスメソッドを持っています。
問題のクラスは(単一のエントリポイントを使用して)うまく設計されていると思います。使い方も拡張も簡単です。かなりソリッド。
no "externally recognizable" state
も同じです。それは良いカプセル化です。
次のようなコードには問題がありません。
var doer = new StuffDoer(initialParams);
var result = doer.Calculate(extraParams);
SOLIDの原則 の観点から jgauffinの答え は理にかなっています。ただし、一般的な設計原則を忘れないでください。 情報の非表示 。
与えられたアプローチにはいくつかの問題があります:
おそらく 議論の主なポイント は 単一の責任の原則 をどこまで引き延ばすかにあります。 "その極端に行き、存在する理由が1つあるクラスを構築すると、クラスごとにメソッドが1つだけになる可能性があります。これにより、最も単純なクラスでも、クラスが大きく増加します。プロセスが原因で、システムが理解しにくくなり、変更が困難になります。」
このトピックに関連する別の関連参照:「1つの識別可能なタスクを実行するメソッドにプログラムを分割します。メソッドのすべての操作を同じレベルの抽象化に保ちます。」 - Kent Beck ここでの鍵は、「同じレベルの抽象化」です。それはしばしば解釈されるので、「1つのこと」を意味しません。このレベルの抽象化は、設計しているcontextに完全に依存します。
具体的なユースケースを知らなければ、それを伝えるのは困難です。私は時々(あまりではないが)同様のアプローチを使用するシナリオがあります。この機能をクラススコープ全体で利用できるようにしたくなく、データセットを処理したい場合。私はそれについてブログ投稿を書きました ラムダがカプセル化をさらに改善する方法 。私 このトピックに関するプログラマーの質問も開始しました 。以下は、この手法を使用した最新の例です。
new TupleList<Key, int>
{
{ Key.NumPad1, 1 },
...
{ Key.NumPad3, 16 },
{ Key.NumPad4, 17 },
}
.ForEach( t =>
{
var trigger = new IC.Trigger.EventTrigger(
new KeyInputCondition( t.Item1, KeyInputCondition.KeyState.Down ) );
trigger.ConditionsMet += () => AddMarker( t.Item2 );
_inputController.AddTrigger( trigger );
} );
ForEach
内の非常に「ローカルな」コードは他の場所では再利用されないため、関連する正確な場所に単純に保持できます。相互に依存するコードが強くグループ化されるような方法でコードの概要を説明すると、私の意見ではより読みやすくなります。
かなり良いと思いますが、APIは静的クラスの静的メソッドである必要があります。ユーザーが期待するものだからです。静的メソッドがnew
を使用してヘルパーオブジェクトを作成し、作業を行うという事実は、それを呼び出している誰からも隠されるべき実装の詳細です。
より複雑で、パーソナライズされた他の回答 に加えて、次の観察は別の回答に値すると感じます。
あなたがフォローしている可能性がある兆候があります ポルターガイストのアンチパターン 。
ポルターガイストは、非常に限られた役割と効果的なライフサイクルを持つクラスです。彼らはしばしば他のオブジェクトのプロセスを開始します。リファクタリングされたソリューションには、ポルターガイストを排除する、より寿命の長いオブジェクトへの責任の再割り当てが含まれます。
症状と結果
- 冗長なナビゲーションパス。
- 一時的な関連付け。
- ステートレスクラス。
- 一時的な短期間のオブジェクトとクラス。
- 一時的な関連付けを通じて他のクラスを「シード」または「呼び出す」ためにのみ存在する単一操作クラス。
- Start_process_alphaなどの「コントロールのような」操作名を持つクラス。
リファクタリングされたソリューション
ゴーストバスターズはポルターガイストをクラス階層から完全に削除することで解決します。ただし、それらを削除した後、ポルターガイストによって「提供された」機能は交換する必要があります。これは、アーキテクチャを修正するための簡単な調整で簡単です。
_new StuffDoer().Start(initialParams);
_
共有インスタンスを使用してそれを使用できる無知な開発者に問題があります
したがって、これはスレッドセーフではなく、軽量である(作成が高速で、外部リソースや大量のメモリを占有しない)場合、その場でインスタンス化しても問題ないという明確なドキュメントが必要です。
静的メソッドで非表示にすると、インスタンスが再利用されないため、これが役立ちます。
コストのかかる初期化がある場合、could(常にそうとは限らない)は、初期化を1回準備し、状態のみを含む別のクラスを作成してそれを複数回使用して、複製可能にすることは有益です。初期化すると、doer
に格納される状態が作成されます。 start()
はそれを複製して内部メソッドに渡します。
これにより、部分実行の状態を永続化するなど、他のことも可能になります。 (長時間の外部要因、たとえば電力供給の障害が実行を中断する可能性がある場合)。しかし、これらの追加の空想的なことは通常必要とされないので、価値はありません。
Martin Fowlerの EAAカタログのP ;には、いくつかのパターンがあります。