C++ - 리스코프 치환 원칙 (LCP, Liskov Substitution Principle)
Architecture & Design Pattern 전체 링크
리스코프 치환 원칙 (LCP, Liskov Substitution Principle)
- 객체의 하위 타입은 기본 타입으로 대체될 수 있어야 한다.
- 상속 관계에 있는 클래스들 사이에서 자식 클래스는 부모 클래스의 기능을 완전히 대체해야 한다.
먼저 아래의 리스코프 치환 원칙 위반 사례를 보자.
#include <iostream>
using namespace std;
class Rectangle
{
protected:
int width, height;
public:
Rectangle(int width, int height) : width(width), height(height) {}
virtual void setWidth(int w) { width = w; }
virtual void setHeight(int h) { height = h; }
int getWidth() const { return width; }
int getHeight() const { return height; }
int getArea() const { return width * height; }
};
class Square : public Rectangle
{
public:
Square(int size) : Rectangle(size, size) {}
void setWidth(int w) override { width = height = w; }
void setHeight(int h) override { width = height = h; }
};
void changeHeight(Rectangle* r)
{
r->setHeight(10);
cout << "Area = " << r->getArea() << endl;
}
int main()
{
Rectangle* rectangle = new Rectangle(5, 5);
Square* square = new Square(5);
changeHeight(rectangle); // 50
// → changeHeight(square); // 100
return 0;
}
수학적으로 볼 때, 정사각형은 직사각형에 포함된다.
따라서 정사각형 Square는 Rectangle을 상속하여
setWidth와 setHeight에서 width와 height가 항상 같도록 오버라이딩하였다.
이제 높이를 10으로 변경해서 넓이를 출력하는 Rectangle을 인자로 받는 changeHeight를 실행해보자.
너비와 높이가 모두 5인 직사각형을 changeHeight로 실행하면 넓이가 50이 출력되지만,
길이가 5인(너비와 높이가 모두 5인) 정사각형을 changeHeight로 실행하면 100이 출력된다.
Rectangle을 Square로 대체할 수 없기 때문에 리스코프 치환 원칙에 위배된다.
즉, 수학적으로 부분 집합관계라고 해서 객체 지향적으로 상속 관계가 되어야 하는 아니다.
Rectangle과 Square는 넓이라는 공통 특성을 가지고 있으므로 Shape 클래스를 각각 상속받아 따로 구현해야 한다.
class Shape
{
public:
virtual double area() const = 0;
};
리스코프 치환 원칙 적용 사례
Animal 클래스는 makeSound 메서드를 가지고 있고, 모든 동물에게 오버라이딩 될 수 있다.
Dog 클래스는 Animal 클래스를 상속받고, makeSound를 재정의한다.
동물의 소리를 출력하는 printSound는 Animal 객체를 인자로 받고 있고, 어떤 동물 객체라도 받을 수 있다.
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
virtual void makeSound() const { cout << "Basic Sound" << endl; }
};
class Dog : public Animal
{
public:
void makeSound() const override { cout << "barks" << endl; }
};
void printSound(const Animal* animal)
{
animal->makeSound();
}
int main()
{
Animal* animalPtr = new Dog();
printSound(animalPtr);
delete animalPtr;
return 0;
}
Dog 클래스가 Animal 클래스의 기능을 완전히 대체하고 있으므로 리스코프 치환 원칙을 준수하고 있다.