工厂如何以抽象工厂模式访问其他工厂的产品

时间:2018-04-16 15:44:08

标签: c# abstract-factory

Here is a diagram of the pattern im referring to.

在这个NYPizzaIngredientFactory的例子中,他们只能用ThinCrustDough制作披萨。如何制作披萨,可以使用其他工厂的成分,如ChicagoCizzaIngredientFactory的ThickCrustDough。我想尝试远离建造者并坚持使用抽象工厂模式和工厂方法。

2 个答案:

答案 0 :(得分:1)

如果您希望NYPizzaStore ChicagoPizzaIngredientFactory ThickCrustDough,则必须使用NYThickPizzaIngredientFactory

但是,如果你考虑一下它的实用性,让他们从芝加哥运送你的食材可能没有意义。

在我看来,你有两个选择:

  1. 在纽约有另一家可生产厚面团的工厂(例如createDough)。这是因为你的界面有一个createDough方法,它没有参数,所以你不能告诉它要做什么类型的面团。它只能制作一个。
  2. 更改您的界面,以便//TDoughArts tells you what type of arguments the factory needs in order to make dough. public interface IPizzaIngredientFactory<TDoughArgs> where TDoughArgs : IDoughArgs { //.... IDough CreateDough(TDoughArgs doughArgs); //.... } public interface IDoughArgs { } public class NYPizzaDoughArgs : IDoughArgs { public enum DoughTypes { Thin = 0, Thick = 1 } public DoughTypes DoughType { get; set; } } public class NYPizzaIngredientFactory : IPizzaIngredientFactory<NYPizzaDoughArgs> { //.... public IDough CreateDough(NYPizzaDoughArgs doughArgs) { //Make the right dough based on args here if(doughArgs.DoughType == DoughTypes.Thin) //... } //.... } 方法接受可以告诉工厂要创建的面团类型的参数。 这是我推荐的
  3. 参数的类型也可以基于特定的工厂。例如:

    IDoughArgs

    我在几分钟内把它扯出来,所以检查一致性,但我想你会明白这个想法。

    您不必使用泛型。如果您不想要更具体的特性,可以简单地使用var factory = new NYPizzaIngredientFactory(); var args = new NYPizzaDoughArgs(); args.DoughType = NYPizzaDoughArgs.DoughTypes.Thick; var dough = factory.createDough(args); 界面。

    用法:

    material-ui-next

答案 1 :(得分:1)

我看到的第一个问题是:

public interface IDoughArgs
{

}

public class NYPizzaDoughArgs : IDoughArgs
{
    public enum DoughTypes
    {
        Thin = 0,
        Thick = 1
    }

    public DoughTypes DoughType { get; set; }
}

IDoughArgs没有会员。实现它的类NYPizzaDoughArgs具有不是IDoughArgs的实现的属性。这使得IDoughArgs界面变得毫无意义。

此外,请查看此类声明:

public class NYPizzaIngredientFactory : IPizzaIngredientFactory<NYPizzaDoughArgs>

什么类会“知道”泛型参数并知道创建此类而不是其他一些通用实现?当你到达那个部分时,它会变得混乱。你需要某种工厂来建立你的工厂。

然后,如果您认为配料工厂的变化不仅仅是面团的类型,而且您需要更多的通用参数,那么它将变得非常混乱。

并且,如果除了具有仅针对一种面团类型的厚度等选项外,还需要仅针对一种厚度的选项,会发生什么?如果你选择纽约或芝加哥风格(不是欧洲风格),也许只有厚面团可以选择,如果你选择了厚厚的外壳,那么填充外壳只是一种选择。用接口来描述真的很难。这听起来更像是数据。

以下是另一种实现此方法的方法:

public  enum PizzaStyle 
{
    NewYork = 1,
    Chicago = 2,
    Greek = 4
}

public enum CrustType 
{
    Thick = 1024,
    Thin = 2048,
    HandTossed = 4096
}

public enum CrustOption
{
    Stuffed = 32768
}

public enum PizzaDoughOption
{
    NewYorkThin = PizzaStyle.NewYork + CrustType.Thin,
    NewYorkHandTossed = PizzaStyle.NewYork + CrustType.HandTossed,
    NewYorkThick = PizzaStyle.NewYork + CrustType.Thick,
    NewYorkThickStuffed = NewYorkThick + CrustOption.Stuffed,
    ChicagoThin = PizzaStyle.Chicago + CrustType.Thin,
    ChicagoHandTossed = PizzaStyle.Chicago + CrustType.HandTossed,
    ChicagoThick = PizzaStyle.Chicago + CrustType.Thick,
    ChicagoThickStuffed = ChicagoThick + CrustOption.Stuffed,
    Greek = PizzaStyle.Greek // only comes one way?
}

还有其他方法可以表示相同的数据。即使PizzaDoughOption枚举中有五十个值,它仍然可能更容易,构建一个确定的,可读的有效选项列表,而不是试图用一堆分支在代码中表示。 (如果你想进行单元测试,你最终会在单元测试中对每一个组合进行编码。)

有几种方法可以使用这些数据。你可以提供一个很大的选项列表。您可以允许用户从各种选项中进行选择,并随时确定它是否与有效组合匹配。或者他们可以选择任何选项,您可以根据包含所需选项的选项列表缩小范围。 (你想要一块毛皮?好吧,那是纽约厚皮或芝加哥厚皮。)

现在,如果您需要工厂根据类型创建面团,您可以这样做:

public interface IDoughFactory
{
    Dough GetDough(PizzaDoughOption doughOption);
}

实现可能看起来像这样。说实话,我可能会在这里使用“工厂工厂”,但是现在因为只有三种类型我会更简单。

public class DoughFactory : IDoughFactory
{
    // Each of these also implement IDoughFactory
    private readonly NewYorkDoughFactory _newYorkDoughFactory;
    private readonly ChicagoDoughFactory _chicagoDoughFactory;
    private readonly GreekDoughFactory _greekDoughFactory;

    public DoughFactory(
        NewYorkDoughFactory newYorkDoughFactory,
        ChicagoDoughFactory chicagoDoughFactory,
        GreekDoughFactory greekDoughFactory)
    {
        _newYorkDoughFactory = newYorkDoughFactory;
        _chicagoDoughFactory = chicagoDoughFactory;
        _greekDoughFactory = greekDoughFactory;
    }

    public Dough GetDough(PizzaDoughOption doughOption)
    {
        if (MatchesPizzaStyle(doughOption, PizzaStyle.NewYork)) 
            return _newYorkDoughFactory.GetDough(doughOption);
        if (MatchesPizzaStyle(doughOption, PizzaStyle.Chicago)) 
            return _chicagoDoughFactory.GetDough(doughOption);
        if (MatchesPizzaStyle(doughOption, PizzaStyle.Greek)) 
            return _greekDoughFactory.GetDough(doughOption);
        // Throw an exception or return a default dough type. I'd throw the exception.
    }

    private bool MatchesPizzaStyle(PizzaDoughOption doughOption, PizzaStyle pizzaStyle)
    {
        return ((int) doughOptions & (int) pizzaStyle) == (int) pizzaStyle;
    }
}

现在你的更具体的面团工厂(纽约,芝加哥,希腊)都收到相同的PizzaDoughOption。如果他们关心是否选择了薄或厚,他们可以处理它。如果该选项不存在,则可以忽略它。即使外部类中出现了某些问题,并且有人使用GreekDoughFactory选项调用了StuffedCrust,它也不会失败。它只是忽略了它。

所有这一切可能有什么意义?

首先,创建披萨的类不知道创建正确的面团类型的复杂性。它只取决于面团工厂,传递参数,并获得正确的面团。这很简单,也可以测试。

其次,您无需在任何地方拨打new。您可以一直使用依赖注入。这样,依赖于抽象IDoughFactory的类对DoughFactory所依赖的依赖性一无所知。

同样,也许具体的面团工厂有自己的依赖性,并且它们之间存在显着差异。只要从容器中解析并注入DoughFactory,这很好,DoughFactory将不知道它们的依赖关系。

所有依赖项都在DI容器中连接,但类本身很小,简单且可测试,具体取决于抽象,而不是与任何实现相结合。

有人可能看起来并且认为它有点复杂。重要的是,它不仅可以使各个班级脱钩,而且还为未来的变革留下了前进的道路。你的课程的设计不应该改变太多,不会密切反映特定类型的比萨饼的细节,这些比萨饼可以而且应该改变。你不想因为一种新的比萨饼而重新设计披萨应用程序。