This article features a few tricks to reduce the size of the code resulting from the use of the ‘strategy’ pattern. As you can deduce from the title, all these tricks will be related to the usage of generic types.
1. Hierarchy of classes involved in magic rituals
Suppose we have an abstract ‘vehicle’ class that can move (the Move method). This class has 3 descendants: a car, a plane, and a rickshaw. Each of them implements the method in its own way:
abstract class Vehicle { abstract void Move(); } class Car : Vehicle { override void Move() { // burn fuel // spin wheel } } class Plane : Vehicle { override void Move() { // suck air // burn fuel // spew jetstream } } class Rickshaw : Vehicle { override void Move() { // do one step // beg white master for money } }
2. Strategy Application
Suppose, we have new requirements beginning to appear:
1. It became obvious that new types of vehicles are to appear
2. Some of them will implement the Move method similarly. For instance, a car and a locomotive will both burn fuel and spin wheels.
3. The way of movement may be changed. For example, a steam frigate can move with the help of both, sail and steam.
Obviously, it is the time to put the code responsible for the movement into a separate Engine class:
abstract class Vehicle { Engine Engine { get { return engine; } set { if (value != null) { engine = value; } else { throw new ArgumentNullException(); } } } private Engine engine; protected Vehicle(Engine engine) { Engine = engine; } public void Move() { engine.Work(); } } class Car : Vehicle { Car() : base(new InternalCombustionEngine()) { } } class Plane : Vehicle { Plane() : base(new JetEngine()) { } } class Rickshaw : Vehicle { Rickshaw() : base(new Slave()) { } } abstract class Engine { public abstract void Work(); } class InternalCombustionEngine : Engine { public override void Work() { // burn fuel // spin wheel } } class JetEngine : Engine { public override void Work() { // suck air // burn fuel // spew jetstream } } class Slave : Engine { public override void Work() { // do one step // beg white master for money } }
3. Reducing Code Size if Possible
If we find out in a certain fragment of the vehicle hierarchy that we don’t need to change the engine in the course of the work, we can parametrize the class of such vehicle with a type of its engine in order to save a few lines of the code. For the sake of brevity, let’s suppose these changes will affect only the already declared classes:
... abstract class Vehicle<EngineT> : Vehicle where EngineT: Engine { protected Vehicle() : base(new EngineT()) { } } class Car : Vehicle<InternalCombustionEngine> { Car() : base(new InternalCombustionEngine()) { } } class Plane : Vehicle<JetEngine> { Plane() : base(new JetEngine()) { } } class Rickshaw : Vehicle<Slave> { Rickshaw() : base(new Slave()) { } } ...
If the engine classes have constructors without parameters, we can use them and add constraint new() to the EngineT parameter:
... abstract class Vehicle<EngineT> : Vehicle where EngineT: Engine, new() { protected Vehicle() : base(new EngineT()) { } } class Car : Vehicle<InternalCombustionEngine> { } class Plane : Vehicle<JetEngine> { } class Rickshaw : Vehicle<Slave> { } ...
4. Adding More Diversity
Suppose now, that we have only 3 types of engines (yes, three entities, one per type), and many types of vehicles. And each vehicle must query a warehouse for a working engine. This is, from one side, a good example from the perspective of changes taking place in the code, and on the other side, it is a bad example from the perspective of the logic of the real world: switching an engine from one vehicle to another is quite a dubious undertaking. And the consequence of such architectural solution may be really amazing. Here is the code adjusted to the new changes:
... abstract class Vehicle<EngineT> : Vehicle where EngineT : Engine { protected Vehicle() : base(Engine.GetFromWarehouse<EngineT>()) { } } ... abstract class Engine { abstract void Work(); private static readonly IDictionary<Type, Engine> warehouse = new Dictionary<Type, Engine> { { typeof(InternalCombustionEngine), new InternalCombustionEngine() }, { typeof(JetEngine), new JetEngine() }, { typeof(Slave), new Slave() }, }; static Engine GetFromWarehouse<EngineT>() where EngineT : Engine { return warehouse[typeof(EngineT)]; } } ...
5. Can we share one engine between several vehicles?
Of course, we can, but it is better to use another example:
ITugboat tugboat1 = new Steamer(); bargeWITHGrain.HookOn(tugboat1); bargeWITHCoal.HookOn(tugboat1); ferryWITHTourists.HookOn(tugboat1);
I will be glad if this article helps someone.
Tags: c#, design patterns Last modified: September 23, 2021