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()
}
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:
While these issues can be addressed, one is still left: complexity.
This is complex because:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With