工厂模式但带有对象参数

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 对于“预定义”比萨,您可能想要定义辅助工厂方法,例如createMargaritaPizzacreateHawaiiPizza 然后在内部使用构建器创建一个披萨,其配料是特定于该披萨的。

【讨论】:

另外,如果 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。嘻嘻。

以上是关于工厂模式但带有对象参数的主要内容,如果未能解决你的问题,请参考以下文章

设计模式系列-Builder模式,工厂方法模式和抽象工厂模式

工厂模式

工厂实际模式

抽象工厂模式

设计模式第3篇:生成器模式(Builder)

工厂模式,简单工厂模式,抽象工厂模式三者有啥区别