Aaron Li

Solid: Liskov Substitution Principle (LSP)

Table of Content

Liskov Substitution Principle (LSP) states

Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a sub-type of T. This means that every subclass or derived class should be substitutable for their base or parent class.

Sub-Type

objects of the subtype should be substitutable for objects of the base type without causing unexpected behavior or violating the expected behavior of the program.

Example with Violation (Invalid)

Class

class Rectangle {
  ...
  setWidth(width) {
    this.width = width 
  }

  setHeight(height) {
    this.height = height
  }

  calculateArea() {
    return this.width * this.height
  }
}

class Square extends Rectangle {
  constructor(side) {
    this.side = side;
  }
  setWidth(width) {
    this.width = width 
    this.height = width
  }
  setHeight(height) {
    this.height = height
    this.width = width
  }
}

why does it valid LSP?

test('Rectangle area should be width times height', () => {
  rect = new Rectangle()
  rect.setWidth(5)
  rect.setHeight(4)
  expect(rect.calculateArea()).toBe(20);
}); // pass

test('it should pass if square is a sub-type of rectangle', () => {
  rect = new Square()
  rect.setWidth(5)
  rect.setHeight(4)
  expect(rect.calculateArea()).toBe(20);
}); // fail

it cause unexpected behavior and violating the expected behavior of the program

When can we use inheritance ? (source: Chat-GPT)

  1. Covariance of Argument (Input Parameter Covariance): The rule of covariance of argument states that the overridden method in the subclass must accept a more derived (specific) type for its input parameters compared to the base class method. In simpler terms, the parameter types in the subclass method can be a subclass of the parameter types in the base class method.

  2. Contravariance of Result (Return Type Contravariance): The rule of contravariance of result states that the overridden method in the subclass must return a more derived (specific) type as the result compared to the base class method. In other words, the return type of the subclass method can be a superclass of the return type of the base class method.

  3. Exception Rule (Exception Constraint Rule): The exception rule states that the subclass should not throw exceptions that are not declared in the base class method's throws clause. In other words, the exceptions thrown by the subclass should be the same or a subset of the exceptions declared by the base class method.

  4. Post-condition Rule (Behavior Preservation Rule): The post-condition rule states that the subclass method should meet the post-conditions (behavioral guarantees) of the base class method. The post-conditions define what is guaranteed to be true after the method has executed. The subclass method should not weaken or violate these guarantees.

  5. Invariant Rule (Consistency Rule): The invariant rule states that the subclass should not change the class invariants of the base class. Class invariants are conditions that must be true for all instances of the class. The subclass must ensure that these invariants remain valid before and after method invocations.

  6. History Rule (History Constraint Rule): The history rule (also known as the "constraint rule") applies to methods that use or rely on the object's history (previous states or data). The subclass should not introduce constraints that are not present in the base class method. It means that any conditions or constraints applied by the subclass method should be consistent with those applied by the base class method.

  7. Pre-condition Rule (Input Constraint Rule): The pre-condition rule states that the subclass method should strengthen the pre-conditions (input constraints) of the base class method. In other words, the requirements for valid input parameters in the subclass method should be more restrictive than those in the base class method.