본문 바로가기
개발용어

SOLID 원칙 - 리스코프 치환 원칙, LSP (Liskov Substitution Principle)

by devscb 2022. 5. 9.
반응형

SOLID 원칙 - 리스코프 치환 원칙, LSP (Liskov Substitution Principle)


리스코프 치환 원칙이란? (리스코프 치환 원칙, LSP ,Liskov Substitution Principle)


리스 코프 치환 원칙은 "특정 메소드가 상위타입을 인자로 사용할때, 그 타입의 하위 타입을 대입했을때도 정상동작해야한다"라는 내용입니다.

리스코프 치환 원리는 바바라 리스코프가 1987년 컨퍼런스 기조연설 《데이터 추상화》에서 처음 소개한 내용입니다.
리스코프 치환 원칙의 수학적 정의는 다음과 같습니다.
"Φ(x)가 T형의 객체 x에 대해 증명 가능한 성질이라고 하자.
그러면 S가 T형의 하위 유형인 S형의 객체 y에 대해서는 Φ(y)가 참이어야 한다."

예를 들어서 설명을 해볼까요?
"Φ(x)가"
--> 먼저, Φ(x)라는 뜻은 x라는 변수를 인수로 무엇인가를 실행한다는/연산한다는 함수입니다.
Φ를 걷는다는 행동이라고 하고, x를 홍길동이라고 해보겠습니다.
그러면 Φ(x)는 걷는다(홍길동) 이 되겠지요.

"T형의 객체 x"
--> T형은 일종의 추상화된 개념이라고 생각할 수 있고, 객체 지향 프로그래밍에서는 class를 의미합니다.
객체 x는 구체적인 객체라고 말할 수 있습니다.
앞의 예제에서 x를 홍길동이라고 했고, T형이라는 것은 추상화된 개념, 예를 들어 사람이라할 수 있습니다.
그러면 "T형의 객체 x"는 예를 든다면 "사람중에서 홍길동"이라고 표현할 수 있습니다.

"증명 가능한 성질이라고 하자. "
--> 정상적으로 동작한다 라는 말과 동일하다고 생각하면 됩니다.


"S가 T형의 하위 유형인"
--> S라는 것은 T보다 하위의 추상화된 개념/클래스라고 볼 수 있습니다.
예를 든다면 사람보다 하위의 클래스를 생각한다면 남자사람, 여자사람이 있겠습니다.

"S형의 객체 y"
--> T형의 객체 x와 비슷합니다.
예를 들어 S를 여자사람이라고 하고, 객체 y는 여자사람중 영심이 라고 해보겠습니다.


이를 토대로 예시문장을 바꿔볼까요?
"Φ(x)가 T형의 객체 x에 대해 증명 가능한 성질이라고 하자.
그러면 S가 T형의 하위 유형인 S형의 객체 y에 대해서는 Φ(y)가 참이어야 한다."

"걷는다(사람)은 이상없이 잘 동작합니다. (아무 사람이나 가능합니다)
그러면 사람의 하위 개념인 여자사람과 남자사람에 대해서도
걷는다(여자사람)이나 걷는다(남자사람)은 정상 동작합니다."



LSP를 준수해야하는 이유


1. 메소드 사용시 코드가 정상동작하지 않아, 메소드 사용에 혼란이 생길 수 있다.
2. 버전 업그레이드 등을 통해 라이브러리가 교체되었을경우, 정상동작하지 않을 수 있다.
(추가적인 코드수정이 필요하다)


LSP 코드의 예 -- 메소드 사용에 혼란


메소드 사용시 코드가 정상동작하지 않아, 메소드 사용에 혼란이 생길 수 있다.
의 경우부터 살펴보겠습니다.




class ListHelper{
 private static currentList = new ArrayList();
 public static List addInfo(List list){
 String info = "new";
 currentList.add(info);
 return currentList;
 }
}


String<> vals = new String<>{"item1", "item2", "item3"};
List list = Arrays.asList(vals);
arr = ListHelper.addInfo(list);



위 코드를 실행시키면 아래와 같은 에러가 발생합니다.
Exception in thread main java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:150)



여기서 발생한 예외는 Arrays.asList(infoValues)가 반환한 List 구현체가 List 인터페이스의 add()
메쏘드를 지원하지 않아서 발생합니다.
(Arrays.asList 는 ArrayList 클래스를 반환하며, 내부적으로 array로 관리하고있기에 사이즈를 변경할 수 없습니다.)

List 인터페이스에는 add()를 제공하도록 되어있는데,
ArrayList는 add()를 지원하지 않아서 생기는 에러입니다.

개발자 입장에서 List라면 add()를 사용할 수 잇을 줄알았는데, 사용할 수 없기에 메소드 사용에 혼란이 있을 수가 있는것입니다.


LSP 코드의 예 -- 버전업그레이드



아래와 같이 Library클래스를 잘 사용하고 있는 class가 있습니다.


class Library{
 Map<string, stirng=""> map = new HashMap<string, string="">()
 public void addPortConfig(String value){
 map.add("port", value);
 }
 public void addConfig(String key, String value){
 map.add(key, value);
 }
 ...
}

public static void main(){
 Library lib = new Library();
 lib.addPortConfig("port", "8080");
}

</string,></string,>



잘 사용하고 있다가, 새로운 기능이 필요해서 Library_ver2를 사용하도록 해보겠습니다.
Library_ver2의 내부코드는 아래와 같다고 가정해보겠습니다.
addPortConfig는 너무 장황하여서 addConfig만 남겨본다고 가정하고,
나머지 메소드들은 그대로 유지해보려고 합니다.


class Library_ver2 extends Library{
 Map<string, stirng=""> map = new HashMap<string, string="">()
 public void addPortConfig(String value){
 throw Exception("Unsupported");
 }
 public void addConfig(String key, String value){
 map.add(key, value);
 }
 ...
}
</string,></string,>




ver2로 사용하고, 이전에 main에 선언하였던 아래 코드중 addConfig는 에러가 발생하겠지요.



public static void main(){
 Library lib = new Library_ver2();
 lib.addPortConfig("8080"); //error 발생
}



버전업그레이드를 원한다면

lib.addPortConfig("8080"); 코드를
lib.addConfig("port", "8080"); 처럼 바꾸어야 하는 상황이 오게 됩니다.
규모가 큰 프로젝트라면 수정해야하는 코드가 어마무시하겠지요.


LSP를 가장 잘 지키는 방법은 상속시 메소드 오버라이드를 하지 않고,
새로운 기능이 필요하다면 메소드/속성을 새로 추가해주면 잘 지켜질 수 있습니다.
하지만 경우에 따라선 이를 못지키는 경우도 존재합니다.


LSP를 지키지 않았으나, 잘 설계된 예시


위와 같은 이유에도 불구하고 LSP는 항상지켜야만 하는 원칙은 아닙니다.
가장 대표적인 사례로 아래와 같은 Java의 unmodifiableCollection이 있습니다.


Collection list = new LinkedList();
list = Collections.unmodifiableCollection(list);
list.add("dd"); // exception 발생 (UnSupportedOperationException();)



불변 컬렉션 객체를 만들기 위해 decorator패턴으로 구현하였는데,
만일 decorator패턴으로 구현하지 않았다면 아래와 같이 class를 더 만들어야 합니다.

UnmodifiableCollection
UnmodifiableList
UnmodifiableLinkedList
...

즉, 이렇게 수 많은 클래스를 추가적으로 작성하지 않기 위해 Java Collection은 LSP를 위반하여 만들게 되었다고 볼 수 있습니다.
이처럼 LSP는 피치못하게 지켜질 수 없기도 합니다.



총평


LSP는 잘 이해하기 어려운 개념인거 같고, 적용하기에도 쉽지 않은것 같습니다.
기본적으로 클래스 설계를 잘 할 수 있는 능력을 키운다면 자연스럽게 LSP를 지켜질 수 있을것입니다.
(상속관계, is - a 관계) 이 클래스가 저 클래스의 하위 클래스로 정말로 적합한가?를 설계시 잘 고려하면 대부분은 문제가 없을것입니다.
그럼에도 불구하고 UnmodifiableCollection 사례에서처럼 피치못하게 지키지 못하는 경우도 있기에,
항상 지킬수만은 없고, 다른 SOLID처럼 많은 개발 경험이 있어야 적절하게 잘 구사할 수 있을거 같습니다.

#SOLID,#SOLID원칙,#리스코프,#치환,#원칙,#SOLIDprinciple,#LSP,#리스코프치환원칙,#LISKOV,#substitution,#principle

 

https://devscb.com/post/96

 

SOLID Principle - Liskov Substitution Principle, LSP (Liskov Substitution Principle)

SOLID Principle - Liskov Substitution Principle (LSP) What is Liskov’s substitution principle? (Liskov Substitution Principle, LSP, Liskov Substitution Principle)Liskov’s substitution principle states

devscb.com

 

728x90
반응형

댓글