Liskov Substitution Principle in JavaScript

Liskov Substitution Principle(LSP) is extension of Open/Closed Principle

LSP?

If 'S' is the subtype of 'T' then 'T' may be completely substitute by subtype 'S'. Here 'T' is the Base class and 'S' is the Derived class.

It mean that Derive class must be completely substitutable for their Base classes. The LSP actually define the Subtype Relation and relation type is Strong behaviour subtype

body_img.png

LSP implementation Rules

  1. Client should know which Subtype they are using.

  2. Derive class just a extension of Base class, without replacing the functionality of Base class.

  3. No new exception, until they are added in Base class.

confuse.gif

Are you confused 🤷‍♀️🤷‍♂️? Don't worry we will validate each point with an example.

We will create 3 class, lets assume first is Employee class which will have all the functionality employee should have, then create second class Permanent Employee and third class Contract Employee.

Here Employee class(T) is the Base class and derive class will be Permanent Employee and Contract Employee (S, subtype)

Derive class should be completely substitute their Base class

Employee Class

class Employee {
  constructor(name, employee_id, department, basic_salary) {
    this.name = name;
    this.employee_id = employee_id;
    this.department = department;
    this.basic_salary = basic_salary;
  }

  getEmployeeDetails() {
    console.log('Employee Name: ', this.name);
    console.log('Employee id: ', this.employee_id);
    console.log('Department is: ', this.department);
  }

  getSalaryDetails() {
    console.log('Basic Salary is: ', this.basic_salary);
  }
}

Permanent Class

class Permanent extends Employee {
  constructor(name, employee_id, department, designation, basic_salary, tds){
    super(name, employee_id, department, basic_salary);
    this.type = 'Permanent';
    this.designation = designation;
    this.tds = tds;
  }

  getOtherDetails() {
     console.log('Department is: ', this.designation);
     console.log('Employee type is: ', this.type);
  }

  getSalaryDetsils() {
     console.log('Basic salary is: ', this.basic_salary);
     console.log('TDS is: ', this.tds);
  }
}

Contract Class

class Contract extends Employee {
  constructor(name, employee_id, department, designation, basic_salary, tds){
    super(name, employee_id, department, basic_salary);
    this.type = 'Contract';
    this.designation = designation;
    this.tds = tds;
  }

  getOtherDetails() {
     console.log('Department is: ', this.designation);
     console.log('Employee type is: ', this.type);
  }

  getSalaryDetsils() {
     console.log('Basic salary is: ', this.basic_salary);
     console.log('TDS is: ', this.tds);
  }
}

Create instance of Permanent class and Contract class

const emp1 = new Permanent('Abc', 11234, 'IT', 'Software Engineer', 10000, 4000);
const emp2 = new Contract('Efg', 11001, 'Management', 'BDM', 20000, 5000);

emp1.getEmployeeDetails();
emp1.getSalaryDetails()
emp2.getEmployeeDetails();
emp2.getSalaryDetails()

const emp1 = new Permanent('Biplab', 11234, 'IT', 'Software Engineer', 10000, 4000)

Here Permanent and Contract class are subtype of Employee class and they are able to substitute the Employee class totally. It means emp1 have the access of all behaviours and member of Employee class.

Derived class just extension of Base class without replacing the functionality of old class

Employee class have getSalaryDetails() and Permanent, Contract classes also have getSalaryDetails() function. Means Base class getSalaryDetails() behaviour is changed by the Subtype classes(derived class). It should not be done according to the LSP. To achieve this we will do some changes in all classes for that.

class Employee {
  constructor(name, employee_id, department, basic_salary) {
    this.name = name;
    this.employee_id = employee_id;
    this.department = department;
    this.basic_salary = basic_salary;
  }

  getEmployeeDetails() {
    console.log('Employee Name: ', this.name);
    console.log('Employee id: ', this.employee_id);
    console.log('Department is: ', this.department);
  }

  getSalaryDetails(tds) {
    console.log('Basic Salary is: ', this.basic_salary);
    console.log('TDS is: ', tds);
    console.log('Total salary is: ', this.basic_salary + tds);
  }
}

class Permanent extends Employee {
  constructor(name, employee_id, department, designation, basic_salary, tds){
    super(name, employee_id, department, basic_salary);
    this.type = 'Permanent';
    this.designation = designation;
    this.tds = tds;
  }

  getOtherDetails() {
     console.log('Department is: ', this.designation);
     console.log('Employee type is: ', this.type);
  }
}

class Contract extends Employee {
  constructor(name, employee_id, department, designation, basic_salary, tds){
    super(name, employee_id, department, basic_salary);
    this.type = 'Contract';
    this.designation = designation;
    this.tds = tds;
  }

  getOtherDetails() {
     console.log('Department is: ', this.designation);
     console.log('Employee type is: ', this.type);
  }
}

Now, we move getSalaryDetails() function to Base class and remove from both Permanent and Contract class.

const emp1 = new Permanent('Abc', 11234, 'IT', 'Software Engineer', 10000, 4000);
const emp2 = new Contract('Efg', 11001, 'Management', 'BDM', 20000, 5000);

emp1.getSalaryDetails(emp1.tds);
emp2.getSalaryDetails(emp2.tds);

Now our Permanent and Contract class is become extension of the Employee class and will not change the behaviour of Employee class.

Not throw new exception, until its added in Base class

Every employee have bonus, so we are going to add new function to calculate bonus in both Permanent and Contract class. But Contract Employee does not have bonus. So we have to throw an error if contract employee want to know their bonus.

Permanent Class

class Permanent extends Employee {
  constructor(name, employee_id, department, designation, basic_salary, tds){
    super(name, employee_id, department, basic_salary);
    this.type = 'Permanent';
    this.designation = designation;
    this.tds = tds;
  }

  getOtherDetails() {
     console.log('Department is: ', this.designation);
     console.log('Employee type is: ', this.type);
  }

  calculateBonus() {
    const bous_amount = this.basic_salary * .15; // bonus is basic salary 15%
    console.log('Bonus amount is: ', bous_amount);
  }
}

Contract Class

class Contract extends Employee {
  constructor(name, employee_id, department, designation, basic_salary, tds){
    super(name, employee_id, department, basic_salary);
    this.type = 'Contract';
    this.designation = designation;
    this.tds = tds;
  }

  getOtherDetails() {
     console.log('Department is: ', this.designation);
     console.log('Employee type is: ', this.type);
  }

  calculateBonus() {
   throw "Contract employee does not have bonus";
  }
}

Execute the code

const emp1 = new Permanent('Abc', 11234, 'IT', 'Software Engineer', 10000, 4000);
const emp2 = new Contract('Efg', 11001, 'Maagement', 'BDM', 20000, 5000);

emp1.calculateBonus();
emp2.calculateBonus(); //Contract employee does not have bonus

According to the LSP the subtype or derived class not allow to throw new exception until it will not present in Base class. So, our above code violate the LSP rule. Now we will do some changes in all classes to ensure that LSP rule.

Employee Class

class Employee {
  constructor(name, employee_id, department, basic_salary) {
    this.name = name;
    this.employee_id = employee_id;
    this.department = department;
    this.basic_salary = basic_salary;
  }

  getEmployeeDetails() {
    console.log('Employee Name: ', this.name);
    console.log('Employee id: ', this.employee_id);
    console.log('Department is: ', this.department);
  }

  getSalaryDetails(tds) {
    console.log('Basic Salary is: ', this.basic_salary);
    console.log('TDS is: ', tds);
    console.log('Total salary is: ', this.basic_salary + tds);
  }

  calculateBonus(emp) {
    if(emp.type == 'Permanent') {
      const bous_amount = this.basic_salary * .15; // bonus is basic salary 15%
      console.log('Bonus amount is: ', bous_amount);
    } else {
      throw "Contract employee does not have bonus";
    }
  }
}

Permanent Class

class Permanent extends Employee {
  constructor(name, employee_id, department, designation, basic_salary, tds){
    super(name, employee_id, department, basic_salary);
    this.type = 'Permanent';
    this.designation = designation;
    this.tds = tds;
  }

  getOtherDetails() {
     console.log('Department is: ', this.designation);
     console.log('Employee type is: ', this.type);
  }
}

Contract Class

class Contract extends Employee {
  constructor(name, employee_id, department, designation, basic_salary, tds){
    super(name, employee_id, department, basic_salary);
    this.type = 'Contract';
    this.designation = designation;
    this.tds = tds;
  }

  getOtherDetails() {
     console.log('Department is: ', this.designation);
     console.log('Employee type is: ', this.type);
  }
}

Here we remove the function calculateBonus() from Permanent and Contract class both and added the same in Employee class(Base class). And also add exception to the Base class instead of Subtype

Execute

const emp1 = new Permanent('Abc', 11234, 'IT', 'Software Engineer', 10000, 4000);
const emp2 = new Contract('Efg', 11001, 'Maagement', 'BDM', 20000, 5000);

emp1.calculateBonus(emp1);
emp2.calculateBonus(emp2);

Now our code satisfy all the rules of LSP(Liskov Substitution Principle).

You can find full code from here

Feel free to comment if you have any query🤔 regarding this article💡!!

No Comments Yet