8
\$\begingroup\$

I am working through more tutorials on Design Patterns and came across the Abstract Factory Pattern. I extended it out a bit (the example that is) and want your opinion:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DoFactory.GangOfFour.Abstract.RealWorld
{
    class MainApp
    {
        /// <summary>
        /// Entry point into console application.
        /// </summary>
        public static void Main()
        {
            // Create and run the African animal world
            ContinentFactory africa = new AfricaFactory();
            AnimalWorld world = new AnimalWorld(africa);
            world.RunFoodChain();

            // Create and run the American animal world
            ContinentFactory america = new AmericaFactory();
            world = new AnimalWorld(america);
            world.RunFoodChain();

            // Wait for user input
            Console.ReadKey();
        }
    }


    /// <summary>
    /// The 'AbstractFactory' abstract class
    /// </summary>
    abstract class ContinentFactory
    {
        public abstract Herbivore CreateHerbivore();
        public abstract Carnivore CreateCarnivore();
        public abstract Problem CreateProblem();
        public abstract Giant CreateGiant();
        public abstract Giant CreateSmallGiant();
    }

    /// <summary>
    /// The 'ConcreteFactory1' class
    /// </summary>
    class AfricaFactory : ContinentFactory
    {
        public override Herbivore CreateHerbivore()
        {
            return new Wildebeest();
        }
        public override Carnivore CreateCarnivore()
        {
            return new Lion();
        }
        public override Problem CreateProblem()
        {
            return new Trouble();
        }

        public override Giant CreateGiant()
        {
            return new Elephant();
        }

        public override Giant CreateSmallGiant()
        {
            return new SmallElephant();
        }
    }

    /// <summary>
    /// The 'ConcreteFactory2' class
    /// </summary>
    class AmericaFactory : ContinentFactory
    {
        public override Herbivore CreateHerbivore()
        {
            return new Bison();
        }
        public override Carnivore CreateCarnivore()
        {
            return new Wolf();
        }
        public override Problem CreateProblem()
        {
            return new Trouble();
        }

        public override Giant CreateGiant()
        {
            return new Elephant();
        }

        public override Giant CreateSmallGiant()
        {
            return new SmallElephant();
        }
    }

    /// <summary>
    /// The 'AbstractProductA' abstract class
    /// </summary>
    abstract class Herbivore
    {
    }

    abstract class Giant
    {
        public abstract void Stomp(Herbivore h);
        public abstract void MakeSound();
    }

    abstract class Problem
    {
        public abstract void Eat(Carnivore c);
    }

    /// <summary>
    /// The 'AbstractProductB' abstract class
    /// </summary>
    abstract class Carnivore
    {
        public abstract void Eat(Herbivore h);
    }

    /// <summary>
    /// The 'ProductA1' class
    /// </summary>
    class Wildebeest : Herbivore
    {
    }

    class Elephant : Giant
    {
        public override void Stomp(Herbivore h)
        {
            Console.WriteLine(this.GetType().Name +
                " stomps on " + h.GetType().Name);
        }

        public override void MakeSound()
        {
            Console.WriteLine(this.GetType().Name +
                " makes trumpet sound!");
        }
    }

    class SmallElephant : Giant
    {
        public override void Stomp(Herbivore h)
        {
            Console.WriteLine(this.GetType().Name +
                " lightly stomps on " + h.GetType().Name);
        }

        public override void MakeSound()
        {
            Console.WriteLine(this.GetType().Name +
                " makes a small trumpet sound!");
        }
    }

    class Trouble : Problem
    {
        public override void Eat(Carnivore c)
        {
            Console.WriteLine(this.GetType().Name +
                " eats " + c.GetType().Name);
        }
    }

    /// <summary>
    /// The 'ProductB1' class
    /// </summary>
    class Lion : Carnivore
    {
        public override void Eat(Herbivore h)
        {
            // Eat Wildebeest
            Console.WriteLine(this.GetType().Name +
              " eats " + h.GetType().Name);
        }
    }

    /// <summary>
    /// The 'ProductA2' class
    /// </summary>
    class Bison : Herbivore
    {
    }

    /// <summary>
    /// The 'ProductB2' class
    /// </summary>
    class Wolf : Carnivore
    {
        public override void Eat(Herbivore h)
        {
            // Eat Bison
            Console.WriteLine(this.GetType().Name +
              " eats " + h.GetType().Name);
        }
    }

    /// <summary>
    /// The 'Client' class
    /// </summary>
    class AnimalWorld
    {
        private Herbivore _herbivore;
        private Carnivore _carnivore;
        private Problem _problem;
        private Giant _giant;
        private Giant _giant2;

        // Constructor
        public AnimalWorld(ContinentFactory factory)
        {
            _carnivore = factory.CreateCarnivore();
            _herbivore = factory.CreateHerbivore();
            _problem = factory.CreateProblem();
            _giant = factory.CreateGiant();
            _giant2 = factory.CreateSmallGiant();
        }

        public void RunFoodChain()
        {
            _carnivore.Eat(_herbivore);
            _problem.Eat(_carnivore);
            _giant.Stomp(_herbivore);
            _giant.MakeSound();
            _giant2.MakeSound();
            _giant2.Stomp(_herbivore);
        }
    }
}

What do you think? Originally it had only Eat and MakeSound and only Carnivore and Herbivore.

\$\endgroup\$
  • \$\begingroup\$ In my experience, the factory pattern is a particularly good fit when you have a class that needs to construct instances of other classes, and those classes have other dependencies. What you've created here seems to demonstrate the strategy pattern more than the factory pattern. \$\endgroup\$ – craftworkgames Jun 24 '14 at 12:29
  • \$\begingroup\$ That's sort of confusing. Doesn't the strategy pattern consist of an interface that can be implemented by one or more concrete classes and is used by a context class while an abstract factory pattern is an interface to describe the product, another interface that describes a type of product and then a concrete class that creates concrete products using the interface that describes the product and another class that uses the itnerface that describes the type of product? The differences are subtle, but there. \$\endgroup\$ – Michael Mahony Jun 24 '14 at 15:15
  • 2
    \$\begingroup\$ ContinentFactory sounds like something that creates continents, not creatures and … problems? That … eat carnivores? This is all really confusing. \$\endgroup\$ – svick Jun 24 '14 at 16:19
  • \$\begingroup\$ Each continent has its own set of animals (hence ContinentFactory) so a continent is created and then animals are created within that continent. "Problem" was my initial attempt to add to this example so I named it something stupid (sorry). \$\endgroup\$ – Michael Mahony Jun 24 '14 at 16:32
  • \$\begingroup\$ Not a big deal, but I think that rather than ContinentFactory a better name for it would be FoodChainFactory. I would say AnimalFactory, except for the fact that it can create "problems" as well as animals. The fact that your two usage examples are continent-specific (Africa and America) is an implementation detail of those usage examples; I don't see any reason why you couldn't use the same abstract factory to model the food chain for a small pond, for example. \$\endgroup\$ – Dr. Wily's Apprentice Jun 25 '14 at 19:11
14
\$\begingroup\$

Single Inheritance

By using abstract classes like this...

abstract class ContinentFactory
{
    public abstract Herbivore CreateHerbivore();
    public abstract Carnivore CreateCarnivore();
    public abstract Problem CreateProblem();
    public abstract Giant CreateGiant();
    public abstract Giant CreateSmallGiant();
}

Really boils down to this:

interface ContinentFactory
{
    Herbivore CreateHerbivore();
    Carnivore CreateCarnivore();
    Problem CreateProblem();
    Giant CreateGiant();
    Giant CreateSmallGiant();
}

You should always prefer interface over abstract class, especially when the abstract class doesn't have anything virtual or protected for the derived types to override or consume.

The reason for this, is because a class in C# can only ever be derived from one single base class, but can implement as many interfaces as you wish. By deriving from a base class you're locking yourself into an inheritance issue.. especially when the base class is used as a marker interface that brings nothing to the table:

abstract class Herbivore
{
}

And I find it's a code smell that every class you derive from Herbivore also has nothing to offer:

class Wildebeest : Herbivore
{
}

class Bison : Herbivore
{
}

Interface Segregation

Your design won't scale very well. In my opinion, the "ideal" abstract factory interface looks something like this:

interface IAbstractFactory
{
    IFactoryProduct Create();
}

In this case I think you need to trim your factory interface / ContinentFactory - all these methods really do this:

interface ICreatureFactory
{
    ICreature Create();
}

This means you would need an ElephantFactory, a LionFactory, a WolfFactory, and so on and so forth. Annoying.

On the other hand, it turns this:

private Herbivore _herbivore;
private Carnivore _carnivore;
private Problem _problem;
private Giant _giant;
private Giant _giant2;

Into that:

private readonly IList<ICreature> _creatures;

Abstract Factory Galore

I think you don't need a factory to create the creatures. Your world could be constructor-injected with any IEnumerable<ICreature> and walk away happy.

Alternatively, the world could be constructor-injected with some IFaunaFactory implementation whose role would be to provide the world with an IEnumerable<ICreature>:

class AfricanFaunaFactory : IFaunaFactory
{
    public IEnumerable<ICreature> Create()
    {
        var result = new List<ICreature>
            { 
                new Lion(),
                new Elephant(),
                new Gorilla(),
                new Hyena()
                // ...
            };

        return result;
    }
}

class AmericanFaunaFactory : IFaunaFactory
{
    public IEnumerable<ICreature> Create()
    {
        var result = new List<ICreature>
            {
                new Wolf(),
                new Bison(),
                new Beaver(),
                new BlackBear()
                // ...
            };

        return result;
    }
}

This design is much easier to extend, and has much less confusing names too. I'd suggest to use simpler interfaces and avoid abstract classes until you identify something that's common to all creatures, that you can write in a virtual method in that base class (which derived types are free to override, or not).

interface ICreature
{
    void MakeSound();
}

interface ICarnivore : ICreature
{
    void Eat(ICreature victim);
}

interface IHerbivore : ICreature
{
    void Eat();
}

interface IGiant : IHerbivore
{
    void Stomp(ICreature victim);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.