본문 바로가기

자바

리스코프 치환원칙

리스코프 치환 원칙(Liskov subsititon Principle)
public class Dog extends Animal{
    @Override
    public void speak() {
        System.out.println("멍멍!!");
    }
}
정의
자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행이 보장되야 한다.
다르게 말해서는..
다형성의 특징을 이용하기 위해 상위 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면, 업캐시팅된 상태에서 부모 메소드를 사용해도 동작이 의도대로만 흘러가도록 구성해야 한다는 것이다.
자바에서 개발하다보면 아래와 같은 코드를 매우 많이 봤을 것이다.
List tmp = new ArrayList<String>();
List 는 인터페이스이고 ArrayList는 List인터페이스를 구현한 구현체이다.
인터페이스 객체인 tmp안에 인터페이스의 구현체 ArrayList 를 넣는 것이다.

다형성의 원리가 적용되서 인터페이스 변수 tmp 안에 기존의 ArrayList 뿐만이 아니라 아래와 같은 코드들도 가능해야 한다.
List tmp = new Stack();
이게 가능해야 한다는 매우 지당한 사실이 리스코프 치환원칙을 의미한다.
이를 자동 타입 변환 이라고도 부른다.
왜 반드시 그래야 하는가?
Animal 이라는 추상클래스를 이용해서 추상메소드 speak를 채워주면 동물이 낼 수 있는 소리를 구현시켜 줄 수 있다고 가정해보자.
abstract public class Animal {
    public abstract void speak();
}
public class Cat extends Animal{
    @Override
    public void speak() {
        System.out.println("야옹ㅋㅋ");
    }
}
근데 갑자기 Fish 클래스를 추가해야 하는 상황이 발생했다. 물고기는 소리를 못내잖아. 근데 speak() 라는 메소드는 강제로 구현해야 한다. 물고기는 소리를 못내니까 speak 메소드를 동작하지 못하게 예외를 던지도록 설정했다.
class Fish extends Animal {
    void speak() {
        try {
            throw new Exception("물고기는 말할 수 없음");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
나혼자 구현하면 이렇게 해도 상관은 없다. 근데 개발이 보통 협업으로 돌아가는 상황이 많고 Animal 이라는 추상클래스의 speak 이라는 추상메소드에서는 동물의 소리를 설정하기로 "약속"을 했다.
물고기 객체를 사용하는 다른 개발자 입장에서는 갑자기 뜬근없이 예외를 던지니까 개발자 상호간의 신뢰를 잃게 된다.
참고 자료 출처

'자바' 카테고리의 다른 글

예외처리  (2) 2023.11.13
추상화란 추상클래스란  (0) 2023.10.23
객체지향의 4대 특징  (4) 2023.10.22