티스토리 뷰

디자인패턴

Bridge 패턴

심재철 2018. 2. 8. 16:56


Bridge의 의미는 다리이다. 

기능 클래스 계층과 구현 클래스 계층을 서로 연결하는 패턴이다.


클래스 계층의 두가지 역할

 


1. 기능 클래스 계층


상위 클래스는 기본적인 기능을 갖고 있다.

하위 클래스는 기본적인 기능에 새로운 기능이 추가 된다.


A라는 추상 클래스를 상속받는 B라는 클래스 존재.


2. 구현 클래스 계층

최상위 클래스가 인터페이스이고 이 인터페이스는 자식 클래스들에서 구체화될 메소드들이 정의되어 있다.

자식 클래스에서 부모 인터페이스를 구현하여 사용한다.


A라는 인터페이스를 구현한 B라는 객체가 있을때 이것을 구현의 클래스 계층이라고 한다.


클래스 계층의 분리

 


만약에 기능 클래스 계층과 구현 클래스 계층이 분리되어 있지 않다면?


그렇다면 하나의 계층 구조 안에 구현 클래스와 기능 클래스가 혼재 하게 되면서 클래스 계층 구조를 파악하기 매우 힘들어짐.


예를 들어 A라는 추상 클래스가 있다. 추상 클래스는 하나 이상의 추상 메소드를 갖는다.


이상황에서 이 추상 클래스를 상속받는 B라는 자식 클래스를 만들었다. 

또한 이 추상 클래스를 상속받는 C라는 자식 클래스를 만들었다.


하지만 B에서는 A의 추상메소드를 단순히 구현만 하고 있고,

C에서는 A의 추상메소드를 구현할뿐만 아니라 새로운 메소드를 정의하고 있다고 해보자.


이런 경우에 만약에 클래스 계층구조가 깊어지게 될 경우 A의 API를 구현한 클래스가 어떤것이고, 기능을 확장한(메소드를 추가한) 클래스가 어떤것인지 파악하기가 힘들어 지게 된다.


A(추상클래스)

B(구현)

E(기능)

C(기능)

F(구현)

D(구현)

G(구현)


기능 클래스와 구현 클래스가 혼재 되어 있는 클래스 계층의 예. 한눈에 봐도 클래스 구조가 복잡하다.


B-> A의 추상 메소드를 모두 구현

C -> A의 추상 메소드 모두 구현 + 새로운 추상 메소드 정의

D -> A의 추상 메소드 모두 구현


E-> B의 추상 메소드 구현 + 새로운 메소드 정의

F-> C의 새로운 추상 메소드를 모두 구현


이런 이유가 있기 때문에 구현 클래스 계층과 기능 클래스 계층을 분리해야 한다.


어떤 기능의 구현과 기능의 사용을 분리했다고 생각하면 될듯함.


예제

 


ITV

LGTV

SamsungTV


-> 구현의 클래스 계층


RemoteControl

LGRemoteControl


-> 기능의 클래스 계층


리모컨은 티비의 전원 on/off기능 또는 채널 바꾸기 기능을 단순히 가져와서 사용만 할 뿐이다. 리모컨 내부에 티비를 끄고 켜거나 채널을 바꾸는 기능 자체가 포함된게 아니다. 단순히 티비의 기능을 가져와서 사용.


다시 말해서 ITV라는 티비 인터페이스를 리모컨 추상클래스에 넣어놓고 그 티비 인터페이스의 API를 리모컨 추상 클래스에서 가져다 쓰기만 하는것.


다시말해서 기능의 사용 부분과 구현 부분을 분리해놓고 ITV라는 브릿지를 통해서 두개의 클래스 계층을 연결해놓은것.

(이것을 느슨한 클래스 결합이라고 한다. -> 상속은 강한 클래스간 결합)


구현 클래스 계층도


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
interface TVI{ //구현의 클래스 계층의 최상위 인터페이스
    public void TVon();
    public void TVoff();
    public void changeChannel(String channel);
}
 
class SamsungTV implements TVI{ //구현 클래스 계층
    public void TVon() {
        System.out.println("SamsungTV on");
    }
    public void TVoff() {
        System.out.println("SamsungTV off");
    }
    public void changeChannel(String channel) {
        System.out.println("SamsungTV Channel : "+channel);
    }
}
 
class LGTV implements TVI{
 
    public void TVon() {
        System.out.println("LGTV on");
    }
    public void TVoff() {
        System.out.println("LGTV off");
    }
    public void changeChannel(String channel) {
        System.out.println("LGTV Channel : "+channel);
    } //구현 클래스 계층
}
cs



기능 클래스 계층도

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

abstract class RemoteControl { //기능 계층 클래스의 최상위 추상클래스
    protected TVI tv; //기능 클래스 계층의 최상위 추상 클래스에서 구현 클래스 계층의 API를 사용하기 위해 Bridge 연결
    public RemoteControl(TVI tv) {
        this.tv = tv;
    }
    /*추상 클래스는 인스턴스를 생성할수 없음에도 불구하고 생성자가 존재하는 이유는
     * 추상 클래스를 상속받는 자식 클래스는 부모 추상 클래스의 모든 추상 메소드를 구현하면, 인스턴스를 생성할수 있기 때문이다.
     * 자식 클래스에서는 자신의 생성자에서 부모 클래스의 생성자를 호출한다.
     * 추상 클래스는 자식 클래스들의 공통적인 부분을 뽑아내서 만든것이다.(일반화시킨것) 
    */
    abstract public void on();
    abstract public void off(); 
    
    /*기능 클래스 계층 에서 구현 클래스 계층의 API를 가져다 사용함.
     * 즉, 티비를 켠다는 기능과 실제 그 티비 켜는 기능의 구현을 분리한것임.
     * 사용자는 그 티비 켜는 기능이 실제로 어떤 로직에 의해서 실행되는지 몰라도 되고 알 수도 없음. 단순히 브릿지에 연결된 인터페이스의 API만 호출하면 됨.
     * 즉 구현과 기능을 분리한 패턴임.
     * 좀 쉽게 말하자면 기능의 사용과 기능의 구현을 분리했다고 생각해야 기억에 잘 남을듯함.
     * */
}
 
class LGRemoteControl extends RemoteControl{ 
    public LGRemoteControl(TVI tv) {
        super(tv);
    }
    public void on() {
        System.out.println("LG 리모컨 사용"); 
        tv.TVon(); //기능 클래스 계층에서 구현 클래스 계층에 구현된 API들을 가져다 사용함.
    }
    public void off() {
        System.out.println("LG 리모컨 사용");
        tv.TVoff();
    }

    public void changechannel(String channel) {
        System.out.println("LG 리모컨으로 채널 변경 ");
        tv.changeChannel(channel);
    }
    //엘지 리모컨에만 새롭게 추가된 기능.
}

cs





public class BridgePattern {
 
    public static void main(String[] args) {
        LGTV LGtv = new LGTV();
        LGRemoteControl LGRemotecontrol = new LGRemoteControl(LGtv); 
//기능 클래스 계층의 최상위 클래스에 구현 클래스 계층의 클래스를 등록함.
        LGRemotecontrol.on();
        LGRemotecontrol.changechannel("101번");
        LGRemotecontrol.off();
        
    }
 
}
 

   cs

결과


LG 리모컨 사용

LGTV on

LG 리모컨으로 채널 변경 

LGTV Channel : 101번

LG 리모컨 사용

LGTV off


브릿지 패턴의 장점

 


분리해 두면 확장이 편해진다.

-> 어떤 프로그램의 기능을 추가하고 싶으면 기능 클래스 계층만 확장시키면 되고,

-> 어떤 프로그램의 구현을 추가하고 싶다(어떤 새로운 버전용 API가 필요하다)면 구현 클래스 계층만 확장시키면 됨.


예를들면, 어떤 프로그램이 있는데 리눅스버전,윈도우 버전, mac os버전이 필요하다고 해보자. 이럴때 구현 클래스 계층에서 API를 정해놓고 기능 클래스 계층에서는 이 api를 가져다가 쓰기만 한다. 현재 리눅스,윈도우 버전 API는 존재하지만 이번에 새로 MAC OS 버전의 API가 새롭게 추가 되어야 한다고 했을때 기능 클래스 계층은 전혀 수정하지 않아도 그냥 MAC OS버전의 API 구현만 새롭게 추가 하게 되면 원래 작동하던대로 잘 작동하게 된다. 만약 이런식으로 분리 해놓지 않았다면 하나의 클래스 계층내에서 여러 파일을 수정해야 되었을 것이다.

또한 기능 클래스 계층에 새롭게 어떤 기능이 추가된 클래스가 나타나더라도 구현 클래스의 api를 단순히 가져다 쓰면 되므로 별로 어렵지 않게 확장이 가능하다.



상속은 견고한 연결이고 위임은 느슨한 연결이다 라고 표현한다.

위임이라는것은 기능 클래스 계층의 private 멤버 변수로 ITV가 있었다. 이 필드가 두 클래스 계층간 브릿지 역할을 하게 되는 것인데(api를 가져다 쓸수있게끔 해줌) 

기능 클래스 계층인 엘지 리모컨 클래스에서 tvon 메소드를 호출하게 되면 사실 구현 클래스 계층의 api인 on메소드를 호출하게 되는것이다. 이런식으로 작업을 자신이 직접 처리하지않고 다른 메소드에게 위임한다고 표현한다.




댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함