Sam Story

JAVA - 리스코프 치환 원칙 본문

JAVA

JAVA - 리스코프 치환 원칙

Sam H 2024. 10. 15. 17:44

오늘은 지난번에 이어서 SOLID 원칙 중 하나인

리스코프 치환 원칙에 대해 포스팅 하려한다.

 

SOLID 원칙에 관한건 아래 포스팅 참고

 

https://samtistory.tistory.com/49

 

객체지향 프로그래밍 SOLID 원칙

지난번 포스팅 했던 객체지향 프로그래밍에 이어서객체지향 프로그래밍의 5가지 원칙 SOLID 원칙에 대해 포스팅 하려한다.  SOLID 원칙 이란? SOLID란 객체 지향 프로그래밍을 하면서 지켜야하

samtistory.tistory.com

 

리스코프 치환 원칙(LSP)

 

리스코프 치환 원칙은 서브타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다는 원칙이다.

간단히 말하면 다형성 원리를 이용하기 위한 원칙이라고 보면된다.

 

리스코프 치환원칙은 다형성의 특징을 이용하기 위해

상위 클래스 타입으로 객체를 선언, 하위 클래스의 인스턴스를 받으면

부모의 메서드를 사용해도 동작이 의도대로 흘러가야 하는것을 의미한다.

 

 

복잡하게 생각할 것 없이 자바를 코딩하면서 사용해온 다형성을

규칙으로 문서화한 것이 리스코프 치환 원칙이다.

 

 

리스코프 치환원칙의 핵심은 부모 클래스의 행동 규약을 자식 클래스가 위반하면 안 된다는 것이다.

 

 

여기서 행동 규약을 위반한다는 것은 크게 2가지로 나뉜다.

 

1. 자식 클래스가 부모 클래스의 메서드 시그니처를 멋대로 변경한다.

2. 자식 클래스가 부모 클래스의 메서드를 의도와 다르게 오버라이딩 한다.

 

 

첫번째 위반하는 내용을 코드로 한번 살펴보자.

 

Animal class , Elephant class

public class Animal {

    int power = 100;

    int bite(int weight) {

        return power * weight;

    }

}

public class Elephant extends Animal{

    // bite 메서드를 오버로딩 했으나
    // 반환타입과 매개변수를 바꿨음
    String bite(int power , String name) {

        if(name.equals("코끼리")) {

            return power + "힘으로 코를 휘둘렀습니다.";

        } else {

            return power + "힘으로 물었습니다.";

        }

    }

}

 

 

Main class

public class Main {
    public static void main(String[] args) {

        Animal elephant = new Elephant();

        elephant.bite(100,"코끼리");

    }
}

 

위처럼 메서드 오버로딩을 부모클래스가 아닌 자식 클래스에서 하게 될 경우

리스코프 치환 원칙을 어기게 되는것이다.

 

그리고 이렇게 작성한 이 코드는 작동조차 하지 않는다.

 

 

실행 결과

 

이럴때는 새로운 메서드를 만들어 사용하는것이 좋다.

 

 

 

이번엔 잘못된 오버라이딩의 경우를 보자.

 

AnimalType class , Animal class , Elephant class

public class AnimalType { // 동물 판별 클래스

    String type;

    // 생성자 매개변수로 animal 객체를 받는다.
    AnimalType(Animal animal) {
     
        // animal 객체의 instance가 Elephant 객체일 때    
        if(animal instanceof Elephant) {

            this.type = "초식";

        } else {

            this.type = "육식";

        }

    }

    String print() {

       return "이 동물은 " + type + " 동물 입니다.";

    }

}

// 동물 클래스
public class Animal {
     
    // AnimalType 객체 리턴하는 메서드
    AnimalType getType() {

        AnimalType animalType = new AnimalType(this);
        return animalType;

    }

}

// 코끼리 클래스
public class Elephant extends Animal{}

 

 

Main class

public class Main2 {
    public static void main(String[] args) {

        Animal elephant = new Elephant();
        String result = elephant.getType().print();

        System.out.println(result);

    }
}

 

 

실행 결과

 

 

먼저 Animal class 에 확장되는 동물객체를 만들고 ,

getType() 메서드를 통해 AnimalType 객체 인스턴스를 만들어

AnimalType의 print() 메서드를 호출하여 값을 얻는 형태이다.

 

 

그런데 협업하는 다른 개발자가 이런식으로 구성하면 뭔가 번거로울 것 같아, 

자기 멋대로 자식 클래스에 부모 메서드인 getType() 의 반환값을 null로 

 

오버라이딩 설정하여 메서드를 사용하지 못하게 설정하고, 

대신 Elephant class 에 getName() 이라는 메서드를 만들어 한번에 출력하도록 설정하면 어떻게 될까

 

 

Elephant class

public class Elephant extends Animal{

    @Override
    AnimalType getType() {

        return null;

    }

    String getAnimalType() {

        return "이 동물은 초식 동물 입니다.";

    }

}

 

 

Main class

public class Main2 {
    public static void main(String[] args) {

        Animal elephant = new Elephant();
        String result = elephant.getType().print();

        System.out.println(result);

    }
}

 

 

실행결과

 

이처럼 NullPointerException 예외 발생하게 된다.

 

 

이것이 리스코프 치환 원칙의 중요 포인트다.

 

자식 클래스로 부모 클래스의 내용을 상속하는데,

기존 코드에서 보장하던 조건을 수정하거나 적용시키지 않아서,

기존 부모 클래스를 사용하는 코드에서 예상하지 않은 오류를 발생시킨 것이다.

 

만일 컴파일 단에서 오류를 체크해주면 좋을텐데,

코드 구성상 문제가 없기 때문에 이렇게 예측하지 못한 에러가 발생한 것이다.

따라서 사전에 약속한 기획대로 구현하고, 상속 시 부모에서 구현한 원칙을 따라야 한다가 이 원칙의 핵심이다.

 

 

 

결국 리스코프 치환 원칙이란, 

다형성의 특징을 이용하기 위해 상위 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면, 

업캐스팅된 상태에서 부모의 메서드를 사용해도 동작이 의도대로만 흘러가도록 구성하면 되는 것이다.

 

'JAVA' 카테고리의 다른 글

JAVA - 의존 역전 원칙  (2) 2024.10.20
JAVA - 인터페이스 분리 원칙  (1) 2024.10.16
JAVA - 개방 폐쇄 원칙  (0) 2024.10.14
JAVA - 단일 책임의 원칙  (1) 2024.10.12
JAVA - 2~36진수 기수변환  (0) 2024.09.29