[참고] JAVA 언어로 배우는 디자인 패턴 입문
안녕하세요. 물류 플랫폼 트레드링스 개발팀 양갱 입니다.
시작하기전에 복습! 디자인 패턴은 왜 사용한다고 했죠?
맞습니다. 소스의 반복적인 코드는 줄이고 보다 효율적으로 관리하기 위해서 사용합니다.
이번에 같이 공부할 디자인 패턴은 바로 Bridge 패턴입니다.
Bridge하니깐 죽기전에 꼭가보고 싶은 런던 브릿지가 떠오르네요.
Bridge(=다리)의 역할은 뭔가요?
바로, 서로 떨어져 있는 두장소를 이어주는 역할을 합니다.
다리가 강 양쪽의 장소를 연결하는 역할을 하듯이 Bridge 패턴도 두장소를 연결하는 역할을 합니다.
이것이 Bridge 패턴의 핵심입니다!!!
Bridge 패턴이 다리 역할을 하고 있는 두 곳은
기능 클래스 계층 ||=========|| 구현클래스 계층
입니다.
기능 클래스 계층과 구현 클래스 계층 사이에 다리를 놓는다? 이게 다 뭔솔?
Bridge 패턴을 들어가기 전에 그럼 먼저,
... 기능 클래스 계층
... 구현 클래스 계층
에 대해서 자세히 알아보도록 하겠습니다.
강의 양쪽 장소에 대해서 이해지 못하면 다리의 의미도 이해할 수 없으니까요. GO!GO!씽!
말을 조금 바꿔서 다음 두가지 경우로 나눠서 생각해보겠습니다.
Case 1. 새로운 기능을 추가하고 싶은 경우
Case 2. 새로운 구현을 추가하고 싶은 경우
먼저, 어떤 새로운 클래스 Something이란 놈이 있다고 가정해 봅시다.
여러분이 Something에 새로운 기능을 추가하고 싶을 때 (= 새로운 메소드를 추가하고 싶을때) 어떻게 하실껀가요?
우리들은 Something을 상속한 하위 클래스(=자식 클래스)로 SomethingGood 클래스를 만듭니다.
이과정에서 작은 클래스 계층이 생성되었습니다.
이것은 기능을 추가하기 위해 만들어진 계층입니다.
이렇게 되면, 상위 클래스는 기본적인 기능을 가지고 있고 하위 클래스에는 새로운 기능을 추가할 수 있게됩니다.
이 클래스 계층을 '기능 클래스 계층' 이라고 합니다.
다음, 우리는 지난시간에 Abstract! 즉, 추상 클래스에 대해서 배웠었습니다.
(내머리 속에 지우개가 있다.. 그것도 무지큰..)
복습해보면, 추상 클래스는 메소드들을 추상 메소드로서 선언하고 인터페이스를 정합니다.
그리고 하위 클래스에서는 그 추상 메소드들을 실제로 구현하는 역할을 했었습니다.
(기억안나는 분들 Abstract Factory 패턴참고!)
이와 같은 상위 클래스와 하위 클래스의 역할 분담에 의해 부품으로서의 가치(교환가능성)가 높은 클래스를 만들 수 있습니다.
여기서 두번째 경우가 등장했습니다.
그러나 여기서 사용되는 클래스 계층은 기능을 추가하기 위한 것도 새로운 메소드를 추가하기 위한 것도 아닙니다. 단지,
... 상위 클래스는 추상 메소드에 의해 인터페이스(API)를 규정
... 하위 클래스는 구체적인 메소드에 의해 그 인터페이스를 구현
이 클래스 계층을 '구현 클래스 계층' 이라고 합니다.
새로운 구현을 만들기 위해서는 AbstractClass 의 하위 클래스를 만들어 추상 메소드를 구현하면 됩니다.
기능 클래스 계층과 구현 클래스 계층에 대해서 이해가 되셨나요?
이제 우리는 하위 클래스를 만들려고 할때, 의도를 확인해봐야 합니다.
'나는 기능을 추가하려고 하는가? or 구현을 수행하겨고 하는가?'
생각을 해봅시다. 만약, 클래스 계층이 하나만 있다면,
기능의 클래스 계층과 구현의 클래스 계층이 하나의 계층구조 안에 뒤죽박죽 되버립니다.
따라서! '기능의 클래스 계층'과 '구현의 클래스 계층'을 두 개의 독립된 클래스 계층으로 분리합니다.
단순히 분리만 하면 흩어져 버리기 때문에 두개의 클래스 계층 사이에 다리를 놓는 일이 필요합니다.
바로, 런던 브릿지를 놓는 거죠!!!ㅎㅎㅎ
자, 그럼 런던 브릿지를 만들러 떠나볼까요!
Bridge 패턴에서 다룰 예제는 '무언가를 표시하기' 라는 예제입니다.
전체적인 구조를 먼저 살펴 보겠습니다.
기능과 계층으로 분리해 놓은 것을 눈여겨 보세요.
Display 클래스는 추상적인 '무언가를 표시하는 것' 입니다. 이 클래스는 기능 클래스 계층의 최상위에 있는 클래스입니다.
impl 필드에 주목하세요! impl 필드는 Display 클래스의 '구현'을 나타내는 인스턴스 입니다.
현재 있는 곳이 어디죠?
네 바로 기능 클래스 최상위 입니다.
Q. 여기에 구현을 나타내는 인스턴스가 왜있지?
이런 의문을 가지신 분! 엄지척척!
바로 이 impl 필드가 두 클래스 계층 (강의 양쪽) 을 이어주는 런던 브릿지가 되는 것입니다.
Display에 '지정횟수만큼 표시한다' 라는 기능을 추가한 것입니다.
여기까지 기능 클래스 계층이였습니다.
DisplayImpl 클래스는 '구현 클래스 계층'의 최상위에 위치합니다.
DjsplayImpl 클래스는 추상 클래스이며 rawOpen, rawPrint, rawClose는 Display의 open, print, close에 각가 대응하며 전처리, 표시, 후처리를 실행합니다.
드디어 그체적인 '구현' 단계에 왔습니다. StringDisplayImpl 클래스는 문자열을 표시하는 클래스입니다. 단지 표시만 하는 것이 아니라 DisplayImpl 클래스의 하위 클래스로서 rawOpen, rawPrint, rawClose 의 구체적인 부분을 구혀하고 있습니다.
자 이제 런던 브릿지를 건너가 볼까요!
Main 클래스에서는 앞에서 설명한 네 개의 클래스를 조합해서 문자열을 표시합니다.
d1, d2, d3 모두 Display 클래스의 인스턴스의 일종이기 때문에 display 메소드를 호출 할 수 있고
d3은 multiDisplay 메소드도 호출할 수 있습니다. StringDisplayImpl 클래스의 인스턴스가 구현을 담당하고 있습니다.
실행결과:
Bridge 패턴의 핵심은 ' 기능 클래스 계층'과 '구현 클래스 계층'을 분리하는 것입니다.
이 두 개의 클래스 계층을 분리해 두면 각각의 클래스 계층을 독립적으로 확장할 수 있습니다!
기능을 추가하고 싶으면 기능의 클래스 계층에 클래스를 추가합니다. 이때 구현의 클래스 계층은 전혀 수정할 필요가 없습니다. 새로 추가한 기능은 모든 구현에서 이용할 수 있습니다.
한가지더 말씀드리자면, 상속은 견고한 연결이고 위임은 느슨한 연결입니다.
즉, '상속'은 클래스를 확장하기 위해 편리한 방법이지만 클래스간의 연결을 강하게 고정시킵니다.
반대로 프로그램의 필요에 따라서 클래스 간의 관계를 척척 바꾸고 싶을 때는 '위임' 을 사용합니다.
예제 프로그램에서는 Main 클래스 내에서 Display나 CountDisplay의 인스턴스를 만들고 그때 StringDisplayImpl의 인스턴스를 인수에게 전달했습니다.
만약, StringDisplayImpl 클래스 말고 다른 ...DisplayImpl 클래스를 만들었다고 가정하고 그 인스턴스를 Dsiplay나 CountDisplay에게 전달하면 그것으로 구현이 확실히 교체됩니다.
여러분, 런던브릿지가 떠올려 지시나요?
기능 클래스 계층과 구현 클래스 계층을 나눈 이유와 그것을 연결하는 방법에 초점을 맞춰 보시기 바랍니다.
모두 실제로 런던 브릿지에 가면 Bridge 패턴을 떠올리실 겁니다^^