리스코프 치환의 원칙 (LSP)
in Development on Design Pattern
LSP(Liskov Substitution Principle)
- ‘자식 클래스는 부모 클래스가 사용되는 곳에 대체될 수 있어야 한다’는 원칙이다. 즉, ‘B가 A의 자식 타입이면 부모 타입인 A 객체는 자식 타입인 B로 치환해도, 작동에 문제가 없어야 한다’.
- 상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 작동해야 한다.
기본적인 뜻
이 원칙은 자식 클래스를 구현하는 개발자가 기존 프로그램이 문제없이 안정적으로 작동할 수 있도록 가이드라인을 알려주는 원칙이라고 볼 수 있을 것이다. 가장 기본적인 정의로는 A - B의 부모 자식에 대한 정의가 논리적으로 제대로된 상속이어야. 기존 프로그램이 자식 클래스인 B로 치환해도 문제 없이 작동해야한다는 것이다. 일반적으로 많이 드는 예시가 바로 직사각형을 상속한 정사각형 클래스의 예시인데, 정사각형 클래스가 직사각형 클래스를 상속해버리면, 정사각형의 특징인 ‘네 변의 길이는 동일하다’와 그렇지 않은 직사각형의 차이로 인해, 직사각형을 정사각형 클래스로 치환해서 사용할 때, 네 변의 길이에 대한 두 클래스의 특징 차이 때문에 기존 프로그램이 오작동 할 수 있다는 얘기이다.
포괄적인 뜻
기본적인 뜻은 그렇지만, 이 원칙은 자식 클래스의 잘못된 상속 구현으로 인한 문제에 대해 좀 더 포괄적인 의미를 내포하고 있다. 개발자 관점으로 더 자세히 얘기하면, ‘부모 클래스 타입인 A를 사용하는 기존의 프로그램 코드가 자식 클래스 B로 대입 시켰을 때도 문제 없이 작동하도록 하기 위해서, 자식 클래스는 부모 클래스가 따르던 계약 사항을 자식도 따라야한다.’이다. 여기서 계약이란, 클래스의 멤버가 어떻게 작동하는지에 대한 구현 조건 사항 같은 것을 말한다.
LSP를 지켰을 때
상위 타입(SuperClass)과 하위 타입(SubClass)가 있다고 가정하자. SuperClass 또는 SubClass의 로직을 사용하고자 하는 클래스의 특정 메서드를 보자. someMethod()의 파라미터의 타입을 SubClass를 받지 않고 SuperClass를 받음으로써, 이 메서드에 상위 타입 또는 하위 타입 둘 중 아무 객체를 전달해도 someMethod()가 정상작동한다.
public void someMethod(SuperClass sc) {
sc.method();
}
이 메서드에 밑과 같이 하위 타입의 객체를 전달해도 someMethod()가 정상적으로 동작해야 한다는 것이 리스코프 치환 원칙이다.
someMethod(new SubClass());
LSP를 지키지 않았을 때
Rectangle은 setWidth()
, increaseWidth()
만 사용하고 싶고, Square은 setWidth()
만 사용하고 싶다고 가정하자. 그래서 밑과 같이 코드를 구현했다.
상위 클래스 (Rectangle - 직사각형)
public class Rectangle {
public void setWidth() {
...
}
public void increaseWidth(Rectangle rectangle) {
if (rectangle instanceof Square)
throw new IllegalArgumentException("해당 클래스는 이 메서드를 사용할 수 없습니다.");
}
rectangle.setHeight();
}
}
하위 클래스 (Square - 정사각형)
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
...
}
}
위 코드를 다시 살펴보자면, 상위 타입의 객체(Rectangle
클래스의 increaseWidth()
)를 하위 타입의 객체(Square
클래스의 increaseWidth()
)로 치환하게 되면, 프로그램이 정상 작동 하지 않고 예외를 throw하게 된다. 그러므로 리스코프 치환 원칙을 어긴 것이다.