1. LSP(Liskov Substitution Principle)

자식 타입들은 부모 타입들이 사용되는 곳에 대체될 수 있어야 한다. 초창기 자바 개발자일때 이 규칙을 잘 모른 상태에서 상속과 폴리모피즘을 어설프게 적용한적이있다. 이 규칙을 확실히 이해했다면 개발할때 해매는것이 아니라 더 깔끔하게 개발했을 것이다. 지금까지 무수히 강조했던 상속과 폴리모피즘을 명확하게 적용하고 싶을때 곰곰히 곱씹어야할 중요한 원칙이다.

 

LSP와 연결되는 개념인 상속과 폴리모피즘의 전형적인 구조는 다음과 같다.

 

[상속과 폴리모피즘의 전형적인 구조 그림]

 

이런 구조는 클라이언트가 하위 클래스가 아닌 상위 클래스나 인터페이스에 의존하고 클라이언트를 실행하는 런쳐 클래스에서 클라이언트 객체를 생성하면서 하위클래스를 넘겨주는 구조로 폴리모피즘을 구현하게 되어 있다.

 

여기서 중요한 점은 클라이언트 객체는 상위 클래스에 의존하기 때문에 상위 클래스의 명세만 알수 있다는 것이다. 만약 하위 클래스에 상위 클래스에는 없는 기능이 추가되었다면 특별히 하위 클래스를 직접 의존하지 않는한 클라이언트는 하위 클래스의 새로운 기능을 알수가 없다.

 

[LSP 위반 사례]

 

위의 클래스 구성도를 보면 [하위 클래스]가 [상위 클래스]처럼 사용될때 오버라이드된 메소드가 다른 의미를 가져와서 LSP와 어긋나는 것으로 보인다

 

가상으로, 유닉스 OS를 참고로 맥OS를 개발한다고 구상했을 때 위와 같은 상속 구성으로 했을 때 문제가 없을지 살펴보자. 어느 OS나 커널이 있어야 하고, 응용 프로그램이 있어야 하나 파라미터가 다르다면, 맥OS가 유닉스OS를 상속받는 것은 쓸데 없는 메소드도 상속받는 것이다.

 

위의 경우 유닉스OS의 응용프로그램(GCC)와 파일시스템(EXT3)같이 전혀 안 쓰일 메소드도 상속받게 된다.

 

이와 같이 하면 전형적인 상속의 오용케이스이며 이렇게 사용할 바에는 아예 별도의 클래스로 분리하는게 날지도 모른다. 이런 경우에는 몇가지 연결 방법으로 대체하여 깔끔하게 재편할 수 있다.

 

위의 그림처럼 위임으로 맥OS가 윈도우OS 객체를 속성으로 갖고 있고, 커널() 등의 메소드에서 먼저 또는 나중에 윈도우 OS의 커널() 메소드를 호출하고 맥OS에 알맞은 로직을 따로 구현할 수 있다.

 

이제 궁금한 것은 상속, 위임, 구성, 집합중 위임을 어느때 사용할것이냐이다. 위임이나 구성이나 집합이나 어느때 써야 되는지 애매하고 햇갈린다. 위임은 다른 클래스의 기능을 사용해야 하지만 그 기능을 변경할 필요가 없을때 사용한다.

 

위임은 위임하고 있는 객체 기능의 로직이 변하지 않는다. 만약 위임하고 있는 클래스의 기능을 다른 기능으로 교체하고 싶을때는 구성을 쓴다.

 

위와 같이 구성을 활용하면, 맥 OS 구현시 참고하고 싶은 OS를 자유자재로 교체할 수 있다. 쓰고 싶은 기능의 로직을 자유롭게 교체할수 있기 때문에 유연하고 확장성이 높다.

 

위의 그림은 여지껏 무수히 많이 설명했던 전형적인 구성 그림이다. 클라이언트 객체는 인터페이스에 의존하였다가 쓰고 싶은 기능을 취사선택하여 얼마든지 변경하거나 확장하여 쓸수 있다. 특히 컴파일 단계가 아닌 실행 단계에 자유롭게 여러 구현 클래스중 하나를 선택하여 쓸수 있으므로 유연하고 강력하다. 구성을 통해 하나의 인터페이스를 구현한 여러 클래스들의 기능을 취사선택할수 있고 실행중에 그 클래스를 바꾸어 기능을 변경할 수 있다.

 

구성의 특징은 클라이언트가 구성으로 조합된 객체들을 소유하고 있다는 것이다. 클라이언트 객체가 없어지면 소유하고 있던 모든 기능들도 같이 없어진다. 구성에 참여한 객체들은 그 클라이언트 객체의 외부에는 존재하지 않는다.

 

여기서 구성과 집합의 차이점을 발견한다. 집합은 하나의 조합된 객체 패밀리들이 어느 클라이언트 객체의 부분으로 사용되지만 다른 외부 세계 클라이언트 객체도 사용할때 집합으로 맺어진다. 이 차이점만 명심하면 구성과 집합에 굳이 눈 부릎뜨고 따지지 않고 효과적으로 상속을 대체하면서 자식 타입들은 부모 타입들이 사용되는 곳에 대체할 수 있다.

 

자식 타입들은 부모 타입들이 사용되는 곳에 대체된다는 규칙을 지킬때 상속을 사용할 경우 상속의 효과를 확실히 누린다.

 

이 규칙을 만족하지 못한다면 상속을 대체할 더 강력한 위임이나 구성, 집합을 사용할 수 있다. 이 연결관계를 명확히 알고 자유자재로 사용할때 객체지향의 객체 설계의 묘미를 알게되며 디자인 패턴을 익히기전 튼튼한 기초를 다지게 될 것이다.

 

LSP 규칙 이해를 바탕으로 상속을 쓸것이냐, 위임, 구성, 집합을 쓸것이냐 결정할 수 있다.

 

덧 ) 이 객체지향의 탄생 원고는 제가 책으로 내려다가 일단 잘 안되었는데요. 이유는 비문이 많다. 단락내 주제가 중복된다. 어떤 상황 설명을 과장한다.등 입니다. 그래도 원고를 일단 블로그에 몽땅 풀어보고 언젠가 제대로 교정해서 다시 도전할 생각입니다. 비문이 많다. 단락내 주제가 중복된다. 어떤 상황 설명을 과장한다. 이점을 감안해서 읽고 객체지향을 이해하는데 도움이 되셨으면 좋겠습니다. 의견도 주셨으면 좋겠습니다. 원고 조금만 교정하면 괜찮을것 같은 출판사 관계자분의 피드백도 환영합니다. 특별한 일 없으면 매주 월수 발행 예정입니다.

신고
Posted by 산골
산골 블로그 소개 저는 하얀머리 개발자와 작가를 꿈꾸는 블로거 산골 입니다. 프로그램 개발자로서 저의 관심사는 개발자의 숨통을 트여준 아이폰 개발, 철학과 같은 깊이가 있는 객체지향 방법론입니다. 글쓰기와 수영을 좋아합니다. 블로그를 통해 관심사를 공유합니다. 제 블로그에 관심 있으시면 아래 RSS나 즐겨찾기로 편하게 구독하세요.

rss Bookmark and Share

댓글을 달아 주세요

  1. 뒤리머 2015.05.26 22:53 신고  댓글주소  수정/삭제  댓글쓰기

    한가지 질문 드려도 될까요? LSP를 위반한 자식클래스의 경우는 결국 직접 자식클래스를 쓰는 방법밖에 없는지요? 잘 아시겠지만 객체지향을 설명하는 수많은 책들을 보면 아마 상속이란 개념을 설명하기 위해서 그런거 같은데 보통 사람이란 클래스를 만들고 개발자, 디자이너식으로 자식클래스를 설명하면서 고유의 기능을 넣곤 합니다 개발자는 개발한다. 디자이너는 디자인한다 이런식으로여 마치 당연히 LSP를 위반해도 된다는 식으로 객체지향에 대해서 설명을 하곤 합니다. 사실 이러한 설명때문에 진즉부터 햇갈렸던 것 같습니다

    LSP를 위반하는 자식클래스를 마구마구 써도 되는지, 아니면 되도록이면 쓰지말아야 하는지, 어쩔 수 없이 써야 한다면 어떻게 어떤 상황에서 쓰면 좋은지 설명해주시면 고맙겠습니다.

    그리고 인터페이스와 추상클래스의 차이점을 기능에 대한 추상화, 객체에 대한 추상화로 구분을 하면 적절한지 그것도 알려주시면 고맙겠습니다

    블로그글 아주 잘 읽고 있습니다. 건강하고 행복하세요!!

    • BlogIcon 산골 2015.05.28 11:06 신고  댓글주소  수정/삭제

      안녕하세요~ 지송한데
      쉽게 댓글달기 어려워
      제가 주말에 댓글 달겠습니다~ ^..^;

    • BlogIcon 산골 2015.05.31 11:57 신고  댓글주소  수정/삭제

      안녕하세요~ 무료한 일상에 힘이 되는 소중한 댓글 감사드리며..

      "LSP를 위반한 자식클래스의 경우는 결국 직접 자식클래스를 쓰는 방법밖에 없는지요?"
      "LSP 위반의 경우 어쩔수 없이 써야 한다면 어떻게 어떤 상황에서 쓰면 좋은지 설명해주시면 고맙겠습니다."
      -> 위의 설명에 LSP를 위반한 자식클래스의 경우 '위임'의 방법으로 리팩토링하면 좋습니다.
      어쩔수 없이 써야 한다면 역시 '위임'의 방법으로 리팩토링하면 하면 좋습니다.

      "객체지향 책에서 당연히 LSP를 위반해도 된다는 식으로 설명한다."
      "LSP를 위반하는 자식클래스를 마구마구 써도 되는지, 아니면 되도록이면 쓰지 말아야 하는지"
      -> 되도록 객체지향 원칙을 준수하는 것이 맞고, 기본을 가르치는 책이라면 당연히 원칙적인
      설명을 해도 좋으나, 저는 실제 개발에서 꼭 LSP를 준수하는것을 따지고 이럴 필요까지는
      없다고 생각합니다. 객체지향이 결국 개발/유지보수하기 편하게 하기 위한 방법이라고 생각하는데
      무엇인가를 개발할때 '개발/유지보수가 용이하게 개발하냐' '객체지향 원칙을 지키는것이 중요한가'
      둘중에 어느게 우선순위 라고 생각하면 '전자' 라고 생각합니다.
      너무 결벽증처럼 객체지향 원칙을 지키는것이 중요하진 않고 개발/유지보수에 도움이 될만큼
      지키면 된다고 저는 생각하고 있습니다. <- 개인적인 생각
      실제 프로젝트에서 객체지향 원칙을 정확하게 준수하는 경우는 거의 없습니다.
      납기 준수에 매달리다 보면 객체지향 적으로 생각하고 구현할틈이 거의 없습니다. ㅠ

      "인터페이스와 추상클래스의 차이점"
      말씀하신 기능/객체에 대한 추상화의 개념적인 차이는 저도 확실히 모르겠네요~ @@
      추상클래스는 자식 클래스에서 반드시 상속해서 구현해야 하기 때문에
      그 기능들이 자식 클래스와의 연결정도가 밀접하게 연결된 경우이고
      인터페이스는 좀더 느슨하게 연결되도 되는 경우입니다.
      추상클래스가 엄마 자식처럼 강력한 연결관계라면
      인터페이스는 친구, 친척 같은?
      저는 이렇게 연결 관계의 정도로 구분하고 싶습니다.

      앞으로도 지속적으로 피드백 주시면 매우 감사하겠습니다. 감사합니다.~