Page 1 of 1

Design Patterns - Factory Analysis of Factory Pattern

#1 MentalFloss  Icon User is offline

  • "ADDICTED"[2:5]
  • member icon

Reputation: 526
  • View blog
  • Posts: 1,397
  • Joined: 02-September 09

Posted 12 August 2010 - 08:10 AM

*
POPULAR

Hello again.

For this tutorial, we're going to do something interesting. We're going to take the existing Strategy Pattern tutorial and modify the code but that's for a little later.

First, we're going to look at creating a bare bones factory.

Here is the pattern website's implementation for reference (Factory Pattern).

Now a factory allows us to create objects without knowing what was actually created. This allows for versatility in object creation and also provides a centralized location for such creations.

In this example, we'll create a factory that makes products that might be in a hardware store. We'll have an interface called IProduct and some concrete products such as Hammer, ScrewDriver, Nail, etc.

Then we'll wrap it up by seeing how we can apply it to the existing candy store code.

So, let's get started!

namespace FactoryDemo
{
    /// <summary>
    /// Contract specification for any product in the system.
    /// </summary>
    public interface IProduct
    {
        string Name { get; }
        decimal Price { get; }
    }
}



This is a basic product contract.

With this completed, let's create our product items:

namespace FactoryDemo
{
    public class Hammer : IProduct
    {
        public string Name { get; private set; }
        public decimal Price { get; private set; }

        public Hammer()
        {
            Name = "Hammer";
            Price = 9.00M;
        }
    }

    public class ScrewDriver : IProduct
    {
        public string Name { get; private set; }
        public decimal Price { get; private set; }

        public ScrewDriver()
        {
            Name = "Screw Driver";
            Price = 3.50M;
        }
    }

    public class Saw : IProduct
    {
        public string Name { get; private set; }
        public decimal Price { get; private set; }

        public Saw()
        {
            Name = "Saw";
            Price = 15.25M;
        }
    }

    public class Wrench : IProduct
    {
        public string Name { get; private set; }
        public decimal Price { get; private set; }

        public Wrench()
        {
            Name = "Wrench";
            Price = 7.15M;
        }
    }
}



So, we'll create our main method now without knowing how to work with a factory. This is just to see the difference and what this solves:

namespace FactoryDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            IProduct product = null;

            // Hammer:
            product = new Hammer();
            PrintProduct(product);

            // Screw Driver:
            product = new ScrewDriver();
            PrintProduct(product);

            // Saw:
            product = new Saw();
            PrintProduct(product);

            // Wrench:
            product = new Wrench();
            PrintProduct(product);

            // pause for output:
            Console.ReadLine();

        }

        private static void PrintProduct(IProduct product)
        {
            Console.WriteLine("{0}, {1:C}", product.Name, product.Price);
        }
    }
}



Quote

Hammer, $9.00
Screw Driver, $3.50
Saw, $15.25
Wrench, $7.15


So, that's not bad right? It works and it's pretty clear to see what's going on. However, instantiation is handled in main and we would like to change that.

We need a static method that returns an IProduct that will serve as our factory. We also need a way to specify what needs built. I personally like enumerations for this task so let's use that:

namespace FactoryDemo
{
    public enum ProductTypes
    {
        Hammer,
        ScrewDriver,
        Saw,
        Wrench
    }
}



Now our factory can take the enum and return an actual instantiation of our product:

namespace FactoryDemo
{
    public static class ProductFactory
    {
        public static IProduct Create(ProductTypes productType)
        {
            switch (productType)
            {
                case ProductTypes.Hammer:
                    return new Hammer();
                case ProductTypes.ScrewDriver:
                    return new ScrewDriver();
                case ProductTypes.Saw:
                    return new Saw();
                case ProductTypes.Wrench:
                    return new Wrench();
                default:
                    return null;
            }
        }
    }
}



Now we can clean up our main method and make it use our factory instead:

namespace FactoryDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            IProduct product = null;
            
            // Hammer without new...
            product = ProductFactory.Create(ProductTypes.Hammer);
            PrintProduct(product);

            // Screw Driver without new...
            product = ProductFactory.Create(ProductTypes.ScrewDriver);
            PrintProduct(product);

            // Saw without new...
            product = ProductFactory.Create(ProductTypes.Saw);
            PrintProduct(product);

            // Wrench without new...
            product = ProductFactory.Create(ProductTypes.Wrench);
            PrintProduct(product);

            // pause for output:
            Console.ReadLine();

        }

        private static void PrintProduct(IProduct product)
        {
            Console.WriteLine("{0}, {1:C}", product.Name, product.Price);
        }
    }
}



There you have it:

Quote

Hammer, $9.00
Screw Driver, $3.50
Saw, $15.25
Wrench, $7.15


Exact same behavior except now we are not actually tied to any one implementation.

Now for that candy store I promised :)

namespace StrategyPatternDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            List<IProduct> CandyList = new List<IProduct>()
            {
                new JawBreaker(),
                new BubbleGum(),
                new Taffy()
            };

            List<RecordEntry> records = new List<RecordEntry>();

            // First Arizona:
            foreach (IProduct product in CandyList)
            {
                IStore store = new ArizonaStore(product);
                records.Add(
                    new RecordEntry()
                    {
                        Location = store.Location,
                        SalesTax = String.Format("{0:P}", store.SalesTax - 1),
                        CandyName = product.Name,
                        BasePrice = String.Format("{0:C}", product.Price),
                        TotalPrice = String.Format("{0:C}", store.Total)
                    }
                );
            }

            // Next Colorado:
            foreach (IProduct product in CandyList)
            {
                IStore store = new ColoradoStore(product);
                records.Add(
                    new RecordEntry()
                    {
                        Location = store.Location,
                        SalesTax = String.Format("{0:P}", store.SalesTax - 1),
                        CandyName = product.Name,
                        BasePrice = String.Format("{0:C}", product.Price),
                        TotalPrice = String.Format("{0:C}", store.Total)
                    }
                );
            }

            // Finally California:
            foreach (IProduct product in CandyList)
            {
                IStore store = new CaliforniaStore(product);
                records.Add(
                    new RecordEntry()
                    {
                        Location = store.Location,
                        SalesTax = String.Format("{0:P}", store.SalesTax-1),
                        CandyName = product.Name,
                        BasePrice = String.Format("{0:C}", product.Price),
                        TotalPrice = String.Format("{0:C}", store.Total)
                    }
                );
            }

            // Last but not least, print:
            foreach (RecordEntry record in records)
            {
                Console.WriteLine(record.ToString());
            }

            // And a pause:
            Console.ReadLine();
        }
    }
}



We have two things we can make factories for. We can make factory for the product like above and we can make a factory for the stores themselves.

Let's get started with the product. I am again going to use an enumeration:

namespace StrategyPatternDemo
{
    public enum ProductTypes
    {
        JawBreaker,
        BubbleGum,
        Taffy
    }
}



Next, we create our factory for our products:

namespace StrategyPatternDemo
{
    public static class ProductFactory
    {
        public static IProduct Create(ProductTypes productType)
        {
            switch (productType)
            {
                case ProductTypes.JawBreaker:
                    return new JawBreaker();
                case ProductTypes.BubbleGum:
                    return new BubbleGum();
                case ProductTypes.Taffy:
                    return new Taffy();
                default:
                    return null;
            }
        }
    }
}



With that, let's clean up our main part.

namespace StrategyPatternDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            List<IProduct> CandyList = new List<IProduct>()
            {
                ProductFactory.Create(ProductTypes.JawBreaker),
                ProductFactory.Create(ProductTypes.BubbleGum),
                ProductFactory.Create(ProductTypes.Taffy)
            };

            // *** SNIP ***
        }
    }
}



When we run it, we get:

Quote

Arizona, 6.00 %, Jaw Breaker : $0.50, $0.53
Arizona, 6.00 %, Bubble Gum : $0.75, $0.80
Arizona, 6.00 %, Taffy : $1.25, $1.33
Colorado, 4.00 %, Jaw Breaker : $0.50, $0.52
Colorado, 4.00 %, Bubble Gum : $0.75, $0.78
Colorado, 4.00 %, Taffy : $1.25, $1.30
California, 8.00 %, Jaw Breaker : $0.50, $0.54
California, 8.00 %, Bubble Gum : $0.75, $0.81
California, 8.00 %, Taffy : $1.25, $1.35


So, this is exactly the same behavior as before.

Next up, we need to create a factory for our stores. This time though, let's use a string which represents the two letter code of the state.

namespace StrategyPatternDemo
{
    public static class StoreFactory
    {
        public static IStore Create(string stateCode, IProduct product)
        {
            string code = stateCode.ToLower();

            switch (code)
            {
                case "az":
                    return new ArizonaStore(product);
                case "co":
                    return new ColoradoStore(product);
                case "ca":
                    return new CaliforniaStore(product);
                default:
                    return null;
            }
        }
    }
}



Finally, let's clean up main:

namespace StrategyPatternDemo
{
    class Program
    {
        private static List<IProduct> candyList = new List<IProduct>();
        private static List<RecordEntry> records = new List<RecordEntry>();

        private static void AddProducts()
        {
            candyList.Add(ProductFactory.Create(ProductTypes.JawBreaker));
            candyList.Add(ProductFactory.Create(ProductTypes.BubbleGum));
            candyList.Add(ProductFactory.Create(ProductTypes.Taffy));
        }

        private static void AddRecords(string state)
        {
            IStore store = null;
            foreach (IProduct product in candyList)
            {
                store = StoreFactory.Create(state, product);

                records.Add(
                    new RecordEntry()
                    {
                        Location = store.Location,
                        SalesTax = String.Format("{0:P}", store.SalesTax - 1),
                        CandyName = product.Name,
                        BasePrice = String.Format("{0:C}", product.Price),
                        TotalPrice = String.Format("{0:C}", store.Total)
                    }
                );
            }
        }

        private static void PrintRecords()
        {
            foreach (RecordEntry record in records)
            {
                Console.WriteLine(record.ToString());
            }
        }

        static void Main(string[] args)
        {
            AddProducts();
            AddRecords("AZ");
            AddRecords("CO");
            AddRecords("CA");
            PrintRecords();

            // And a pause:
            Console.ReadLine();
        }
    }
}



And notice we have the exact same output:

Quote

Arizona, 6.00 %, Jaw Breaker : $0.50, $0.53
Arizona, 6.00 %, Bubble Gum : $0.75, $0.80
Arizona, 6.00 %, Taffy : $1.25, $1.33
Colorado, 4.00 %, Jaw Breaker : $0.50, $0.52
Colorado, 4.00 %, Bubble Gum : $0.75, $0.78
Colorado, 4.00 %, Taffy : $1.25, $1.30
California, 8.00 %, Jaw Breaker : $0.50, $0.54
California, 8.00 %, Bubble Gum : $0.75, $0.81
California, 8.00 %, Taffy : $1.25, $1.35


So, that's how we take already pretty good code and make it better! Main no longer makes anything.

Is This A Good Question/Topic? 8
  • +

Page 1 of 1