SOLID에서 L에 해당되는 Liskov Substitution Principle(LSP)이다. 이 원칙은 서브 타입은 기반 타입을 대체할 수 있다는 법칙이다. 즉, 부모 클래스 A를 상속한 클래스 B는 언제든 A클래스를 대체할 수 있다는 뜻이다. 이것은 다형성(polymorphism)을 말한 법칙이다. 이것을 코드로 확인해보고자 한다.

코드

const Rectagle = class {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
};

const Square = class extends Rectagle {
  constructor(length) {
    super(length, length);
  }

  getArea() {
    return this.width * this.height + "입니다.";
  }
};

const rec = new Square();
rec.width = 10;
rec.height = 20;
console.log("rec", rec.getArea()); // rec 200입니다.

const rec2 = new Rectagle(5, 7);
rec2.width = 10;
rec2.height = 20;
console.log("rec2", rec2.getArea()); // rec2 200

위 코드는 LSP를 위반했다. Square클래스의 getArea()메소드는 number 타입을 반환해야하는데 string을 반환하고 있다. 이는 부모와의 규약을 어긴행동이고 서브타입인 Square가 기반타입인 Rectangle을 대체할 수 없기 때문이다. 위 코드를 LSP를 위배하지 않고 하려면 같은 number타입을 반환해야 한다.

주 위반 사례

부모의 메소드의 시그니처가 다른경우

const Animal = class {
  constructor(name) {
    this.name = name;
  }

  say() {
    console.log("my name is", this.name);
  }
};

const Person = class extends Animal {
  constructor(name) {
    super(name);
  }

  say(age) {
    console.log("my name is", this.name, age);
  }
};

const App = class {
  constructor(person) {
    this.person = person;
  }

  run() {
    this.person.say();
  }
};

const app = new App(new Person("철현"));
app.run();

Person클래스의 say 메소드는 Animal클래스의 say 메소드는 필요한 인자가 다르다(시그니처가 다르다) 그래서 Animal클래스를 상속받은 Person클래스는 Animal클래스를 대체할 수 없다.

잘못된 오버라이딩 메소드

const Animal = class {
  constructor() {}
  speak() {
    throw "override method!";
  }
};

const Cat = class extends Animal {
  constructor() {
    super();
  }
  speak() {
    console.log("냥냥!");
  }
};

const Dog = class extends Animal {
  constructor() {
    super();
  }
  speak() {
    console.log("왈왈!");
  }
};

const Fish = class extends Animal {
  constructor() {
    super();
  }
  speak() {
    throw "난 짖을 수 없어요.";
  }
};

const animalArr = [new Cat(), new Dog(), new Fish()]; // Animal이니까 가능

for (const animal of animalArr) {
  animal.speak();  // error (fish는 speak이 에러)
}

다음과 같이 Animalspeak()메소드를 override하도록 Animal클래스의 speak을 추상 메소드로 만들어 놓았다. 하지만 speak()이 불가능한 Fish클래스가 Animal클래스를 오버라이딩 했기 때문에 하단에서 에러가 났다. 이와 같은 문제는 정상적으로 실행하다가 런타임에 발견되어 협업하는 개발자에게 큰 문제를 일으킨다.

정리

다형성을 원칙화한 LSP를 잘 활용한다면 유연한 코드를 제공하지만 잘못된 사용은 협업관계에 문제를 일으키기 때문에 조심해야 한다.

자바스크립트에서는 형이 자유롭기 때문에 크게 사용될 일이 없을 것 같지만, 프로그래밍 개발론 중 보편화된 개체지향을 이해하기 위해 정리해 보았다.

참조