리스코프 치환 원칙
(C++ 소프트웨어 디자인 책 정리)
하위 타입(subtype) 요구 사항: ψ(x) 를 T 타입 객체 x에 대해 증명할 수 있는 특성이라 하자. 이 때 S가 T의 하위 타입이라면 ψ(y)는 S 타입 객체 y에 대해 참이어야한다.
LSP는 세 번쨰 SOLID원칙이며 행위적 서브타이핑, 즉 추상화로 기대하는 행위와 관련이 있다.
이 원칙은 흔히 IS-A 관계라고 하는 것을 공식화 한다. 이 관계, 즉 추상화의 기대를 하위타입에서 반드시 따라야 한다.
Struct X
{
virtual ~X() = default;
// 사전 조건: 함수는 0보다 큰 모든 'i'를 받아들인다
virtual void f( int i) const
{
assert(i > 0);
// ...
}
};
struct Y : public X
{
/*
사전조건: 함수는 10보다 큰 모든 'i'를 받아들인다.
이는 사전 조건을 강화한다. 1과 10 사이 숫자를 더 이상 허용하지 않으며, 이는 LSP 위반이다.
*/
void f( int i ) const override
{
assert( i > 10);
// ...
}
};
하위 타입의 함수 반환 타입은 반드시 공변적(covariant)이어야 한다. 하위 타입의 멤버 함수는 사위 타입의 해당 멤버 함수에서 반환하는 타입의 하위 타입을 반환할 수 있다. 이 특성은 c++에서 언어적으로 직접 지원한한다. 하지만 위 타입은 상위 타입의 해당 함수에서 반환하는 타입의 어떤 상위 타입도 반환할 수 없다.
struct Base { /*...소멸자를 포함한 가상 함수들 ...*/ };
struct Derived : public Base { /* ... */ };
struct X
{
virtual ~X() = default;
virtual Base* f();
};
struct Y : public X
{
Derived* f() override; // 공변적인 반환 타입
};
하위 타입의 함수 매개변수는 반드시 반공변적(contravariant)이어야 한다. 하위 타입은 멤버 함수에서 상위 타입의 해당 멤버 함수에서 받은 함수 매개변수의 상위 타입을 받아들일 수 있다. 이 특성은 C++에서 언어적으로 직접 지원하지 않는다.
struct Base { /*...소멸자를 포함한 가상 함수들 ...*/ };
struct Derived : public Base { /* ... */ };
struct X
{
virtual ~X() = default;
virtual void f( Derived* );
};
struct Y : public X
{
void f( Base* ) override; // 반공변적 함수 매개변수, C++에서 지원하지 않으므로 컴파일 실패한다.
};
상위 타입의 불변속성을 하위 타입에서 반드시 보존해야 한다. 상위 타입의 상태에 관한 모든 기대는 하위 타입의 멤버 함수를 포함한 모든 멤버 함수 호출 전후로 항상 유효해야 한다.
struct X
{
explicit X(int v = 1)
: value_(v)
{
if ( v < 1 || v > 10) throw std::invalid_argument( /* ... */ );
}
virtual ~X() = default;
int get() const { return value_; }
protected:
int value_; // 불변속성: [1...10] 범위 내이어야 한다
};
struct Y : public X
{
public:
Y()
: X()
{
value_ = 11; // 깨진 불변속성: 생산자 호출 후 'value_'는 기대한 범위를 벗어난다.
}
};