SECRET OF CSS

The Big Car Showroom and the Strategy Design Pattern | by Abhijeet Rai | Jun, 2022


Let’s say we secured the positions of software developer interns at a supermarket. Our task is to develop an application for the market that lists all the cars on sale.

0*bFOZnR 7FdWadpRM
A huge supermarket that sells everyday items, from needles to cars.
1*F1zdw7bsz3u8DbSkkkUGbw
Basic approach

We started with the basics and we let all the car types inherit from the class Car. The listManufacturer method lists the details and brand of the car manufacturer and it varies from subclass to subclass in our case, which is why it is kept abstract and class Car is an abstract class. Methods run and carryPassengers are ordinary and generic methods as shown below.

void run() {
System.out.println("Run from point A to point B");
}
void carryPassengers() {
System.out.println("Carry passengers and their luggages");
}

Everything looks perfect and the `Inheritance` seems to be solving all of our problems here like the perfect piece of the puzzle.

But the dummy car or toy car does not have carryPassengers feature and it should not inherit the carryPassengers method. In such scenarios, implement the following object oriented design principle.

Identify aspects of your applications that vary (carryPassengers) and separate them from what remains same. 

Abstract methods listManufacturer and run remain the same for subclasses as far as inheritance is concerned. While carryPassengers method does not. Let’s encapsulate what changes so it does not affect rest of the code.

1*6 2UgrMb4nGFmuKAiE8QVQ
Modified approach

We wrote an interface Carriable which has abstract method carryPassengers. And only those cars (classes) implement the interface which can actually carry passengers. Bingo! We successfully segregated the varying part in implementation. Let’s focus on code maintenance.

Here, carryPassengers() is separately and individually implemented in each Carriable class. (As of now, let’s ignore other methods for simplicity.)

class TeslaCar extends Car implements Carriable {
void carryPassengers() {
System.out.println("Carry passengers and their luggages");
}
}
class HondaCar extends Car implements Carriable {
void carryPassengers() {
System.out.println("Carry passengers and their luggages");
}
}

Now imagine, we need to change the behaviour of method carryPassengers and carryPassengers consists of 100 lines of code and there are 100 classes of type Carriable.

To change the behaviour of method carryPassengers, we will need to touch 100 classes of type Carriable individually and separately. Well this does nothing but invites bugs into code and makes code maintenance difficult. Touching each subclass of type Carriable renders the code design inflexible.

1*gn7YAHsTyvSFYhngQDJaNQ
Final approach

The concrete implementation of carryPassengers from Carriable interface has been given in class Carry and CarryNot

class Carry implements Carriable {
public void carryPassengers() {
System.out.println("Carry passengers and their luggages");
}
}
Class CarryNot implements Carriable {
public void carryPassengers() {
System.out.println("Cannot carry passengers and their luggages");
}
}

On the other hand, abstract class Car has instance variable carriable to look at concrete class Carry or CarryNot and consequently to look at varying implementation of method carryPassengers encapsulated in classes Carry and CarryNot.

Each subclass of class Car will be looking at either Carry or CarryNot via inherited instance variable carriable of type Carriable.

The abstract class Car has method setCarriable(Carriable carriable) to decide which type of Carriable to look at, whether to look at class Carry or CarryNot.

void setCarriable(Carriable carriable) {
this.carriable = carriable;
}

Each subclass of class Car will also inherit carryPassengers() method from parent abstract class Car. The method carryPassengers does nothing but calls carryPassengers() method of already set carriable.

void carryPassengers() {
carriable.carryPassengers();
}

Now if we need to change behaviour of carry method, we need to touch only one subclass Carry. This approach is far more flexible than the previous approach.

Strategy design pattern defines a family of algorithms (in our case, the varying code of carryPassengers) encapsulates each one (in our case, class Carry and CarryNot) and makes them interchangable (in our case, Carriable carriable variable could look to classes Carry and CarryNot). It lets algorithms vary independently from the client that uses it. (In our case, we could modify carryPassengers behaviour without touching subclasses of class Car).



News Credit

%d bloggers like this: