22

采用以下经典工厂模式:

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 的参数,并且需要此参数来实例化对象?

4

7 回答 7

21

您可以将参数添加到工厂的创建者方法。但是,如果参数的数量越来越多(对我来说会超过 2-3 个),特别是如果这些参数中的部分或全部是可选的且具有合理的默认值,您可以考虑将工厂改为Builder

这可能特别适用于比萨饼,你通常有相同的外壳,只是配料不同(组合)。A Builder 非常接近常见的订购方式,例如“披萨配意大利腊肠、西红柿、玉米和双层奶酪”。OTOH 对于“预定义”比萨,您可能想要定义辅助工厂方法,例如,createMargaritaPizza或者createHawaiiPizza然后在内部使用构建器创建具有特定于那种比萨的浇头的比萨。

于 2010-07-28T14:33:30.493 回答
1

您可以传递一个新参数,例如 Map。并查询每个具体构造函数的属性。然后所有方法都将具有相同的签名。但是,使用这种解决方案,构造函数的调用者必须知道具体构造函数的具体属性......(耦合)

于 2010-07-28T14:58:20.113 回答
0

首先,对我来说,一个抽象类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,它在内部映射到某个比萨食谱表,具有完全不同的食谱,即使是稍微改变的比萨内容。

于 2010-07-28T15:07:40.777 回答
0

你可以尝试这样的事情:

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);
  }
}

恕我直言,具有实现特定参数的工厂概念看起来是错误的。

于 2010-07-28T16:50:05.133 回答
0

您必须为该工厂类添加另一个 CreatePizza() 方法。这意味着工厂的用户将无法创建这些类型的比萨饼,除非他们专门使用 HamAndMushroomPizzaFactory 类的实例。如果他们只是有一个 PizzaFactory 引用,他们只能调用无参数版本,并且不能通用地创建火腿和蘑菇比萨。

于 2010-07-28T14:37:29.207 回答
0

当参数计数变得非常高时,我确实认为工厂变得不那么方便和多余,因为它的主要目的是使创建过程变得不可见。

另外,当参数是“必需的”时,我也认为 Builder 失去了它的魅力。

在这种情况下,我可能希望将工厂与“参数对象”结合起来,这将减少需要传递给静态工厂方法的参数数量,并且可以使创建逻辑比使用 Builder 更具可读性和简洁性。但是,当然,还需要创建该参数对象,但至少它会在您的应用程序中以一种单一的形式出现。

于 2017-11-28T10:17:04.260 回答
-2

您可以使用反射:

using System.Reflection;

// ...

public override IPizza CreatePizza(PizzaType pizzaType, params object[] parameters) {
            return (IPizza)
                   Activator.CreateInstance(
                        Assembly
                             .GetExecutingAssembly()
                             .GetType(pizzaType.ToString()),
                        parameters);
        }
于 2010-07-28T14:37:58.010 回答