Take the following classic factory pattern:
public interface IPizza
{
decimal Price { get; }
}
public class HamAndMushroomPizza : IPizza
{
decimal IP
You can try something like this:
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);
}
}
IMHO concept of factory with implementation specific parameters looks wrong.
You can use reflection:
using System.Reflection;
// ...
public override IPizza CreatePizza(PizzaType pizzaType, params object[] parameters) {
return (IPizza)
Activator.CreateInstance(
Assembly
.GetExecutingAssembly()
.GetType(pizzaType.ToString()),
parameters);
}
You could pass a new parameter, such as a Map. And query the properties on each concrete constructor. Then all the methods would have the same signature. However, with this solution, the caller of the constructor has to know the specific properties of the concret constructor...(Coupling)
When parameter count gets very high, I do think factory becomes less handy and redundant since the main point of it to make the creation process kinf of invisible.
Also, when the parameters are 'required', then I also think Builder loses its charm.
In this case, I may want to combine factory with a 'Parameter Object' which would reduce the # of parameters needed to be passed into the static factory methods and that could have made the creation logic more readable and neat than using a Builder. But of course, that parameter object is also needed to be created as well but at least it would be in one, single form across your application.
You can add parameters to the creator method(s) of your factory. However, if the number of parameters is getting higher (for me that would be more than 2-3), and especially if some or all of those parameters are optional with reasonable default values, you may consider turning the factory into a Builder instead.
That may be especially appropriate for pizzas, where you usually have the same crust, just with different (combinations) of toppings. A Builder models very closely the common way of ordering e.g. "a pizza with salami, tomatoes, maize and double cheese". OTOH for "predefined" pizzas you may want to define helper factory methods, e.g. createMargaritaPizza
or createHawaiiPizza
which then internally use the builder to create a pizza with the toppings specific to that kind of pizza.
First of all, it seems strange to me that an abstract class PizzaFactory
contains an abstract general method CreatePizza
that takes a parameter of a more concrete type ItalianPizzaFactory.PizzaType
.
To cover the problem I have just mentioned and the problem stated in the post, I would suggest the following approach.
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.");
}
}
}
As you see, the ParseTag() method may be of arbitrary complexity, parsing a plain text or an encrypted value. Or the Tag field can be a simple int that is mapped internally to some pizza recipe table, with whole different recipes for even slightly changed pizza content.