A Factory Pattern that will satisfy the Open/Close

2019-02-08 17:41发布

问题:

I have the following concrete Animal products: Dog and Cat.

I am using a parameterized Factory method to create said products. Depending on the AnimalInfo parameter that is passed to the Factory method, a concrete product will be created. The mapping logic is placed in the Factory method.

Here is my code:

 public abstract class AnimalInfo
    {
        public abstract String Sound { get; }
    }

    public class DogInfo : AnimalInfo
    {
        public override string Sound
        {
            get { return "Bark"; }
        }
    }

    public class CatInfo : AnimalInfo
    {
        public override string Sound
        {
            get { return "Meow"; }
        }
    }

    public abstract class Animal
    {
        public abstract void Talk();
    }

    public class Dog : Animal
    {
        private readonly DogInfo _info;

        public Dog(DogInfo aInfo)
        {
            _info = aInfo;
        }

        public override void Talk()
        {
            Console.WriteLine(_info.Sound);
        }
    }

    public class Cat : Animal
    {
        private readonly CatInfo _info;

        public Cat(CatInfo aInfo)
        {
            _info = aInfo;
        }

        public override void Talk()
        {
            Console.WriteLine(_info.Sound);
        }
    }

Here's my Factory method with its logic:

public static class AnimalFactory
{
    public static Animal CreateAnimal(AnimalInfo aInfo)
    {
        if (aInfo is DogInfo)
            return new Dog(aInfo as DogInfo);
        if (aInfo is CatInfo)
            return new Cat(aInfo as CatInfo);
        return null;
    }
}

The problem I'm seeing here is that the Factory method itself violates the Open/Closed principle in such a way that if I add a new Animal, I will need to modify the Factory method to reflect the new mapping.

Is there a way to make the creation more "dynamic" via reflection? More importantly, is there any anti-pattern in my design?

回答1:

The easy way out is to make AnimalInfo itself the factory:

public abstract class AnimalInfo<T> where T: Animal
{
    public abstract String Sound { get; }
    public abstract T CreateAnimal();
}

Implementation for DogInfo:

public class DogInfo : AnimalInfo<Dog>
{
    public override string Sound
    {
        get { return "Bark"; }
    }

    public override Dog CreateAnimal()
    {
        return new Dog(this);
    }
}

You could keep your current static Factory if you wanted to:

public static class AnimalFactory
{
    public static Animal CreateAnimal(AnimalInfo aInfo)
    {       
        return aInfo.CreateAnimal();
    }
}

Not exactly strict adherance to the Factory pattern, IMO, but no longer violates your open/close principle.



回答2:

Let me sidestep a bit. The SOLID principles are good. But realize at some point, the principles break down, a fact even the originator of the SOLID term acknowledges. Yes, you want to follow single responsibility, open/closed, etc., but when you do so, something has to know how to create all those things that are otherwise nicely decoupled with single responsibilities.

Think about one of the things Uncle Bob said regarding ifs and switches in your code. "Have it exactly once." It stands to reason that the long if or the switch will indeed be a violation of SRP and OCP. And that's OK, if you have that violation once.

So go ahead, have your

if (a)
   return x;
else if (b)
   return y;
else if (c)
   return z;
else
   throw new InvalidOperationException();

And have it once. Yes, it's a violation of OCP. Yes, it might violate SRP. But something somewhere has to. The key is reducing the number of those somethings and those somewheres.



回答3:

If you are looking for a reflection based method, you could do something like the following...

public static class AnimalFactory
{
    public static Animal CreateAnimal(Type animalType)
    {
        return Activator.CreateInstance(animalType) as Animal;
    }

    public static Animal CreateAnimal(string animalType)
    {
        Type type = Assembly.GetExecutingAssembly().GetType(animalType);
        return Activator.CreateInstance(type) as Animal;
    }


}