在这个NYPizzaIngredientFactory的例子中,他们只能用ThinCrustDough制作披萨。如何制作披萨,可以使用其他工厂的成分,如ChicagoCizzaIngredientFactory的ThickCrustDough。我想尝试远离建造者并坚持使用抽象工厂模式和工厂方法。
答案 0 :(得分:1)
如果您希望NYPizzaStore
ChicagoPizzaIngredientFactory
ThickCrustDough
,则必须使用NYThickPizzaIngredientFactory
。
但是,如果你考虑一下它的实用性,让他们从芝加哥运送你的食材可能没有意义。
在我看来,你有两个选择:
createDough
)。这是因为你的界面有一个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);
界面。
用法:
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容器中连接,但类本身很小,简单且可测试,具体取决于抽象,而不是与任何实现相结合。
有人可能看起来并且认为它有点复杂。重要的是,它不仅可以使各个班级脱钩,而且还为未来的变革留下了前进的道路。你的课程的设计不应该改变太多,不会密切反映特定类型的比萨饼的细节,这些比萨饼可以而且应该改变。你不想因为一种新的比萨饼而重新设计披萨应用程序。