【问题标题】: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 中。

标签: c# design-patterns factory-pattern


【解决方案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。嘻嘻。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-09-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多