개발은 재밌어야 한다
article thumbnail
반응형

객체 지향 프로그래밍  설계에서 5가지 기본원칙(SRP, OCP, LSP, ISP, DIP)의 세번째 원칙인 LSP(Liskov Substitution Principle)에 대해 알아보겠습니다.

 

치환성(영어: substitutability)은 객체 지향 프로그래밍 원칙이다. 
컴퓨터 프로그램에서 자료형 {\displaystyle S}S가 자료형 {\displaystyle T}T의 하위형이라면 
필요한 프로그램의 속성(정확성, 수행하는 업무 등)의 변경 없이 
자료형 {\displaystyle T}T의 객체를 자료형 {\displaystyle S}S의 객체로 
교체(치환)할 수 있어야 한다는 원칙이다. 
리스코프 치환 원칙(영어: Liskov substitution principle, LSP)은 바바라 리스코프가 
자료 추상화와 계층 (Data abstraction and hierarchy)이라는 제목으로 
기조연설을 한 1987년 컨퍼런스에서 처음 소개한 내용으로, 
이 원칙을 엄밀한 용어로 말하자면 (강한) 행동적 하위형화라 부르는 하위형화 관계의 특정한 사례이다. 
이 정의는 1994년 논문에서 다음 원칙을 만들어낸 자료형의 의미론적 상호처리를 보장하기 때문에 
단순한 문법적 관계일 뿐만 아니라 의미론적 관계다.

<위키백과 LSP 정의>

 

너무 어렵게 느껴지는 문장이다. 아주 단순하게 풀어보면 LSP는 일반화 관계에 대한 이야기며 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행할 수 있어야 한다는 뜻이다.

 

https://viblo.asia/p/the-solid-principles-maGK7JVB5j2

 

위의 그림을 보면 부모 로봇인 Sam 은 커피를 만들수 있고 자식인 Eden이 있다.부모에게 커피를 만들어 달라고 요구하면 Sam은 커피를 만들어 주는 행위를 한다.Sam의 자식인 Eden에게도 똑같이 커피를 만들어 달라고 요구하면 Eden도 부모의 행위를 수행할 수 있어야 하기 때문에 자식인 Eden도 동일하게 커피를 만들어주는 행위를 할 수 있어야 한다.

 

LSP를 만족하려면 프로그램에서 부모 클래스의 인스턴스 대신에 자식 클래스의 인스턴스로 대체해도 프로그램의 의미는 변화되지 않는다. 이를 위해 부모 클래스와 자식 클래스 사이는 행위가 일관되어야 한다.

 

 

그럼 자식클래스가 부모 클래스 인스턴스의 행위를 일관성 있게 실행하려면 어떻게 해야 할까?

최소한 부모 클래스의 인스턴스가 실행하는 행위는 자식 클래스의 인스턴스들도 일관성 있게 실행할 수 있어야 하는데, 이를 위해서는 부모 클래스의 행위를 더 명확하게 정의할 수 있는 수단이 필요하다.

 

여기서는 어떤 클래스의 행위를 일종의 방정식 형태로 기술해 자식 클래스의 인스턴스가 이 방정식을 만족하는지 점검해본다. 만약 만족한다면 자식 클래스가 부모 클래스의 행위를 일관성 있게 실행한다고 말할 수 있다.

 

public Class Bag {
	private int price;
    
    public void setPrice(int price) {
    		this.price = price;
    }
    public void getPrice() {
    		return price;
    }
}

Bag 클래스는 가격을 설정(Setter메서드)하고 가격을 조회(Getter메서드)하는 기능이 있다. 따라서 Bag 클래스의 행위는 다음과 같이 표현 할 수 있다.

 

가격은 설정된 가격 그대로 조회된다.

 

이를 좀 더 형식적으로 작성하면 다음과 같습니다.

// 모든 Bag 객체 b와 모든 정수 값 p에 대해
[b.setPrice(p)].getPrice() == p;

 

여기에서 '객체.메서드(인자값).]'는 메서드가 실행된 후의 b객체를 나타낸다.

이런 경우 Bag 클래스의 행위를 손상하지 않고 일관성 있게 실행하는 클래스를 만드려면 어떻게 해야할까?

가장 직접적이고 직관적인 방법은 슈퍼클래스에서 상속받은 메서드들이 서브클래스에 오버라이드, 즉 재정의 되지 않도록 하면 된다.

 

Bag클래스를 상속받아 가방 가격을 할인받을 수 있게 하는 DiscountedBag 클래스를 아래와 같이 구현한다고 가정해보자

public class DiscountedBag extends Bag {
	private double discountedRate = 0;
    
    public void setDiscounted(double discountedRate) {
    	this.discountedRate = discountedRate;
    }
    
    public void applyDiscount(int price) {
    	super.setPrice(price - (int)(discountedRate * price));
    }
}

DiscountedBag 클래스는 할인율을 설정해서 할인된 가격을 계산하는 기능이 추가되었다.

기존의 Bag 클래스에 있던 가격을 설정하고 조회하는 기능은 변경없이 그대로 상속받아서 사용한다.

 

 

Bag 클래스로 작성된 코드와 DiscountedBag으로 작성된 클래스의 예제를 살펴보면

 

Bag 클래스 예제

Bag b1 = new Bag();
Bag b2 = new Bag();
b1.setPrice(30000);
System.out.println(b1.getPrice());
b2.setPrice(b1.getPrice());
System.out.println(b2.getPrice());

실행결과 : 

30000

30000

 

DiscoutnedBag 클래스 예제

DiscountedBag b3 = new DiscountedBag();
DiscountedBag b4 = neW DiscountedBag();
b.setPrice(30000);
System.out.println(b3.getPrice());
b4.setPrice(b3.getPrice());
System.out.println(b4.getPrice());

실행결과:

30000

30000

 

Bag 클래스의 setPrirce와 getPrice 메서드가 DiscountedBag 클래스에서 재정의 되지 않았으므로 Bag클래스의 예제와 DiscountedBag 클래스의 예제가 실행결과가 동일하다.

 

이는 DiscountedBag 클래스와 Bag 클래스의 상속관계가 LSP 를 위반하지 않았다는 것을 뜻합니다.

 

 

만약 DiscountedBag 클래스를 아래와 같이 구현한다면 어떻게 될까?

public class DiscountedBag extends Bag {
	private double discountedRate;
    
    public void setDiscounted(double discountedRate) {
    	this.discountedRate = discountedRate;
    }
    
    public void setPrice(int price) {
    	super.setPrice(price - (int)(discountedRate * price));
    }
}

DiscountedBag 클래스에서 setPrice를 재정의하여 가격을 세팅하도록 메소드를 바꾸었다

 

이렇게되면 위의 조건 사항이 만족 하지 못하게 된다.

// 모든 Bag 객체 b와 모든 정수 값 p에 대해
[b.setPrice(p)].getPrice() == p;

이유는 할인율이 0이 아닐 때는 setPrice 메서드를 실행한 후 DicountedBag 객체의 price 속성 값이 p에서 discounted * price를 차감한 결과가 되버리며 이는 p와 같지 않기 때문이다.

 

p - (int)(discountedRate * price) != p;

 

 

즉, Bag 클래스의 setPrice를 재정의한 DicountedBag 클래스의 구현은 Bag 클래스의 행위와 일관되지 않으므로 LSP를 만족하지 못하게 된다.

 

 

결론적으로 LSP를 만족시키는 간단한 방법은 재정의하지 않는 것이다.

 

 

한줄 요약

상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.

반응형
profile

개발은 재밌어야 한다

@ghyeong

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!