본문 바로가기

Design Pattern

[Java-Pattern] Bridge

 

이 패턴은 기능의 클래스 계층과 구현의 클래스 계층으로 나누어 설계하는 것이다.

기능의 클래스 계층이란

Something이란 클래스가 있을때 여기에 새로운 기능을 추가하기 위해서

Something의 하위 클래스로서 SomethigGood이라는 클래스를 만들어 기능을 확장할때

이것을 기능의 클래스 계층이라 한다.


구현의 클래스 계층이란

A라는 추상메서드를 상속하는 B클래스에서 그 추상메서드에서 선언된 메서드들을 실제로 구현하게 되는데 이를 구현의 클래스 계층이라고 한다.


예제프로그램

여기서는 주어진 문자열을 박스안에 넣어서 출력하는 클래스 하나와

주어진 숫자만큼 반복해서 출력하는 프로그램을 예제로 사용하였다.



기능의 클래스 계층 - Display(표시하는 클래스),CountDisplay(지정 횟수만큼 표시하는 클래스)

구현의 클래스 계층 - DisplayImpl(표시하는 클래스), StringDisplayImpl(문자열을 사용해서 표시하는 클래스)


public class Display {
    private DisplayImpl impl;
    public Display(DisplayImpl impl) {
        this.impl = impl;
    }
    public void open() {
        impl.rawOpen();
    }
    public void print() {
        impl.rawPrint();
    }
    public void close() {
        impl.rawClose();
    }
    public final void display() {
        open();
        print();                   
        close();
    }
}
- 무언가를 표시하기 위한 클래스로 생성자에게는 구현되는 클래스의 인스턴스를 넘겨주고

이를 필드에 저장하여 앞으로의 처리에 사용된다.

Display의 메서드들을 살펴보면 전부 DisplayImpl의 메서드로 변환되어 있다. display메서드는

open,print,close를 사용해여 '표시한다

라는 처리를 실현하고 있다.


기능의 클래스계층 : CountDisplay

public class CountDisplay extends Display {
    public CountDisplay(DisplayImpl impl) {
        super(impl);
    }
    public void multiDisplay(int times) {       // times번을 반복해서 표시한다.
        open();
        for (int i = 0; i < times; i++) {
            print();
        }
        close();
    }
}
Display를 상속하며, 표시한다라는 기능밖에 없던 Display의 기능을 지정횟수만큼 표시한다 라는 기능으로 확장 시킨 클래스이다. Display에서 상속받은 open,print,close를 사용하여 새로운 메서드를 구현하고 있다.



구현의 클래스 계층 - DisplayImpl


public abstract class DisplayImpl {
    public abstract void rawOpen();
    public abstract void rawPrint();
    public abstract void rawClose();
}

구현의 클래스 계층의 최상위 클래스로서 앞에 Display의 open,close,print에 대응하는

rawOpen,rawPrint,rawClose 메서드들을 가지고 있다.


구현의 클래스 계층 - StringDisplayImpl


public class StringDisplayImpl extends DisplayImpl {
    private String string;                              // 표시해야할 문자열
    private int width;                                  // 바이트 단위로 계산한 문자열의 "길이"
    public StringDisplayImpl(String string) {           // 생성자에서 넘어온 문자열 string를
        this.string = string;                               // 필드에 기억해 둔다.
        this.width = string.getBytes().length;              // 그리고 바이트 단위의 길이도 필드에 기억해두고 나중에 사용한다.
    }
    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++) {               // width개의 "-"를 표시해서
            System.out.print("-");                      // 틀의 선으로 이용한다.
        }
        System.out.println("+");                        // 틀의 모퉁이를 표현하는 "+"마크를 표시한다.
    }
}
DisplayImpl 클래스를 상속하며 상위 클래스에서 정의한 추상 메서드들을 실제적으로 구현하고 있다.


Main

public class Main {
    public static void main(String[] args) {
        Display d1 = new Display(new StringDisplayImpl("Hello, Korea."));
        Display d2 = new CountDisplay(new StringDisplayImpl("Hello, World."));
        CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe."));
  RandomCountDisplay d4 = new RandomCountDisplay(new StringDisplayImpl("hi,,"));
  d4.randomDisplay(10);
        d1.display();
        d2.display();
        d3.display();
        d3.multiDisplay(5);
    }
}

d3를 예제로 따라가보면..

CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe."));


 

우선 StringDisplayImple의 인스턴스를 생성하여 CountDisplay의 생성자 인자로 넘겨주고 있다.

CountDisplay의 생성자는

public CountDisplay(DisplayImpl impl) {
        super(impl);
    }

로 되어있고, super를 사용해 전달받은 인스턴스(DisplayImpl형으로 업캐스팅 된 StringDisplayImpl의 인스턴스)를 상위 클래스에게 넘겨주고 있다. 상위클래스는 Display는

public Display(DisplayImpl impl) {
        this.impl = impl;
    }

로 구성되어 있으며 (생성자가..) 필드에 impl를 보존해 두고 사용하게 된다.


즉 전달받은 impl은 Display와 CounstDisplay의 메서드를 모두 사용 할 수 있는 것이다.


d3.display();
d3.multiDisplay(5);


또한 d3는 CountDisplay형이기 때문에 상속받은 메서드인 display와 기능확장으로 구현한 multiDisplay메서드를 모두 사용 할 수 있다. 이 메서드들은 open,print,close 메서드들로 구성이 되고 이 메서드들은 impl.rawOpen,등의 메서드로 구성이 되는데


display 메서드를 살펴보면 open,print,close로 되어 있으며 이는

각각 rawOpen등을 호출하고 있다. 아까 보존한 impl(DisplayImpl형으로 업캐스팅 된 StringDisplayImpl의 인스턴스) 의 rawOpen등을 호출하게 되는데,


rawOpen은 DisplayImpl의 추상메서드이고 이는 StringDisplayImpl에서 구현하고 있으므로

업캐스팅 원리에 의해 StringDisplayImpl에서 구현한 rawOpen이 실행되는 것이다.


(만약 전달받은 인스턴스가 FileDisplayImpl의 인스턴스라한다면 당연히 이 클래스의 메서드가 실행되는 것..)


multiDisplay같은 경우 Display형인 d1,d2등은 호출을 할 수 없다.


왜냐하면 Display형에서 multiDisplay를 구현 혹은 제시하지 않고 있기 때문에 ...


알 수 없는 메서드라고 나오는 것이다.


PS- 여기서 랜덤 횟수만큼 표시하고 싶다. 이것은 기능에 해당한다. 어떻게 표시할 것인가 에 해당..따라서 이런 경우 CountDisplay를 상속하여 기능을 확장하면 되고.


만일 파일에서 읽어들여 표시하고 싶다 라고 한다면 이것은 구현에 해당되므로

DisplayImpl를 상속하는 확장 클래스를 하나 더 만들어 주면 될 것이다.