Bridge Pattern - 브릿지 패턴
Developer/Design Pattern

Bridge Pattern - 브릿지 패턴

반응형

브릿지 패턴?

구현부에서 추상층을 분리하여 각자 독립적으로 변형할 수 있게 하는 패턴이다

 

또 무슨 말인지 어렵네요

 

우선 UML 클래스와 함께 살펴볼게요~!

Bridge UML Diagram

  • Abstraction : Implementor인스턴스 변수로 합성된 상태이고, function이 추상화된 상태
  • Implementor : 추상화된 인터페이스
  • ConcreteImplementor : 인터페이스(Implementor)를 구체화한 객체 = 상속 받아서 구현
  • RefinedAbstraction : 추상화 클래스(Abstraction)를 구체화한 객체

한마디로, Abstraction 클래스는 Implementor에 의존되어 있는 상태고,

의존되어 있는 상태를 추상화하여 실제 구현은 자식 클래스(RefinedAbstraction)에 맡기는 전체 구조입니다

 

아직도 어렵죠?

 

예시와 함께 살펴보도록 할게요!

 

Bridge 패턴의 예시

우리는 그림을 그리는 인터페이스(DrawAPI -> Implementor)를 만들었어요

빨간색원초록색원을 그리는 각자의 객체(ConcreteImplementor)를 만들고,

해당 도형에 대한 그림을 그리고 싶은데 각자 그리는 방법이 다르겠죠?

 

빨간색원은 빨간색으로 해야되고, 초록색원은 초록색으로 해야되니까요!!

 

그럼 우선은, 원(Circle)말고도 나중에 사각형(Rectangle)을 그리라고 할 수 있으니 

이를 전체를 아우루는 도형(Shape -> Abstraction)을 만들까 해요!

 

그 후에 이를 상속받은 원(Circle -> RefinedAbstraction)을 만드는 거죠!

그림을 그리기 위해서는 길이(x,y)와 각도(radius)정보를 알아야 하니

이를 draw()  메서드 인자로 넘기게 되면, 우리가 원하는 그림을 그릴 수 있게 되는 겁니다!

 

Why 브릿지 패턴?

DrawAPI를 Shape에 합성시킴으로써 조금 더 유연한 설계가 만들어지기 때문이에요

도형이라는 객체와, 그림을 그리는 객체 분리함으로써 도메인을 나눴는데요

 

그렇게 되면 Domain 로직에 도형이라는 객체는 그림을 그리는 것과 상관없이 로직을 짤 수 있고,

그림을 그리는 객체는 그리는 것만 집중할 수 있게 되죠

 

한마디로, SRP(Single Resposibility Principle)을 지킨 객체지향적인 코드가 될 것 같아요

도형이라는 객체(Shape)는 도형을 나타내는 책임만 가지게 되고,

그림을 그리는 객체(DrawAPI)는 도형을 그리는 책임만 가지게 되는 것이죠!

 

제가 다른 예시로 한번 설명해볼게요~!

 

예제 코드

저는 게임을 좋아하는 게이머에요

그래서 게임을 접속하고 싶은데, 하는 게임이 너무 많아서 걱정이네요

내가 하는 모든 게임마다 동일한 메서드로 기능을 동작하게 할 수 있을까요?

package designpattern.bridge;

interface PlayGameAPI {
    String playGame(final String id, final String password);
}

 1.Implementor 인 PlayGameAPI 구현 

주어진 id와 password만 있다면 쉽게 게임에 접속할 수 있겠죠?

 

다음은 제가 하는 게임들이에요!

package designpattern.bridge;

class LOL implements PlayGameAPI {
    @Override
    public String playGame(final String id, final String password) {
        return "LOL Connect complete! [id=" + id + " password=" + password + "]";
    }
}
package designpattern.bridge;

class Overwatch implements PlayGameAPI {
    @Override
    public String playGame(final String id, final String password) {
        return "Overwatch Connect complete! [id=" + id + " password=" + password + "]";
    }
}

 2.ConcreteImplementor 인 LOL, Overwatch 구현 

저는 주로 LOL, Overwatch를 주로 하기 때문에 각자 객체를 만들어 봤어요~!

 

다음은 게임을 하는 Player를 추상화 해볼까요~?

package designpattern.bridge;

abstract class GamePlayer {
    protected PlayGameAPI playGameAPI;

    public GamePlayer(final PlayGameAPI playGameAPI) {
        this.playGameAPI = playGameAPI;
    }

    public abstract String play();
}

 3. Abstraction 인 GamePlayer구현 

API를 합성하여 나타낸 객체고, 실제 게임을 play하는 기능은 추상화해서 자식에게 맡겼어요!

GamePlayer는 어떤 게임을 선택할 것인지에 대한 책임만 가지게 되는거죠!

 

마지막으로 실제 게임을 하기 위한 객체를 만들어볼게요!

package designpattern.bridge;

class OnlineGamePlayer extends GamePlayer {
    private final String id;
    private final String password;

    public OnlineGamePlayer(final PlayGameAPI playGameAPI, final String id, final String password) {
        super(playGameAPI);
        this.id = id;
        this.password = password;
    }

    @Override
    public String play() {
        return playGameAPI.playGame(id, password);
    }
}

 4. RefinedAbstraction 인 OnlineGamePlayer구현 

주어진 id와 password만 있으면 게임에 접속할 수 있으니

API를 통해서 게임에 접속하는 객체에요~!

이 OnlineGamePlayer는 게임에 접속하는 책임만 가지게 되는 것이죠!

 

한번 제대로 되는지 테스트해볼까요?

package designpattern.bridge;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class OnlineGamePlayerTest {
    private GamePlayer gamePlayer;

    @Test
    void LOL이_올바르게_실행되는지_테스트() {
        /* Given */
        final PlayGameAPI LOL = new LOL();
        final String id = "Ashe";
        final String password = "ad";
        /* When */
        gamePlayer = new OnlineGamePlayer(LOL, id, password);
        /* Then */
        assertThat(gamePlayer.play()).isEqualTo("LOL Connect complete! [id=Ashe password=ad]");
    }

    @Test
    void Overwatch가_올바르게_실행되는지_테스트() {
        /* Given */
        final PlayGameAPI LOL = new Overwatch();
        final String id = "Ashe";
        final String password = "bob!!";
        /* When */
        gamePlayer = new OnlineGamePlayer(LOL, id, password);
        /* Then */
        assertThat(gamePlayer.play()).isEqualTo("Overwatch Connect complete! [id=Ashe password=bob!!]");
    }
}

테스트가 너무나도 잘 통과 되네요 ㅎㅎ

제가 작성한 코드의 전체 Diagram입니다!

예시 Diagram

브릿지 패턴의 장점!

이제 마지막으로 장점을 정리하면서 게시글을 마무리할까 해요!

  1. 브릿지 패턴은 구현에서 추상화를 분리하여 두 가지 객체가 독립적으로 되게 한다!
  2. 브릿지 패턴은 런타임 의존성컴파일 의존성이 다르므로 확장에 유연하다!
  3. 브릿지 패턴은 직교적인 설계다!

참고

Bridge Pattern - tutorialspoint
Geeks for geeks - Bridge Pattern

 

반응형