Search
Duplicate
🚙

9. LSP: 리스코프 치환 원칙

: S 타입의 객체 o1 각각에 대응하는 T 타입 객체 o2가 있고 T 타입을 이용해서 정의한 모든 프로그램 P에서 o2 자리에 o1을 치환하더라도 P의 행위가 변하지 않는다면 S는 T의 하위 타입이다.

상속을 사용하도록 가이드하기

: License라는 클래스가 있다고 가정해보자
: 이 클래스는 calcFee()라는 메서드를 가지며 Billing 애플리케이션에서 이 메서드를 호출한다.
: License는 PersonalLicense와 BusinessLicense라는 두 가지 하위 타입이 존재한다.
: 이들 두 하위타입은 서로 다른 알고리즘을 이용해서 라이선스 비용을 계산한다.
: 이 설계는 LSP를 준수하는데
: Billing의 행위가 License 하위 타입 중 무엇을 사용하는지에 전혀 상관하지 않기 때문이다.
: 이들 하위 타입은 모두 License 타입을 치환할 수 있다.

정사각형/직사각형 문제

: LSP를 위반하는 전형적인 문제로는 유명한 정사각형/직사각형 문제가 있다.
: 이 예제에서 Square는 Rectangle의 하위 타입으로 적합하지 않다.
: Rectangle의 높이와 너비는 서로 독립 적으로 변경될 수 있는 반면, Square의 높이와 너비는 반드시 함께
변경되기 때문이다.
: User 어플리케이션은 대화하고 있는 상대가 Rectangle이라고 생각해야하지만 혼동이 발생할 수 있다.
: 결국 Rectangle과 Square는 서로의 하위타입이 될 수 없다는 것이다.

LSP와 아키텍처

: 객체 지향이 혁명처럼 등장한 초창기에 LSP는 상속을 사용하도록 가이드하는 방법 정도로 간주되었으나 시간이 지나며 인터페이스와 구현체에도 적용되는 설계 원칙이 되었다.
: 여기서 인터페이스는 인터페이스 하나와 이를 구현하는 여러 개의 클래스로 구성된다. 아키텍처 관점에서 LSP를 이해하는 최선의 방법은 이 원칙을 어겼을 때, 무슨일이 일어나는
지 관찰하는 것이다.

LSP 위배 사례

: 다양한 택시 파견 서비스를 통합하는 애플리케이션을 만들고 있다고 해보자, 고객은 어떤 태깃업체인지는 신경쓰지 않고 자신의 상황에 가장 적합한 택시를 찾는다.
: 고객이 이용할 택시를 결정하면 시스템은 REST 서비스를 통해 선택된 택시를 고객 위치로 파견한다.
: 택시 파견 REST 서비스의 URI가 운전기사 데이터베이스에 저장되어 있다고 가정해보자
: 시스템이 고객에게 알맞은 기사를 선택하면 해당 기사의 레코드로부터 URI 정보를 얻은 다음, 그 URI 정보를 이용하여 해당 기사를 고객의 위치로 파견한다.
: 만약 이 상태에서 특정 택시회사를 제외해야하는 상황이 발생한다면 파견 명령어를 구성하는 모듈에 해당 로직을 추가해야할 것이다.
: 해당 택시회사를 직접 코드에 기입하는 것은 끔찍할 뿐만 아니라 이해가 불가능한 온갖 오류를 발생할 여지를 만들게 된다.

결론

: LSP는 아키텍처 수준까지 확장할 수 있고 반드시 확장해야 한다. 치환 가능성을 조금이라도 위배하면 시스템 아키텍처가 오염되어 상당량의 별도 메커니즘을 추가해야 하므로