Заводская модель, но с параметрами объекта
Возьмите следующий классический фабричный образец:
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.");
}
}
}
Что делать, если одна (или несколько) из конкретных пицц требует параметр, специфичный для конкретной реализации при строительстве. Например, предположим, что фабрике HamAndMushroom требуется параметр с именем MushroomType, и этот параметр потребуется для создания экземпляра объекта?
7 ответов
Вы можете добавить параметры в метод (ы) создателя вашей фабрики. Однако, если число параметров становится больше (для меня это будет больше, чем 2-3), и особенно если некоторые или все эти параметры являются необязательными с приемлемыми значениями по умолчанию, вы можете вместо этого превратить фабрику в Builder.
Это может быть особенно подходящим для пиццы, где у вас обычно одна и та же корочка, только с разными (комбинациями) начинок. Строитель очень близко моделирует общий способ заказа, например, "пицца с салями, помидорами, кукурузой и двойным сыром". OTOH для "предопределенных" пицц вы можете определить вспомогательные фабричные методы, например createMargaritaPizza
или же createHawaiiPizza
которые затем внутренне используют строителя для создания пиццы с начинками, характерными для этого вида пиццы.
Вы можете передать новый параметр, такой как Map. И запросить свойства каждого конкретного конструктора. Тогда все методы будут иметь одинаковую подпись. Тем не менее, с этим решением, вызывающая сторона конструктора должна знать конкретные свойства конкретного конструктора...(Связь)
Когда количество параметров становится очень большим, я думаю, что фабрика становится менее удобной и избыточной, поскольку главная цель - сделать процесс создания как бы невидимым.
Кроме того, когда параметры "обязательны", я также думаю, что Builder теряет свое очарование.
В этом случае я могу захотеть объединить фабрику с "Объектом параметров", который уменьшил бы число параметров, которые должны быть переданы в статические фабричные методы, и это могло бы сделать логику создания более читабельной и аккуратной, чем при использовании Builder. Но, конечно, этот объект параметра также необходимо создать, но по крайней мере он будет в одной единой форме для всего приложения.
Вы можете попробовать что-то вроде этого:
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);
}
}
ИМХО концепция фабрики с реализацией конкретных параметров выглядит неправильно.
Вы должны добавить еще один метод CreatePizza() для этого класса фабрики. И это будет означать, что пользователи фабрики не смогут создавать пиццы такого типа, если они не будут специально использовать экземпляр класса HamAndMushroomPizzaFactory. Если у них просто есть ссылка на PizzaFactory, они могут вызвать только версию без параметров и не смогут создавать пиццу с ветчиной и грибами вообще.
Прежде всего, мне кажется странным, что абстрактный класс 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, которое отображается внутри некоторой таблицы рецептов пиццы с совершенно разными рецептами даже для слегка измененного содержания пиццы.
Вы можете использовать отражение:
using System.Reflection;
// ...
public override IPizza CreatePizza(PizzaType pizzaType, params object[] parameters) {
return (IPizza)
Activator.CreateInstance(
Assembly
.GetExecutingAssembly()
.GetType(pizzaType.ToString()),
parameters);
}