すべてのブログで目にするビルダー デザイン パターンの実装を紹介します。
interface IProductBuilder
{
void BuildPart1(Part1 value);
void BuildPart2(Part2 value);
void BuildPart3(Part3 value);
}
class ConcreteProduct
{
public readonly Part1 Part1;
public readonly Part2 Part2;
public readonly Part3 Part3;
public ConcreteProduct(Part1 part1, Part2 part2, Part3 part3)
{
Part1 = part1;
Part2 = part2;
Part3 = part3;
}
}
class ConcreteProductBuilder : IProductBuilder
{
Part1 _part1;
Part2 _part2;
Part3 _part3;
public void BuildPart1(Part1 value)
{
_part1 = value;
}
public void BuildPart2(Part2 value)
{
_part2 = value;
}
public void BuildPart3(Part3 value)
{
_part3 = value;
}
public ConcreteProduct GetResult()
{
return new ConcreteProduct(part1, part2, part3);
}
}
ビルダーを単体テストする一般的な方法は、次のようなものです。
[TestMethod]
void TestBuilder()
{
var target = new ConcreteBuilder();
var part1 = new Part1();
var part2 = new Part2();
var part3 = new Part3();
target.BuildPart1(part1);
target.BuildPart2(part2);
target.BuildPart3(part3);
ConcreteProduct product = target.GetResult();
Assert.IsNotNull(product);
Assert.AreEqual(product.Part1, part1);
Assert.AreEqual(product.Part2, part2);
Assert.AreEqual(product.Part3, part3);
}
したがって、これは非常に単純な例です。
ビルダーパターンは本当に良いものだと思います。これにより、変更可能なすべてのデータを 1 か所に配置し、他のすべてのクラスを不変のままにすることができます。これは、テスト容易性に優れています。
しかし、Product のフィールドを誰かに公開したくない場合はどうすればよいでしょうか (または、Product が一部のライブラリの一部であるため公開できません)。
ビルダーを単体テストするにはどうすればよいですか?
今はこんな感じでしょう?
[TestMethod]
void TestBuilder()
{
var target = new ConcreteProductBuilder();
var part1 = new Part1();
var part2 = new Part2();
var part3 = new Part3();
target.BuildPart1(part1);
target.BuildPart2(part2);
target.BuildPart3(part3);
ConcreteProduct product = target.GetResult();
TestConcreteProductBehaviorInUseCase1(product);
TestConcreteProductBehaviorInUseCase2(product);
...
TestConcreteProductBehaviorInUseCaseN(product);
}
ここに、少なくとも 1 つの簡単な解決策があります。変更ConcreteProductBuilder.GetResult
してファクトリを取得します。
public ConcreteProduct GetResult(IConcreteProductFactory factory)
{
return factory.Create(part1, part2, part3);
}
IConcreteProductFactory
2 つの方法
で実装します。
public MockConcreteProductFactory
{
public Part1 Part1;
public Part2 Part2;
public Part3 Part3;
public ConcreteProduct Product;
public int Calls;
public ConcreteProduct Create(Part1 part1, Part2 part2, Part3 part3)
{
Calls++;
Part1 = part1;
Part2 = part2;
Part3 = part3;
Product = new ConcreteProduct(part1, part2, part3);
return Product;
}
}
public ConcreteProductFactory
{
public ConcreteProduct Create(Part1 part1, Part2 part2, Part3 part3)
{
return new ConcreteProduct(part1, part2, part3);
}
}
そのような場合、テストは以前と同じくらい簡単になります。
[TestMethod]
void TestBuilder()
{
var target = new ConcreteBuilder();
var part1 = new Part1();
var part2 = new Part2();
var part3 = new Part3();
target.BuildPart1(part1);
target.BuildPart2(part2);
target.BuildPart3(part3);
var factory = new MockConcreteProductFactory();
ConcreteProduct product = target.GetResult(factory);
Assert.AreEqual(1, factory.Calls);
Assert.AreSame(factory.Product, product);
Assert.AreEqual(factory.Part1, part1);
Assert.AreEqual(factory.Part2, part2);
Assert.AreEqual(factory.Part3, part3);
}
したがって、私の質問は、より良い方法で解決する方法ではなく、ビルダーのパターン自体に関するものです。
Builder デザイン パターンは、単一責任の原則に違反していますか?
私にとって、一般的な定義のビルダーは次のことを担当しているようです。
- コンストラクター引数 (またはビルダー パターンの他の実装におけるプロパティ値) の収集
- 収集されたプロパティを持つオブジェクトの構築