我看到的第一个问题是这样的:
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 容器中,但类本身很小、简单且可测试,取决于抽象,并且不耦合到任何东西的实现。
有人可能会看着这个并认为它有点复杂。至关重要的是,它不仅使各个类保持解耦,而且为未来的变化留下了前进的道路。你的课程设计不应该改变太多,不会密切反映特定类型的比萨饼的细节,这些细节可以而且应该改变。您不希望因为一种新的比萨饼而重新设计您的比萨饼应用程序。