Factory Pattern but with object Parameters
Asked Answered
C

7

25

Take the following classic factory pattern:

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

What if one (or many) of the Concrete Pizzas requires a parameter specific to the concrete implementation at construction. For example, lets say the HamAndMushroom factory requires a parameter called, MushroomType and this parameter would be required to instantiate the object?

Cytolysis answered 28/7, 2010 at 14:29 Comment(2)
Uhm...maybe your approach to the problem is wrong. Since all the Pizza types only differ WRT a data field, a single Pizza type would do, with data in a DB.Bartlett
By definition, the parameter in factory pattern is used to identify only type of subclasses. wiki -> en.wikipedia.org/wiki/Factory_method_patternAdallard
D
22

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.

Discriminant answered 28/7, 2010 at 14:33 Comment(3)
Also, if the Pizza requires other objects, live the Oven, or specially a StoneOven, i would recommend using and IoC, as these are othogonal objects to the (configureable) toppings ;-)Jacobite
I agree with Peter, Builder pattern would be a more suitable pattern for the scenario described by the OP. He may want to look at a Composite pattern as well for handling the case of a single order of multiple pizza's.Unwarranted
You guys are making me hungry :)Whirlwind
S
0

You would have to add another CreatePizza() method for that factory class. And that would mean that users of the factory wouldn't be able to create those types of pizzas unless they were specifically using an instance of the HamAndMushroomPizzaFactory class. If they simply have a PizzaFactory reference, they can only call the parameterless version and won't be able to create ham and mushroom pizzas generically.

Soviet answered 28/7, 2010 at 14:37 Comment(0)
H
0

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)

Helminthic answered 28/7, 2010 at 14:58 Comment(0)
R
0

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.

Rodomontade answered 28/7, 2010 at 16:50 Comment(0)
S
0

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.

Subterrane answered 28/11, 2017 at 10:17 Comment(0)
D
-1

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.

Dehnel answered 28/7, 2010 at 15:7 Comment(2)
I don't think having the pizza description being in a string is the right way to do things. Better would be to have PizzaDefinition be a class that can be subclassed. More specialized pizzas can have more specialized pizza definitions.Soviet
@siride: correct, what I did is just specifying an approach. The PizzaDefinition class can be really tough and have some methods that will help you choose from options and so on. But this particular example is still worth considering, since it is very robust and at the same time giving what you may want - a list of available pizzas and a way of creating each one.Dehnel
B
-2

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);
        }
Bartlett answered 28/7, 2010 at 14:37 Comment(2)
I don't like this approach. If you change the constructor of a class you have to change all calls to the factory (and you will get only runtime errors). This erases the advantage of the factory: hiding construction of objects.Layman
I just vomited IPizza all over my keyboard after reading "use reflection". heehee.Twelvetone

© 2022 - 2024 — McMap. All rights reserved.