工厂如何以抽象工厂模式访问其他工厂的产品
How can factory's have access to other factory's products in abstract factory pattern
在本例中,NYPizzaIngredientFactory 只能使用 ThinCrustDough 制作披萨。我如何制作可以使用其他工厂原料的比萨饼,例如 ChicagoPizzaIngredientFactory 的 ThickCrustDough。我想尝试远离构建器并坚持使用抽象工厂模式和工厂方法。
如果您希望 NYPizzaStore
能够使用 ThickCrustDough
,则必须使用 ChicagoPizzaIngredientFactory
。
但是,如果您考虑这样做的实用性,让他们从芝加哥向您运送原料可能没有意义。
在我看来,你有两个选择:
- 在纽约有另一家工厂可以生产厚面团(例如
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);
我看到的第一个问题是:
public interface IDoughArgs
{
}
public class NYPizzaDoughArgs : IDoughArgs
{
public enum DoughTypes
{
Thin = 0,
Thick = 1
}
public DoughTypes DoughType { get; set; }
}
IDoughArgs
没有成员。实现它的 class NYPizzaDoughArgs
具有不是 IDoughArgs
实现的属性。这使 IDoughArgs
接口变得毫无意义。
此外,看看这个 class 声明:
public class NYPizzaIngredientFactory : IPizzaIngredientFactory<NYPizzaDoughArgs>
什么 class 将要 "know" 通用参数并知道创建此 class 而不是其他一些通用实现?当你到达那个部分时,它会变得混乱。您需要某种工厂来创建您的工厂。
然后,如果您认为配料工厂不仅仅因面团类型而异,并且您需要更通用的参数,那么它会变得非常混乱。
而且,如果除了具有特定于一种面团类型的厚度等选项之外,您还需要特定于一种厚度的选项,会发生什么情况?如果您 select 吃过纽约或芝加哥风格(非欧洲),也许厚面团是唯一的选择,而如果您 select 吃过厚面包皮,则只有填充面包皮是一种选择。这将很难用接口来描述。听起来更像数据。
这是另一种实现方法:
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
枚举中有 50 个值,构建一个确定的、可读的有效选项列表,而不是试图用一堆分支在代码中表示它,这样可能更容易。 (如果你想对其进行单元测试,你最终会在单元测试中对每个组合进行编码。)
您可以通过多种方式使用这些数据。您可以只列出一大堆选项。您可以允许用户从各种选项中选择 select,然后确定它是否与有效组合相匹配。或者他们可以 select 任何选项,您可以根据包含所需选项的选项来缩小选项列表。 (你想要馅饼皮吗?好的,要么是纽约厚饼皮要么是芝加哥厚饼皮。)
现在,如果你需要一个工厂来根据类型创建面团,你可以这样做:
public interface IDoughFactory
{
Dough GetDough(PizzaDoughOption doughOption);
}
实现可能看起来像这样。老实说,我可能会在这里使用 "factory factory",但现在因为只有三种类型,所以我会保持简单。
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
。如果他们关心 select 是薄还是厚,他们可以处理。如果该选项不存在,他们可以忽略它。即使外部 class 出现问题并且有人以某种方式使用 StuffedCrust
选项调用了 GreekDoughFactory
,它也不会失败。它只是忽略它。
这一切的可能指向是什么?
首先,制作披萨的 class 不知道制作正确面团类型的复杂性。它只是依赖于一个面团工厂,传递一个参数,并得到正确的面团。这很简单且可测试。
其次,您不必在任何地方调用 new
。您可以一直使用依赖注入。这样,依赖于抽象 IDoughFactory
的 class 就不知道 DoughFactory
有什么依赖关系。
同样,也许具体的面团工厂有自己的依赖关系,并且它们之间存在显着差异。只要那些从容器中得到解析并注入到 DoughFactory
,那很好,并且 DoughFactory
不会知道它们的依赖关系。
所有依赖项都连接在您的 DI 容器中,但 classes 本身很小、简单且可测试,取决于抽象而不与任何实现耦合。
有人可能会看这个并认为它有点复杂。关键是它不仅使个体 classes 分离,而且为未来的变化留下了前进的道路。您的 classes 的设计不必更改太多,不会密切反映特定类型比萨饼的细节,这些细节可以而且应该更改。您不希望因为一种新的比萨饼而不得不重新设计您的比萨饼应用程序。
在本例中,NYPizzaIngredientFactory 只能使用 ThinCrustDough 制作披萨。我如何制作可以使用其他工厂原料的比萨饼,例如 ChicagoPizzaIngredientFactory 的 ThickCrustDough。我想尝试远离构建器并坚持使用抽象工厂模式和工厂方法。
如果您希望 NYPizzaStore
能够使用 ThickCrustDough
,则必须使用 ChicagoPizzaIngredientFactory
。
但是,如果您考虑这样做的实用性,让他们从芝加哥向您运送原料可能没有意义。
在我看来,你有两个选择:
- 在纽约有另一家工厂可以生产厚面团(例如
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);
我看到的第一个问题是:
public interface IDoughArgs
{
}
public class NYPizzaDoughArgs : IDoughArgs
{
public enum DoughTypes
{
Thin = 0,
Thick = 1
}
public DoughTypes DoughType { get; set; }
}
IDoughArgs
没有成员。实现它的 class NYPizzaDoughArgs
具有不是 IDoughArgs
实现的属性。这使 IDoughArgs
接口变得毫无意义。
此外,看看这个 class 声明:
public class NYPizzaIngredientFactory : IPizzaIngredientFactory<NYPizzaDoughArgs>
什么 class 将要 "know" 通用参数并知道创建此 class 而不是其他一些通用实现?当你到达那个部分时,它会变得混乱。您需要某种工厂来创建您的工厂。
然后,如果您认为配料工厂不仅仅因面团类型而异,并且您需要更通用的参数,那么它会变得非常混乱。
而且,如果除了具有特定于一种面团类型的厚度等选项之外,您还需要特定于一种厚度的选项,会发生什么情况?如果您 select 吃过纽约或芝加哥风格(非欧洲),也许厚面团是唯一的选择,而如果您 select 吃过厚面包皮,则只有填充面包皮是一种选择。这将很难用接口来描述。听起来更像数据。
这是另一种实现方法:
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
枚举中有 50 个值,构建一个确定的、可读的有效选项列表,而不是试图用一堆分支在代码中表示它,这样可能更容易。 (如果你想对其进行单元测试,你最终会在单元测试中对每个组合进行编码。)
您可以通过多种方式使用这些数据。您可以只列出一大堆选项。您可以允许用户从各种选项中选择 select,然后确定它是否与有效组合相匹配。或者他们可以 select 任何选项,您可以根据包含所需选项的选项来缩小选项列表。 (你想要馅饼皮吗?好的,要么是纽约厚饼皮要么是芝加哥厚饼皮。)
现在,如果你需要一个工厂来根据类型创建面团,你可以这样做:
public interface IDoughFactory
{
Dough GetDough(PizzaDoughOption doughOption);
}
实现可能看起来像这样。老实说,我可能会在这里使用 "factory factory",但现在因为只有三种类型,所以我会保持简单。
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
。如果他们关心 select 是薄还是厚,他们可以处理。如果该选项不存在,他们可以忽略它。即使外部 class 出现问题并且有人以某种方式使用 StuffedCrust
选项调用了 GreekDoughFactory
,它也不会失败。它只是忽略它。
这一切的可能指向是什么?
首先,制作披萨的 class 不知道制作正确面团类型的复杂性。它只是依赖于一个面团工厂,传递一个参数,并得到正确的面团。这很简单且可测试。
其次,您不必在任何地方调用 new
。您可以一直使用依赖注入。这样,依赖于抽象 IDoughFactory
的 class 就不知道 DoughFactory
有什么依赖关系。
同样,也许具体的面团工厂有自己的依赖关系,并且它们之间存在显着差异。只要那些从容器中得到解析并注入到 DoughFactory
,那很好,并且 DoughFactory
不会知道它们的依赖关系。
所有依赖项都连接在您的 DI 容器中,但 classes 本身很小、简单且可测试,取决于抽象而不与任何实现耦合。
有人可能会看这个并认为它有点复杂。关键是它不仅使个体 classes 分离,而且为未来的变化留下了前进的道路。您的 classes 的设计不必更改太多,不会密切反映特定类型比萨饼的细节,这些细节可以而且应该更改。您不希望因为一种新的比萨饼而不得不重新设计您的比萨饼应用程序。