[클린 소프트웨어] chapter 10. 리스코프 치환 원칙 LSP

이다은·2023년 9월 5일
0

독서

목록 보기
5/8
post-thumbnail

리스코프 치환 원칙(LSP) - Liskov Substitution Principle

서브타입(subtype)은 그것의 기반 타입(base type)으로 치환 가능해야 한다.

LSP 위반은 잠재적인 OCP 위반이다. (LSP 위반은 OCP 위반을 유발한다)

IS-A (상속은 IS-A 관계이다)

상속은 IS-A관계에서 사용하는 것이 가장 좋다.
상속에서 IS-A 관계는 일반적인 개념과 구체적인 개념 사이의 관계이다.
상위 클래스가 일반적인 개념이고, 하위 클래스가 구체적인 개념이 된다. 즉, 일반 클래스(상위 클래스)를 구체화 하는 상황에서 상속을 사용해야 한다.

예시
고양이는 동물이다.
Cat is an Animal
💡 상위 클래스: 동물 / 하위 클래스: 고양이

유효성은 본래 갖추어진 것이 아니다

  • 모델만 별개로 본 뒤, 그 모델의 유효성(validity)을 충분히 검증할 수 없다.
  • 처음부터 모든 설계를 예상하려고 한다면 시스템을 불필요한 복잡성의 위기로 이끈다. 취약성을 느낄 때까지 가장 명백한 LSP 위반 을 제외한 나머지의 처리는 연기하는게 최선이다.

계약에 의한 설계(DBC) - design by contract

  • 합리적인 추정을 명시적으로 만들어서 LSP를 강제한다.
  • DBC 를 사용하면 작성자는 클래스의 계약사항을 명시적으로 정한다.
  • 이 계약은 메소드의 사전조건과 사후조건을 선언하는 것으로 구체화된다.
  • 파생된 객체는 기반 클래스가 받아 들일 수 있는 것은 모두 받아들일 수 있어야하며, 기반 클래스의 모든 사후조건을 따라야 한다. 즉, 파생 클래스의 행위와 출력은 기반 클래스의 제약을 위반해서는 안된다.
  • 파생 클래스에서는 사전 조건과 같거나 약한 조건, 사후 조건보다 같거나 강한 수준에서 대체할 수 있다

LSP를 따르는 해결책


예를 들어, PersistentSet이 어떤 스트림에도 쓰이고 나중에 다른 어플리케이션에 의해서도 다시 읽힐 수 있는 집합이라고 가정했을 때,
만약 Add 메서드가 특정 타입인 경우 PersistentObject에서 파생된 것이 아닌 경우에는 LSP를 위배하게 된다.

이때, 위 구조처럼 LSP 관점에서 문제가 되는 메서드는 서브 클래스로 분리하고 문제 없는 메서드는 기반 클래스로 분리하면 문제를 해결할 수 있다.

공통 인자 추출(인터페이스 분리) 하기

LSP를 지키기위한 다양한 방식 중 가장 적용하기 편한 설계 수단이 공통 인자 추출(인터페이스 분리) 방법이다. 공통 인자를 추출하는 방법은 위 사진과 같다.

  • 최상위에 인터페이스를 하나 선언한다. (Shape)
    • 클라이언트는 해당 인터페이스에 의존하면 된다.
  • 인터페이스를 구현하는 추상클래스나 클래스를 생성한다. (Rectangle)
    • 그리고 해당 클래스를 구성 해서 인터페이스를 구현하면 된다. (Square)

LSP 위반의 단서를 보여주는 휴리스틱(heuristic)

LSP 위반의 단서를 보여주는 휴리스틱은 다음과 같다.

  • 기반 클래스보다 덜한 동작을 하는 파생 클래스는 보통 그 기반 클래스와 치환이 불가능하므로 LSP를 위반한다.
  • 기반 클래스에서 발생하지 않는 예외를 파생 클래스의 메소드에 추가 시켰을 때 이들은 치환 가능하지 않을 수 있다.

결론

LSP는 OCP를 가능하게 하는 요인 중 하나이다. 이것은 기반 타입으로 표현된 모듈을 수정 없이도 확장 가능하게 만드는, 서브 타입의 (특히 행위) 치환 가능성을 말한다.
즉, LSP는 모듈을 수정 없이도 확장 가능하게 만들어주기 때문에, "수정에 닫혀있고 확장에 열려있어야 한다" 를 주장하는 OCP 를 가능하게 한다.
또한 LSP에서 상속을 주요하게 다루기 때문에, 관련해서 자주 쓰이는 'IS-A' 관계에 대해서도 잘 알아두면 좋을 것 같다.
끗 !

0개의 댓글