コードを作成するとき、クラスメソッドを呼び出すパターンをよく使用し、そのメソッドが同じクラス内のいくつかのプライベート関数を呼び出して作業を実行することに気づきました。プライベート関数は1つのことだけを行います。コードは次のようになります。
public void callThisMethod(MyObject myObject) {
methodA(myObject);
methodB(myObject); // was mehtodB before
}
private void methodA(MyObject myObject) {
//do something
}
private void methodB(MyObject myObject) {
//do something
}
5つ以上のプライベートメソッドが存在することもあります。以前は、一部のプライベートメソッドを別のクラスに移動しましたが、そうすると不必要に複雑になる場合があります。
このパターンが気に入らない主な理由は、あまりテストできないことです。すべてのメソッドを個別にテストできるようにしたいのですが、すべてのメソッドを公開して、それらのテストを作成できるようにする場合があります。
私が使用できるより良いデザインはありますか?
それは良いパターンです。これは、ボブおじさんが ドロップするまで抽出する について話すときに言及するものです。
ただし、テストには影響しません。パブリックインターフェイスのみをテストする必要があります。はい、方法を反映してプライベートメソッドをテストすることはできますが、パブリックメソッドをテストするときにもう一度テストする必要があるため、認識された問題は解決しません。
「問題」は、複数のパブリックメソッドを使用しているためにプライベートメソッドを複数回テストするときに発生します。
時には、実際にはあなたが思っているよりも頻繁に、これは問題ではありません。特に、コードの各行をテストしようとするのではなく、行動によってテストしている場合。あなたが知りたいのは、「このパブリックメソッドは本来の機能を果たしますか?」だけです。実装の詳細は気にしません。
ただし、何を行うかは重要ですが、同じ動作の複雑なロジックを繰り返しテストする場合、各動作に複数のテストが追加されるためです。その時点で、サービスに抽出してモックアウトするだけです。
問題のパターンは合成メソッドとして知られていると思います。ケントベックスSmalltalkベストプラクティスパターン[1]で言及されています。さらに、各メソッドは識別可能な1つの処理を実行し、メソッド内で呼び出されるメソッドはすべて同じ抽象化レベルである必要があると述べています。
[1] http://www.Amazon.com/Smalltalk-Best-Practice-Patterns-Kent/dp/013476904X
ただし、最初のobject
パラメーターを渡す回数に注意します。すべてのプライベートメソッドがメインパラメータで動作している場合は、コードの匂いがするかもしれません。例えば。あなたが持っている場合:
class Foo {
public void callThisMethod(Bar myBarObject) {
methodA(myBarObject);
methodB(myBarObject);
methodC(myBarObject);
methodD(myBarObject);
methodE(myBarObject);
…
methodZ(myBarObject);
}
}
class Bar {
…
}
これは、そのクラスでのFoo
のメソッド間の結合力が低いことを示しています—メソッドは、Bar
とFoo
よりも共通点が多いようです。その場合は、関連するメソッドを、それらが操作しているクラスに移動することを検討してください( Law of Demeter を参照)。
そうでない場合でも、1つのことだけを行うメソッドを持つこと、またはプライベート内部ではなくパブリックメソッドを介してアプリケーションをテストすることに問題はありません(これらのメソッドのすべてのユースケースを「実行」できるため、ただし、必要に応じて、後でこれらのメソッドをリファクタリングすることができます。
プライベートメソッドを介して コマンドパターン を実装しようとしているのかどうか疑問に思っています。コマンドパターンへの切り替えが、自分のしていることの方が簡単だと思うかもしれません。
あなたが今持っているのはこれです:
public void callThisMethod(MyObject myObject) {
methodA(myObject);
mehtodB(myObject)
}
private void methodA(MyObject myObject) {
//do something
}
private void methodB(MyObject myObject) {
//do something
}
コマンドとディスパッチャーを使用して同じことを実装できます。
public interface Command {
void Execute(Context c);
}
public class Context {
// this holds shared data
}
public class Dispatcher
{
private List<Command> _commands = new List<Command>();
public void Add(Command cmd)
{
_commands.Add(cmd);
}
public void Run(Context c)
{
foreach(Command cmd in _commands) { cmd.Execute(c); }
}
}
次に、プライベートメソッドをコマンドとして実装します。
public class CommandA : Command { ... }
public class CommandB : Command { ... }
特別な状態情報は、各コマンドに渡されるContext
クラスで定義されます。コードを実行するには、コマンドをディスパッチャーに追加して実行します。
Dispatcher d = new Dispatcher();
d.Add(new CommandA());
d.Add(new CommandB());
d.Run(new Context());
このアプローチの利点は、コマンドを互いに分離し、Context
クラスを使用してすべてを連携させることです。また、作成するコマンド、およびそれらをインスタンス化するために使用するパラメーターにロジックを追加することもできます。
その他のヒントとして、よく使用されるコマンドのシングルトンを作成して、常に新しいコマンドを作成する際のオーバーヘッドを減らすことができます。
各コマンドを分離できるため、このアプローチは非常にテスト可能です。
欠点は、コマンドのコレクションが大量にあると維持が困難になる可能性があることです。単純な小さなコマンドを多数作成するよりも、多くのことを実行できるファットコマンドを作成することをお勧めします。
私にとって、これは2つの理由で悪い習慣のように思えます。
これらの方法が相互にどのように依存しているかは不明です。厳密に順守する命令がある場合は、この命令をコードに反映する必要があります。それ以外の場合は、非表示の時間的結合( クリーンコードの項目G31を参照 )が導入されます。すべてのメソッドからオブジェクトを返し、それを次のメソッドに渡すことで、結合を明確にすることができます。
これらのメソッドが実際には互いに依存していない場合は、単一のメソッドでインターフェースを導入する必要があります。異なる実装はcallThisMethod
の所有クラスのフィールドのコレクションに保持する必要があり、そのメソッドはそのコレクションを反復処理し、コレクション要素メソッドのみを呼び出す必要があります。このようにして、オープンクローズドプリンシパルをより厳守します。別の機能を追加することは、そのリストに保持される別のインターフェース実装を構成することだけの問題であり、 callThisMethod
の所有クラスを変更しない