3

すべてのブログで目にするビルダー デザイン パターンの実装を紹介します。

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);
}

IConcreteProductFactory2 つの方法 で実装します。

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 デザイン パターンは、単一責任の原則に違反していますか?

私にとって、一般的な定義のビルダーは次のことを担当しているようです。

  1. コンストラクター引数 (またはビルダー パターンの他の実装におけるプロパティ値) の収集
  2. 収集されたプロパティを持つオブジェクトの構築
4

2 に答える 2

3

ここでは、次の 2 つの概念を扱います。

ビルダー パターンは、単一責任の原則を尊重していますか? はい。

ビルダーには 2 つの責任があると思われるかもしれません。

  • プロパティの収集
  • 収集したプロパティに基づいて製品を作成する

実際、収集したプロパティに基づいて製品を作成する責任は 1 つしかありません。ご覧のとおり、プロパティの収集を担当するクラスは Director クラスです (ウィキペディアのリンクを参照)。Builderは、受動的な方法でのみプロパティを受け取ります。それは本当にその責任ではありません。プロパティを受け取った後、オブジェクトを構築します。

ユニットテストはどうですか?

技術的には、パターンでフィールドを公開したくない場合、それは理由があり、コア デザインの一部です。したがって、単体テスターに​​対応するのは「設計者」の仕事ではありません。「設計」に対応するのが単体テスターの仕事です。

リフレクションでそれを達成するか (わかりました、それは不正行為です)、テストしたい Concrete Builder を継承するモックアップ ビルダーの作成を購入することができます。このモックアップ ビルダーは、組み立てたパーツを保存し、ユニット テスターがアクセスできるようにします。

于 2012-12-07T02:01:04.990 に答える
3

単一責任原則 (SRP) に違反しているとは思いません。SRP の wiki に記載されている例を考えてみましょう。

Martin は責任を変更する理由として定義し、クラスまたはモジュールには変更する理由が 1 つだけあるべきだと結論付けています。例として、レポートをコンパイルして印刷するモジュールを考えてみましょう。このようなモジュールは、2 つの理由で変更できます。まず、レポートの内容が変更される可能性があります。次に、レポートの形式が変更される可能性があります。これら 2 つのことは、非常に異なる原因で変化します。1つは実質的で、もう1つは化粧品です。

しかし、あなたが言及した場合、一方が変更された場合、もう一方も変更する必要があります。つまり、追加の引数がある場合、オブジェクトはこの追加の引数で構築する必要があります。したがって、基本的には 1 つの責任と 2 つのサブタスクです。

于 2012-12-08T14:21:18.613 に答える