lundi 12 novembre 2018

Use Of Factory Method/Abstract Factory Pattern

I have the following simplified requirement of parsing an inventory file that contains the letter of the brand of car and the corresponding spec string on each line. For example:

A Sedan
B Blue

And below I provide a simplified version of the code:

    class StockManager
    {
        List<ICar> cars = new List<ICar>();
        public StockManager(List<string> inventoryFileLines)
        {
            foreach(var inventoryFileLine in inventoryFileLines)
            {
                string[] parts = inventoryFileLine.Split(' ');
                cars.Add(CreateCar(parts[0], parts[1]));
            }
        }

        public decimal CalculateTotal()
        {
            decimal total = 0;
            foreach(var car in cars)
            {
                total += car.GetPrice();
            }
            return total;
        }

        public ICar CreateCar(string brand, string spec)
        {
            if(brand == "A")
            {
                return new CarA(spec);
            }else if(brand == "B")
            {
                return new CarB(spec);
            }
            throw new Exception();
        }
    }


    interface ICar
    {
        decimal GetPrice();
    }

    class CarA : ICar
    {
        string type;

        public CarA(string type)
        {
            this.type = type;
        }
        public decimal GetPrice()
        {
            if(type == "Sedan")
            {
                return 30000;
            }
            else if (type == "SUV")
            {
                return 50000;
            }
            throw new Exception();
        }
    }

    class CarB : ICar
    {
        string color;

        public CarB(string color)
        {
            this.color = color;
        }
        public decimal GetPrice()
        {
            if (color == "Orange")
            {
                return 20000;
            }else if (color == "Red")
            {
                return 25000;
            }
            throw new Exception();
        }
    }

In the future, new brands and specs might be added. This is the change that I should anticipate and provide flexibility for. Now I want to apply the right design patterns but apply them for the right reasons, not just for the sake of having applied a design pattern. (As stated by GoF: “A design pattern should only be applied when the flexibility it affords is actually needed.”)

The first thing that came to my mind is the factory method or abstract factory pattern. So when there is a new car brand C is added in the future:

Factory Method

Make CreateCar virtual and override it in the new StockManager class that I will be using:

class StockManager2 : StockManager
{
    public StockManager2(List<string> inventoryFileLines) : base(inventoryFileLines) { }
    public override ICar CreateCar(string brand, string spec)
    {
        if (brand == "C")
        {
            ...
        }
        return base.CreateCar(brand, spec);
    }
}

Abstract Factory

Make CreateCar method into its own abstract factory class and provide it to the StockManager class.


Both these refactorings look great if I want to use different alternative creation options at run-time, such as multiple valid CreateCar factories. And the Maze example is given by GoF also expands on this idea.

But as a matter of fact, the change I anticipate is not an alternative factory but a modified factory. So it seems much more logical to me to modify the CreateCar method instead of creating a new factory class and leaving the old one obsolete (speaking for the Abstract Factory method here). And the same holds true for creating a second StockManager2 class in the case of Factory method. I know that Open/Closed principle says not to modify the class but extend it, and the factory pattern does exactly that but does the above example warrant its use, given the extensibility requirement I mentioned in the beginning? It seems like the requirement is not an extension in the sense explained in GoF but a true modification instead. But I would like to be corrected if I am wrong.

Aucun commentaire:

Enregistrer un commentaire