Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory Design Pattern (needing critique)

I am putting together an explanation and code example of this design pattern, attempting to help others around me grasp it (along with helping myself to master the pattern as well).

What I am looking for is opinions & or critique of my explanation and code sample...thanks!

What is the factory pattern? The factory pattern utilizes a particular dedicated "object creator object" to handle creation of - and most times instantiation of - objects, similar to a real world factory.

Real world example
Think of an automobile factory being the creator of various types of automobiles. One of the assembly lines in that automobile factory may produce a truck one day, but on another day may be re-tooled for producing cars. Say a dealership places an order for 10 cars to their assigned account handling department. That department then utilizes a certain factory and orders up the 10 cars. The account handlers are not concerned with making the cars themselves (imagine the poor results) they only work with the final product, ensuring the dealership gets their vehicles.

A new model of that same car comes out the next year and orders begin flowing in. The account handlers (still not concerned with the production of the car) place the orders, but now the car they receive is different, the assembling method or even maybe the factory altogether may be different, yet the account handlers need not worry about this. An additional thought: the factory assemblers of the vehicles may know exactly what action to take if a certain account handler places an order (i.e. account handler X places an order, factory assembler knows that for account handler X, they produce 10 vehicles of type Y). Another option may be that the account handler tells the assembler exactly what type of vehicle to produce.

If the account handlers also handled the creation of the vehicles (i.e. they were coupled), every time a vehicle changed in any way, each of the account handlers would have to be retrained in producing that vehicle. This would create quality issues as there are far more account handlers than there would be factories...mistakes would happen, expense would be far greater.

Circling back to OOP
An object factory as a design pattern applied to software engineering is similar to the above example in concept… The factory churns out various types of other objects, you can utilize an assembly line (object assembler) which produces a certain object type, returned in a certain way. The assembler can either inspect the requesting client and handle, or the client may tell the assembler what object it requires. Now...you are on a project and create an object factory and various assemblers, later on down the road in the project, the requirements change slightly, you are now asked to alter object contents and how its clients are handling that object. Since you utilized the factory pattern this is a simple change and in one location, you can change or add the objects the factory produces, and alter the format in which the assemblers lay the object contents out.

The unfortunate way to have done this would have been without a factory method, instantiating each object instance and formatting object contents in the clients themselves...say you used this particular object in 20 clients. Now you must go to each one of the clients, alter each of the object instances and formats...what a waste of time…Be lazy...do it the right way the first time so you save yourself (and others) time and effort later.

Code example (C#)
Below is an example utilizing a factory for food and various food objects

Factory module
    public enum FoodType
    {
    //enumerated foodtype value, if client wants to specify type of object, coupling still occurs
        Hamburger, Pizza, HotDog
    }
 
    /// <summary>
    /// Object to be overridden (logical)
    /// </summary>
    public abstract class Food
    {
        public abstract double FoodPrice { get; }
    }
 
    /// <summary>
    /// Factory object to be overridden (logical)
    /// </summary>
    public abstract class FoodFactory
    {
        public abstract Food CreateFood(FoodType type);
    }
 
    //-------------------------------------------------------------------------
    #region various food objects
    class Hamburger : Food
    {
        double _foodPrice = 3.59;
        public override double FoodPrice
        {
            get { return _foodPrice; }
        }
    }
 
    class Pizza : Food
    {
        double _foodPrice = 2.49;
        public override double FoodPrice
        {
            get { return _foodPrice; }
        }
    }
 
    class HotDog : Food
    {
        double _foodPrice = 1.49;
        public override double FoodPrice
        {
            get { return _foodPrice; }
        }
    }
    #endregion
    //--------------------------------------------------------------------------
 
 
    /// <summary>
    /// Physical factory
    /// </summary>
    public class ConcreteFoodFactory : FoodFactory
    {
        public override Food CreateFood(FoodType foodType)
        {
            switch (foodType)
            {
                case FoodType.Hamburger:
                    return new Hamburger();
                    break;
                case FoodType.HotDog:
                    return new HotDog();
                    break;
                case FoodType.Pizza:
                    return new Pizza();
                    break;
                default:
                    return null;
                    break;
            }
        }
    }
 
    /// <summary>
    /// Assemblers
    /// </summary>
    public class FoodAssembler
    {
        public string AssembleFoodAsString(object sender, FoodFactory factory)
        {
            Food food = factory.CreateFood(FoodType.Hamburger);
            if (sender.GetType().Name == "default_aspx")
            {
                return string.Format("The price for the hamburger is: ${0}", food.FoodPrice.ToString());
            }
            else
            {
                return food.FoodPrice.ToString();
            }  
        }
 
        public Food AssembleFoodObject(FoodFactory factory)
        {
            Food food = factory.CreateFood(FoodType.Hamburger);
            return food;
        }
    }

Calling factory
FoodFactory factory = new ConcreteFoodFactory(); //create an instance of the factoryenter code here
lblUser.Text = new FoodAssembler().AssembleFoodAsString(this, factory); //call the assembler which formats for string output

Object o = new FoodAssembler().AssembleFoodObject(factory); //example: instantiating anon object, initialized with created food object
like image 770
dbobrowski Avatar asked Nov 04 '10 18:11

dbobrowski


2 Answers

Sorry. That is a quite inflexxible factory. Reflection can giva some POWWAH!!

public interface IFood
{
    bool IsTasty { get; }
}
public class Hamburger : IFood
{
    public bool IsTasty {get{ return true;}}
}
public class PeaSoup : IFood
{
    public bool IsTasty { get { return false; } }
}

public class FoodFactory
{
    private Dictionary<string, Type> _foundFoodTypes =
        new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);

    /// <summary>
    /// Scan all specified assemblies after food.
    /// </summary>
    public void ScanForFood(params Assembly[] assemblies)
    {
        var foodType = typeof (IFood);
        foreach (var assembly in assemblies)
        {
            foreach (var type in assembly.GetTypes())
            {
                if (!foodType.IsAssignableFrom(type) || type.IsAbstract || type.IsInterface)
                    continue;
                _foundFoodTypes.Add(type.Name, type);
            }
        }

    }

    /// <summary>
    /// Create some food!
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public IFood Create(string name)
    {
        Type type;
        if (!_foundFoodTypes.TryGetValue(name, out type))
            throw new ArgumentException("Failed to find food named '" + name + "'.");

        return (IFood)Activator.CreateInstance(type);
    }

}

Usage:

var factory = new FoodFactory();
factory.ScanForFood(Assembly.GetExecutingAssembly());

Console.WriteLine("Is a hamburger tasty? " + factory.Create("Hamburger").IsTasty);

Edit, feedback on your code:

First of all, factories are used to be able to create objects with a little code changes as possible when adding new types of implementations. Using an enum means that all places that are invoking the factory need to use an enum and be updated when the enum changes.

Sure, it's still a bit better than creating types directly.

The second problem with your code is that you are using a switch statement (but that's the best way to do it if the enum is an requirement). It's better to be able to register all different classes in some way. Either from a config file or by allowing the actual implementations (for instance the Hamburger class) to register themselves. This requires that the factory follows the singleton pattern.

Here comes Reflection to the rescue. Reflection allows you to go through all types in DLLs and EXEs. So we can search for all classes that implements our interface and therefore be able to build a dictionary will all classes.

like image 65
jgauffin Avatar answered Sep 22 '22 12:09

jgauffin


I think your explanation including the real world example is good. However, I don't think your example code shows the real benefits of the pattern.

Some possible changes:

  • I wouldn't have the enum in parallel to the types. This looks like you have to update the enum every time a type is added. It might be more appropriate to pass the System.Type. Then you can even make the factory a generic with a template argument.
  • I think the pattern is more "impressive" if you use it for creating something like a hardware interface. You'd then have a "AbstractNetworkDevice" and all your callers don't know which hardware setup you have. But the factory can create a "TcpNetworkDevice" or a "SerialNetworkDevice" or whatever based on some configuration which was made at startup.
like image 25
Philipp Avatar answered Sep 22 '22 12:09

Philipp