Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resolve class type using enum

I have a group of classes (following strategy pattern) in my project. In the main function, I receive an enum value from the server and based on that I create an object of the base class type.

I am using switch/case statement to achieve this. I read somewhere that the Open/Closed principle does not allow opening a function to add a new case statement whenever a new class is added.

I am thinking of using a Activator.CreateInstance(). Is there any drawback to it.

Is there any other way to create an object from the enum type?

Adding example below even though it is not a full fledged Strategy pattern

abstract public class Mammal
{
   public abstract void MakeSound()
}

class Cat:Mammal
{      
    public override  void MakeSound()
    {
        Console.WriteLine("Meow");        
    }    
}

class Dog:Mammal
{

    public override void MakeSound()
    {
         Console.WriteLine("Bow");        
    }    
}

Main()
{

    MammalTypes mammalType = RecieveValueFromServer();
    Mammal mammalBase
    switch(mammalType) // need to make this dynamic depending upon Enum type
    {
        case MammalTypes.Cat:mammalBase = new Cat()
                             break;
        case MammalTypes.Dog:mammalBase = new Dog()
                             break;            
    }

    mammalBase.MakeSound()
}
like image 565
logeeks Avatar asked Jan 22 '12 07:01

logeeks


2 Answers

One method for achieving true OCP might be the following:

Define an abstract method Is to force every concrete subtype of Mammal to specify whether it is appropriate for a given value of the enum:

abstract public class Mammal
{
    public abstract void MakeSound();

    public abstract bool Is(MammalTypes mammalType);
}

The implementations of Is in the subclasses would look like:

class Cat : Mammal
{
    // other specific members

    public override bool Is(MammalTypes mammalType)
    {
        return mammalType == MammalTypes.Cat;
    }
}

class Dog : Mammal
{
    // other specific members

    public override bool Is(MammalTypes mammalType)
    {
        return mammalType == MammalTypes.Dog;
    }
}

This being done, we can now create a MammalFactory class that, when given a Mammal enum value scans through the available classes and, when it finds a match, it returns an instance of that class:

public class MammalFactory
{
    private readonly IEnumerable<Type> _mammalTypes;

    public MammalFactory()
    {
        var currentAssembly = Assembly.GetExecutingAssembly();

        _mammalTypes = currentAssembly.GetTypes()
            .Where(t => typeof(Mammal).IsAssignableFrom(t) && !t.IsAbstract);
    }

    public Mammal Create(MammalTypes mammalType)
    {
        return _mammalTypes
            .Select(type => CreateSpecific(type, mammalType))
            .First(mammal => mammal != null);
    }

    public Mammal CreateSpecific(Type type, MammalTypes mammalEnumType)
    {
        var mammalInstance = (Mammal)Activator.CreateInstance(type);

        return mammalInstance.Is(mammalEnumType) ? mammalInstance : null;
    }
}

The final usage will look like this:

var mammalFactory = new MammalFactory();

var guessWhatMammal = mammalFactory.Create(MammalTypes.Cat);

This fully complies to OCP. It is only necessary to create a new Mammal class for it to be automatically wired and ready to use within the application. (no need to modify anything else in the application, except for the enum itself)

There are some problems with this approach:

  • it only scans the currently executing assembly for Mammal types
  • it has to create an instance of Mammal every time it needs to test whether that type is appropriate

While these issues can be addressed, one is still left: complexity.

This is complex because:

  • we've doubled the amount of code needed
  • the auto-wiring might be confusing for people new to the project

I think the conclusion is this: design patterns are not strict rules. It's not worth doing something just to conform to a given design. Instead, we have to be pragmatic and find that perfect balance between pattern conformance and usefulness/simplicity/readability. This heavily depends on the problem we attempt to solve and in many cases it might very well be the switch statement you presented in the question.

like image 65
Cristian Lupascu Avatar answered Oct 17 '22 05:10

Cristian Lupascu


You could use a Dictionary from the enum type to a function. The functions creates your strategy object:

public delegate Strategy StrategyFactory();
var strategyFactories = new Dictionary<MyEnum, StrategyFactory>();

This dictionary used to create your objects based on enum values:

var newStategy = strategyFactories[myEnumValue]();

the factory functions need to be added to the dictionary somehow. For that you can expose register (and maybe unregister) methods.

like image 26
Christian Horsdal Avatar answered Oct 17 '22 06:10

Christian Horsdal