【问题标题】:How can factory's have access to other factory's products in abstract factory pattern工厂如何在抽象工厂模式下访问其他工厂的产品
【发布时间】:2018-04-16 15:44:07
【问题描述】:

在这个 NYPizzaIngredientFactory 的示例中,他们只能使用 ThinCrustDough 制作披萨。我如何制作可以使用其他工厂原料的比萨饼,例如来自 ChicagoPizzaIngredientFactory 的 ThickCrustDough。我想尝试远离 builder 并坚持使用抽象工厂模式和工厂方法。

【问题讨论】:

  • 不能,类图就是这样设计的(例如,FreshClams 在芝加哥不可用)。

标签: c# abstract-factory


【解决方案1】:

如果您希望它能够使用ThickCrustDough,您的NYPizzaStore 必须使用ChicagoPizzaIngredientFactory

但是,如果您考虑到这种做法的实用性,那么让他们从芝加哥向您运送原料可能没有意义。

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

  1. 在纽约有另一家可以生产厚面团的工厂(例如NYThickPizzaIngredientFactory)。这是因为您的界面有一个 createDough 方法,它不接受任何参数,因此您无法告诉它要制作哪种类型的面团。它只能做一个。
  2. 更改您的界面,以便createDough 方法接受可以告诉工厂创建哪种类型的面团的参数。 这是我推荐的一个

参数的类型也可以基于特定的工厂。例如:

//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)
            //...
    }
    //....
}

我在几分钟内完成了这个,所以请检查一致性,但我想你会明白的。

您不必使用泛型。如果您不想要更多的特异性,您可以简单地坚持使用IDoughArgs 界面。

用法:

var factory = new NYPizzaIngredientFactory();
var args = new NYPizzaDoughArgs();
args.DoughType = NYPizzaDoughArgs.DoughTypes.Thick;
var dough = factory.createDough(args);

【讨论】:

  • 谢谢,这就是我要找的。​​span>
【解决方案2】:

我看到的第一个问题是这样的:

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。如果他们关心选择的是薄还是厚,他们可以处理。如果该选项不存在,他们可以忽略它。即使外部类中出现问题并且不知何故有人使用StuffedCrust 选项调用了GreekDoughFactory,它也不会失败。它只是忽略它。

这一切的可能点是什么?

首先,制作披萨的班级不知道制作正确面团类型的复杂性。它只依赖于一个面团工厂,传递一个参数,然后得到正确的面团。这很简单且可测试。

其次,您不必在任何地方致电new。您可以一直使用依赖注入。这样,依赖于抽象 IDoughFactory 的类就不知道 DoughFactory 有什么依赖。

同样,混凝土面团工厂可能有自己的依赖关系,并且它们之间存在显着差异。只要这些是从容器中解析并注入DoughFactory 的,那很好,DoughFactory 不会知道它们的依赖关系。

所有依赖项都连接在您的 DI 容器中,但类本身很小、简单且可测试,取决于抽象,并且不耦合到任何东西的实现。

有人可能会看着这个并认为它有点复杂。至关重要的是,它不仅使各个类保持解耦,而且为未来的变化留下了前进的道路。你的课程设计不应该改变太多,不会密切反映特定类型的比萨饼的细节,这些细节可以而且应该改变。您不希望因为一种新的比萨饼而重新设计您的比萨饼应用程序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-05
    • 1970-01-01
    • 1970-01-01
    • 2012-04-08
    相关资源
    最近更新 更多