Dog foot print

[Architecture] S.O.L.I.D [L] 본문

Architecture

[Architecture] S.O.L.I.D [L]

개 발자국 2021. 5. 6. 23:44

서론 

지난 포스팅에 이어 SOLID 원칙 중 [L]에 대해서 포스팅을 이어나가보도록 하겠다. 

 

LSP(Liskov substitution Principle) : 리스코프 치환 법칙

 

리스코프는 하위타입에 대해서 다음과 같이 정의하였다. "S타입의 객체 o1 각각에 대응하는 T타입 객체 o2가 있고, T타입을 이용해서 정의한 모든 프로그램 P에서 o2의 자리에 o1을 치환하더라도 P의 행위가 변하지 않는다면,S는 T의 하위 타입이다. " 리스코프의 말이 어렵게 느껴지는 것이 당연하다. 이를 조금 더 쉽게 풀어 설명하면, 다음과 같다. "부모 A로부터 상속받은 자식 B가 존재할 때 부모 A는 자식 B로 타입을 치환하여도 프로그램에서 행위는 변하지 않는다. "

 

어찌 보면 당연한 소리이다. 부모 A로부터 만들어진 자식 B는 상속을 받았기 때문에 거의 모든 것이 동일 할 것이다. 그럼 이를 위반하는 행위는 어떤 상황이 존재 할까 ? 바로 부모로부터 상속받은 메서드나 멤버 변수를 부모 클래스와 다르게 오버라이드 하는 과정에서 발생한다. 

 

다음의 예제를 보자 .

 

type paymentInfo = {
  productName: string;
  productId: string;
  price: number;
};

type creditCardInfo = {
    creditCardNumb : string;
    passwrod : string;
    signature : string;
  };

class POS {
  public payment(
    product: paymentInfo,
    creditCardNumb: string,
    password: string
  ) {
    //logic ~~~
    return "OK";
  }
}

class MobilePOS extends POS {
  public payment(product: paymentInfo, creditCardInfo : creditCardInfo) {
	return {result : "OK"}
  }
}

이 예제에서 MobilePOS가 부모인 POS 클래스를 상속 받고 있다. 이 과정에서, 두가지 문제점이 존재한다. 바로 오버라이드된 메서드 인 payment의 매개변수가 달라졌다는 점과, 함수의 return 값의 type이 다르다는 점이다. 그렇기 때문에, POS가 MobilePOS로 타입 치환을 하였을 때, POS인스턴스를 사용하는 로직들이 제대로 동작하지 않을 가능성이 매우 매우 높다. 그렇다면 위의 코드에서 LSP법칙을 지킬 수 있는 방법은 무엇일까 ? 

 

  1. 부모의 매개변수를 그대로 이용하며, 매개변수를 확장한다. 
  2. 매개변수의 속성을 부모의 메서드의 매개변수를 포함하는 상위 type이나, 유니언 타입을 사용하고 타입가드를 이용한다. 
  3. return 되는 값이 아예 다르거나 매개변수를 불가피하게 변경해야 한다면,오버라이드 하지 않고 하위 타입에서 다른 메서드 명을 사용한다.

 

이를 가장 간단한 방법인 3번 방법으로 고치면 다음처럼 변경 할 수 있다. 

 

type paymentInfo = {
  productName: string;
  productId: string;
  price: number;
};

type creditCardInfo = {
  creditCardNumb: string;
  passwrod: string;
};

class POS {
  public payment(
    product: paymentInfo,
    creditCardNumb: string,
    password: string
  ) {
    //logic ~~~
    return "OK";
  }
}

class MobilePOS extends POS {
  public payment(
    product: paymentInfo,
    creditCardNumb: string,
    password: string
  ) {
    //logic ~~~
    return "OK";
  }

  public paymentByCreditcardWithSignature(
    product: paymentInfo,
    creditCard: creditCardInfo
  ) {
    return "OK";
  }
}

 

이 LSP법칙에서 가장 유명한 문제는 정사각형의 직각사각형 상속 문제이다. 

 

정사각형 클래스는 직각사각형클래스를 상속 받고 있는데, LSP원칙에 의해서 setSideLength가 override되었지만 정사각형과 직각사각형의 1변의 길이의 증가에 대하여, 다른 변에 영향을 준다는 큰 차이로 인하여, 직각사각형이 정사각형 클래스로 타입 치환 되면, 오류에 노출된다. 이런 경우 어떻게 하면 좋을까 ? 바로 직각사각형과 정사각형이 동시에 상속가능한 인터페이스를 구현하여 이를 두 클래스 모두 상속 받으면 된다. 

 

 

 

반응형

'Architecture' 카테고리의 다른 글

[GOF] 추상 팩토리 메서드 패턴  (0) 2021.10.23
[GOF] 팩토리 메서드 패턴  (0) 2021.10.17
[Architecture] S.O.L.I.D [D]  (0) 2021.05.13
[Architecture] S.O.L.I.D [I]  (0) 2021.05.11
[Architecture] S.O.L.I.D [S,O]  (0) 2021.04.30
Comments