A Glimpse on Strategy Pattern

How to use Strategy Pattern - a kickstarter

Definition : This is used when there are a group of algorithms that can be used interchangeably without the caller bothering about the implementation.

Example

Let's take scenario where the task is implement a Car in game. Requirement :

  1. can be of type electric , fuel run(Petrol, Diesel)

  2. can be an automatic or a manual gear system

  3. can have three types of roof SUN,MOON,SOLID

Looking at the problem statement, this can be solved using inheritance by thinking of the car as the base class, then sub-class based on each of the properties

// Base Car class with common properties
abstract class Car {
    constructor(public roofType: string) {}

    abstract describe(): string;
}

// Power Source Inheritance
abstract class ElectricCar extends Car {
    powerSource = 'Electric';

    describe(): string {
        return `${this.powerSource} Car with ${this.roofType} roof`;
    }
}

abstract class FuelCar extends Car {
    abstract fuelType: string;

    describe(): string {
        return `${this.fuelType} Car with ${this.roofType} roof`;
    }
}

// Specific Fuel Types
class PetrolCar extends FuelCar {
    fuelType = 'Petrol';
}

class DieselCar extends FuelCar {
    fuelType = 'Diesel';
}

// Gear System Inheritance
abstract class AutomaticCar extends Car {
    gearSystem = 'Automatic';

    describe(): string {
        return `${super.describe()} and ${this.gearSystem} gear system`;
    }
}

abstract class ManualCar extends Car {
    gearSystem = 'Manual';

    describe(): string {
        return `${super.describe()} and ${this.gearSystem} gear system`;
    }
}

// Combining features using multiple inheritance (mimicked via mixins)
type Constructor<T = {}> = new (...args: any[]) => T;

function AutomaticFuelCar<T extends Constructor<FuelCar>>(Base: T)

Look at this when you want to make the car that has combination of different options in different sectors has given us more tedious code and keeps growing with more number of variables this is because we are tightly coupling the functionality with an is-a relationship by using Inheritance line of thought.

Change in thought

Now, the change in point of view is to make a Car class and then give it implementation details as an input while creating the object. This will decouple the structure from implementation and give us the freedom to use different ways of implementation.

For a relationship point of view, instead of saying FuelCar is-a Car like we do in Inheritance, we are saying Car has-a PowerSource,Rooftop ,GearSystem. Car has become a clubbing of these three or Composition .

Read the code you will understand.

interface IPowerSource {
getType(): string;
}

class PetrolEngineStrategy implements IPowerSource {
    getType() {
        return "runs on a Petrol Engine";
    }
}

class DieselEngineStrategy implements IPowerSource {
    getType() {
        return "runs on a Diesel Engine";
    }
}

class ElectricBatteryStrategy implements IPowerSource { 
    getType() {
        return "runs on a Battery";
    }
}
interface IGearSystem {
getType(): string;
}

class ManualGearSystemStrategy implements IGearSystem {
getType() {
    return "is Manual geared";
    }
}

class AutomaticGearSystemStrategy implements IGearSystem {
getType() {
    return "is Automatic geared";
    }
}

interface IRooftop {
    getType(): string;
}
class SunRooftopStrategy implements IRooftop {
    getType() {
        return "has a Sun Rooftop";
    }
}
class SolidRooftopStrategy implements IRooftop {
    getType() {
        return "has a solid Rooftop";
    }
}

class Car {
    private powerSource: IPowerSource;
    private gearSystem: IGearSystem;
    private roofTop: IRooftop;

    constructor(
        powerSource: IPowerSource,
        gearSystem: IGearSystem,
        roofTop: IRooftop
                ) {
    this.powerSource = powerSource;
    this.gearSystem = gearSystem; 
    this.roofTop = roofTop;
               }

    describe(): string {
        return `This car ${this.powerSource.getType()}, 
                ${this.gearSystem.getType()} , 
                and ${this.roofTop.getType()}.`;
        }

}

let expensiveCar = new Car(
    new ElectricBatteryStrategy(),
    new AutomaticGearSystemStrategy(),
    new SunRooftopStrategy()
);
console.log(expensiveCar.describe());

Here you are can keep adding need Functionalities or types of cars like Gasoline Powered or NO-ROOFED car with making any changes in the base car class itself.

Conclusion

Strategy is used when your class knows the input - output format to expect but you want keep changing the internal algorithm to derive the output