工厂模式但带有对象参数
Posted
技术标签:
【中文标题】工厂模式但带有对象参数【英文标题】:Factory Pattern but with object Parameters 【发布时间】:2011-03-22 04:46:31 【问题描述】:采用以下经典工厂模式:
public interface IPizza
decimal Price get;
public class HamAndMushroomPizza : IPizza
decimal IPizza.Price
get
return 8.5m;
public abstract class PizzaFactory
public abstract IPizza CreatePizza(ItalianPizzaFactory.PizzaType pizzaType);
public class ItalianPizzaFactory : PizzaFactory
public enum PizzaType
HamMushroom,
Deluxe,
Hawaiian
public override IPizza CreatePizza(PizzaType pizzaType)
switch (pizzaType)
case PizzaType.HamMushroom:
return new HamAndMushroomPizza();
case PizzaType.Hawaiian:
return new HawaiianPizza();
default:
throw new ArgumentException("The pizza type " + pizzaType + " is not recognized.");
如果一个(或多个)Concrete Pizzas 需要一个特定于构造时具体实现的参数怎么办。例如,假设 HamAndMushroom 工厂需要一个名为 MushroomType 的参数,并且需要此参数来实例化对象?
【问题讨论】:
嗯...也许您解决问题的方法是错误的。由于所有 Pizza 类型仅在 WRT 一个数据字段中有所不同,因此单个 Pizza 类型就可以了,数据在 DB 中。 【参考方案1】:您可以向工厂的创建者方法添加参数。但是,如果参数数量越来越多(对我来说会超过 2-3 个),特别是如果这些参数中的部分或全部是可选的且具有合理的默认值,您可以考虑将工厂变成 Builder而是。
这可能特别适用于比萨饼,因为您通常有相同的外皮,只是配料不同(组合)。 Builder 非常接近地模拟了常见的排序方式,例如“披萨配意大利腊肠、西红柿、玉米和双层奶酪”。 OTOH 对于“预定义”比萨,您可能想要定义辅助工厂方法,例如createMargaritaPizza
或 createHawaiiPizza
然后在内部使用构建器创建一个披萨,其配料是特定于该披萨的。
【讨论】:
另外,如果 Pizza 需要其他对象,使用烤箱,或者特别是 StoneOven,我建议使用和 IoC,因为这些是(可配置)浇头的正交对象;-) 我同意彼得的观点,对于 OP 描述的场景,Builder 模式将是更合适的模式。他可能还想查看一个复合模式来处理一个订单多个披萨的情况。 你们让我饿了:)【参考方案2】:您可以传递一个新参数,例如 Map。并查询每个具体构造函数的属性。然后所有方法都将具有相同的签名。 但是,使用这种解决方案,构造函数的调用者必须知道具体构造函数的具体属性......(耦合)
【讨论】:
【参考方案3】:您必须为该工厂类添加另一个 CreatePizza() 方法。这意味着工厂的用户将无法创建这些类型的比萨饼,除非他们专门使用 HamAndMushroomPizzaFactory 类的实例。如果他们只是有一个 PizzaFactory 引用,他们只能调用无参数版本,并且不能通用地创建火腿和蘑菇比萨。
【讨论】:
【参考方案4】:首先,在我看来,抽象类PizzaFactory
包含一个抽象通用 方法CreatePizza
,它接受一个更具体类型ItalianPizzaFactory.PizzaType
的参数。
为了解决我刚才提到的问题和帖子中提到的问题,我建议采用以下方法。
public struct PizzaDefinition
public readonly string Tag;
public readonly string Name;
public readonly string Description;
public PizzaDefinition(string tag, string name, string description)
Tag = tag; Name = name; Description = description;
public abstract class PizzaFactory
public abstract IEnumerable<PizzaDefinition> GetMenu();
public abstract IPizza CreatePizza(PizzaDefinition pizzaDefinition);
public class ItalianPizzaFactory : PizzaFactory
public enum PizzaType
HamMushroom,
Deluxe,
Hawaiian
public override IEnumerable<PizzaDefinition> GetMenu()
return new PizzaDefinition[]
new PizzaDefinition("hm:mushroom1,cheese3", "Ham&Mushroom 1", "blabla"),
new PizzaDefinition("hm:mushroom2,cheese1", "Ham&Mushroom 2", "blabla"),
new PizzaDefinition("dx", "Deluxe", "blabla"),
new PizzaDefinition("Hawaian:shrimps,caramel", "Hawaian", "blabla")
;
private PizzaType ParseTag(string tag, out object[] options)...
public override IPizza CreatePizza(PizzaDefinition pizzaDefinition)
object[] options;
switch (ParseTag(pizzaDefinition.Tag, out options))
case PizzaType.HamMushroom:
return new HamAndMushroomPizza(options);
case PizzaType.Hawaiian:
return new HawaiianPizza();
default:
throw new ArgumentException("The pizza" + pizzaDefinition.Name + " is not on the menu.");
如您所见,ParseTag() 方法可能具有任意复杂性,可以解析纯文本或加密值。或者,Tag 字段可以是一个简单的 int,它在内部映射到某个比萨食谱表,其中包含完全不同的食谱,即使比萨内容略有变化。
【讨论】:
我不认为将披萨描述放在字符串中是正确的做事方式。最好让 PizzaDefinition 成为一个可以被子类化的类。更专业的比萨可以有更专业的比萨定义。 @siride:正确,我所做的只是指定一种方法。 PizzaDefinition 类可能非常困难,并且有一些方法可以帮助您从选项等中进行选择。但是这个特殊的例子仍然值得考虑,因为它非常健壮,同时提供了您可能想要的东西 - 可用比萨饼的列表和创建每个比萨饼的方法。【参考方案5】:你可以试试这样的:
interface IPizza
class Pizza1 : IPizza
public Pizza1(Pizza1Parameter p)
class Pizza2 : IPizza
public Pizza2(Pizza2Parameter p)
interface IPizzaParameter
object Type get; set;
class Pizza1Parameter : IPizzaParameter
public object Type get; set;
class Pizza2Parameter : IPizzaParameter
public object Type get; set;
static class PizzaFactory
public enum PizzaType
Pizza1,
Pizza2,
public static IPizza CreatePizza(PizzaType type, IPizzaParameter param)
switch (type)
case PizzaType.Pizza1:
return new Pizza1(param as Pizza1Parameter);
case PizzaType.Pizza2:
return new Pizza2(param as Pizza2Parameter);
throw new ArgumentException();
class Program
static void Main()
var param1 = new Pizza1Parameter();
var p1 = PizzaFactory.CreatePizza(PizzaFactory.PizzaType.Pizza1, param1);
恕我直言,带有实现特定参数的工厂概念看起来是错误的。
【讨论】:
【参考方案6】:当参数计数变得非常高时,我确实认为工厂变得不那么方便和多余,因为它的主要目的是使创建过程变得不可见。
另外,当参数是“必需的”时,我也认为 Builder 失去了它的魅力。
在这种情况下,我可能想将工厂与“参数对象”结合起来,这将减少需要传递给静态工厂方法的参数数量,并且可以使创建逻辑比使用生成器。但当然,也需要创建该参数对象,但至少它会在您的应用程序中以一种单一的形式存在。
【讨论】:
【参考方案7】:你可以使用反射:
using System.Reflection;
// ...
public override IPizza CreatePizza(PizzaType pizzaType, params object[] parameters)
return (IPizza)
Activator.CreateInstance(
Assembly
.GetExecutingAssembly()
.GetType(pizzaType.ToString()),
parameters);
【讨论】:
我不喜欢这种方法。如果更改类的构造函数,则必须更改对工厂的所有调用(并且只会得到运行时错误)。这抹去了工厂的优势:隐藏对象的构造。 在阅读“使用反射”后,我只是在键盘上吐了IPizza
。嘻嘻。以上是关于工厂模式但带有对象参数的主要内容,如果未能解决你的问题,请参考以下文章