안녕하세요. 오늘은 Bridge 패턴에 대해 알아 보려고 합니다.
Bridge는 다리라는 의미를 가진 영어 단어 입니다.
실제로 다리가 하는 역할은 강 양쪽의 장소를 연결하는 역할을 한다고 합니다.
요번에 알아볼 Bridge 패턴 역시 '기능의 클래스 계층'과 '구현의 클래스 계층'이라는 두 장소를 연결하는 역할을 하는데요.
기능의 클래스 계층과 구현의 클래스 계층을 모른 상태에서 접하신다면 조금 어려우실 겁니다.
먼저 이 두 곳의 장소를 알아보고 갑시다.
계층은 크게 새로운 기능을 추가하고 싶은 경우, 새로운 구현을 추가하고 싶은 경우로 두가지 역할을 합니다.
새로운 기능을 추가하고 싶은 경우
어떤 클래스가 있다고 가정했을 때 그 클래스에 새로운 기능(새로운 메소드)을 추가하고 싶은 경우 그 클래스의 하위 클래스를 만들 수 있습니다.
- A Class
- A-1 Class
이런식으로 말이죠. 이랬을 때 A클래스는 기본적인 기능을 가지고 있는 상위 클래스가 되고, A-1 Class는 새로운 기능을 추가하는 하위 클래스가 됩니다.
이러한 클래스 계층을 기능의 클래스 계층이라고 부릅니다.
여기에서 A-1 Class에 또 다른 새로운 기능을 추가하려면, A-2 Class라는 하위 클래스를 만들 수 있습니다.
- A Class
- A-1 Class
- A-2 Class
- A-1 Class
이렇게 클래스 계층이 깊어지는 것을 볼 수 있죠.
이렇게 새로운 기능을 추가하고 싶을 때 클래스 계층 안에서 자신의 목적과 가까운 클래스를 찾아내 그 하위 클래스를 만들어서 원하는 기능을 추가한 새로운 클래스를 만들어 내는 것을 기능의 클래스 입니다.
새로운 구현을 추가하고 싶은 경우
두 번째 새로운 구현을 추가하고 싶은 경우입니다. 혹시 템플릿 메소드에서 추상 클래스의 역할에 대해 기억하고 계신지요? 추상 클래스가 일련의 메소드들을 추상 메소드로서 선언하고 인터페이스(ATI)를 규정했었죠?
그리고 하위 클래스 측에서 그 추상 메소드를 실제로 구현하고 상위 클래스는 추상 메소드로 인터페이스(API)를 규정하는 역할을 하고 하위 클래스는 그것을 구현하는 역할을 했었습니다.
이와 같은 상위 클래스와 하위 클래스의 역할 분담에 의해 부품으로의 가치가 높은 클래스를 만들 수 있었습니다.
마찬가지로 오늘 살펴 보고 있는 클래스의 계층에서도 이 부분이 등장합니다.
예를 들어 상위 클래스A Class의 추상메소드를 구현하는 하위클래스를 A-1 Class로 한다 했을 때, 아래와 같은 클래스 계층이 만들어지게 됩니다.
- A Class
- A-1 Class
여기에서 사용되는 클래스 계층은 추가하기 위한 것도, 새로운 메소드를 추가하기 위한 것도 아니지만 위의 상위 클래스인 A Class는 추상 메소드에 의해 인터페이스(API)를 규정하고, 하위 클래스 A-1 Class는 구상 메소드에 의해 그 인터페이스(API)를 구현합니다.
이런 클래스의 계층을 구현의 클래스 계층이라 부르는데요, A Class의 다른 구현을 만들려고 할 때, 하위 클래스를 A-2 Class라고 하면 구현의 클래스 계층은 다시 변하게 됩니다.
- A Class
- A-1 Class
- A-2 Class
- A-1 Class
새로운 구현을 만들기 위해서는 A class의 하위 클래스를 만들어 추상 메소드를 구현하게 되는데 이것이 구현의 클래스 계층입니다.
여기까지 기능의 클래스 계층과 구현의 클래스 계층에 대해 이해가 되셨는지요?
이제 부터 하위 클래스를 만들려고 생각할 때, 내가 기능을 추가하려고 하는지, 구현을 수행하려 하는지에 대한 의도를 확인할 필요가 생겼네요.
클래스 계층이 하나라면 기능의 클래스 계층과 구현의 클래스 계층이 하나의 계층구조 안에 혼재하게 됩니다.
이것은 클래스 계층을 복잡하게 해서 예측을 어렵게 할 우려가 있죠. 자신이 하위 클래스를 만들러고 할 때 클래스 계층의 어디에 만들면 좋을지 해매이기 일쑤죠...
그래서 기능의 클래스 계층과 구현의 클래스 계층을 두 개의 독립된 클래스 계층으로 분리합니다.
단순히 분리만 하면 흩어져버리기 때문에 두 개의 클래스 계층 사이에 다리를 놓는 일이 필요하죠.
이제 코드로 넘어가 볼까요?
package bami;
public class View {
private ViewImpl Vimpl;
public View(ViewImpl Vimpl) {
this.Vimpl = Vimpl;
}
public void Open() {
Vimpl.rawOpen();
}
public void Print() {
Vimpl.rawPrint();
}
public void Close() {
Vimpl.rawClose();
}
public final void View() {
Open();
Print();
Close();
}
}
View.class를 작성해보겠습니다. View 클래스는 표시해주는 추상 클래스입니다.
이 클래스는 기능의 클래스 계층의 최상위에 있는 클래스이고, Vimpl 필드는 View 클래스의 구현을 나타내는 인스턴스 입니다.
Viewimplement의 줄임말 입니다.
생성자에서는 구현을 나타내는 클래스이ㅡ 인스턴스를 전달하고, 인수로 전달된 인스턴스는 Vimpl 필드에 저장되어 이후에 처리할 때 사용 되는데 이 필드가 두 클래스 계층의 다리가 됩니다.
Open()은 표시의 전처리, Pirnt()는 표시 그 자체이고, Close()는 표시의 후 처리의 역할을 맡게 됩니다. 이 세 메소드의 내용을 보면, 메소드들을 실현하기 위해 Vimpl 필드의 구현 메소드를 이용하고 있습니다. 여기에서 View의 인터페이스(API)가 Vimpl인터페이스로 변환됩니다.
package bami;
public class CountView extends View{
public CountView(ViewImpl Vimpl) {
super(Vimpl);
}
public void MultiView (int times) {
Open();
for (int i = 0; i < times; i++) {
Print();
}
Close();
}
}
View 클래스에 기능을 추가한 CountVIew 클래스를 작성하였습니다.
View 클래스에는 표시하는 기능만 있어 지정횟수만큼 표시해주는 기능을 추가해보았습니다.
그 부분이 MultiView 메소드에서 동작하고 있는 모습을 보여주죠.
package bami;
public abstract class ViewImpl {
public abstract void rawOpen();
public abstract void rawPrint();
public abstract void rawClose();
}
여기서부터 구현의 클래스 계층에 속합니다. ViewImpl 클래스는 구현의 클래스 계층의 최상위에 위치합니다.
ViewImpl 클래스는 추상 클래스이고, rawOpen(), rawPrint(), rawClose()라는 세가지 메소드를 가지고 있고,
각각 View 클래스의 Open(), Print(), Close()에 대응하며 전처리, 표시, 후처리를 실행하죠.
package bami;
public class StringViewImpl extends ViewImpl {
private String string;
private int width;
public StringViewImpl(String string) {
this.string = string;
this.width = width;
}
public void rawOpen() {
printLine();
}
public void rawPrint() {
System.out.println("|" + string + "|");
}
public void rawClose() {
printLine();
}
private void printLine() {
System.out.print("*");
for (int i = 0; i < width; i++) {
System.out.print("=");
}
System.out.println("*");
}
}
이제 구현 단계입니다. StringVIewImpl 클래스는 문자열을 표시하는 클래스입니다.
ViewImpl 클래스의 하위 클래스이고, rawOpen(), rawPrint(), rawClose() 메소드를 사용해서 표시를 실행합니다.
ViewImpl과 StringViewImpl 두 클래스가 구현의 클래스 계층에 해당되죠.
package bami;
public class Main {
public static void main(String[] args) {
View v1 = new View(new StringViewImpl ("Hello, Bami"));
View v2 = new CountView(new StringViewImpl ("Hello, everyBody!"));
CountView v3 = new CountView(new StringViewImpl("Hello, worid!"));
v1.View();
v2.View();
v3.View();
v3.MultiView(5);
}
}
Main 클래스에서는 앞에서 작성했었던 4개의 클래스를 조합하여 문자열을 표시합니다.
변수 v1에는 View 클래스의 인스턴스를 대입하고, 변수 v2,v3에서는 CountView 클래스의 인스턴스를 대입합니다.
둘 다 StringViewIpml 클래스의 인스턴스가 구현을 담당하죠.
'프로그래밍(Basic) > 디자인 패턴(Java)' 카테고리의 다른 글
[바미] Composite 패턴에 대해 알아봅시다. (0) | 2022.01.31 |
---|---|
[바미] Strategy 패턴에 대해 알아봅시다. (0) | 2022.01.25 |
[바미] Builder 패턴에 대해 알아봅시다. (0) | 2021.10.25 |
[바미] Prototype 패턴에 대해 알아봅시다. (0) | 2021.10.04 |
[바미] Singleton 패턴에 대해 알아봅시다. (0) | 2021.09.23 |