디자인 패턴(구)/구조 패턴

브릿지(Bridge) 패턴이란?

공대키메라 2022. 4. 7. 14:47

1. 브릿지 패턴이란?

의도

구현에서 추상을 분리하여, 이들이 독립적으로 다양성을 가질 수 있도록 한다. 

즉, 추상적인 것과 구체적인 것을 분리하여 연결하는 패턴이다.

사용 시기

  • 추상적 개념과 이에 대한 구현 사이의 지속적인 종속 관계를 피하고 싶을 때
  • 추상적 개념과 구현 모두가 독립적으로 서브클래싱을 통해 확장되어야 할 때
  • 추상적 개념에 대한 구현 내용을 변경하는 것이 다른 관련 프로그램에 아무런 영향을 주지 않아야 할 때
  • 클래스 계통에서 클래스 수가 급증하는것을 방지하고자 할 때

구조

  • Abstraction : 추상적 개념에 대한 인터페이스를 제공하고, 객체 구현자에 대한 참조자를 관리
  • Refind Abstraction : 추상적 개념에 정의된 인터페이스를 확장
  • Implementor : 구현 클래스에 대한 인터페이스를 제공
  • ConcreteImplementor : Implementor 인터페이스를 구현. 실제적은 구현 내용을 담음

장점

  • 추상적인 코드를 구체적인 코드 변경 없이도 독립적으로 확장할 수 있다.
  • 추상적인 코드와 구체적인 코드를 분리하여 사용 가능

단점

  • 계층 구조가 늘어나 복잡도가 증가할 수 있다.

2. 코드 구현

이번에도 이해를 위해 상황을 가정하겠다.

 

또한, Before 와 After의 비교를 통해 장단점을 되새길 것이다. 

 

필자는 설명을 위해 차를 예로 들 것이다. 

 

자동차의 역할에 대해 생각을 해보면, 사실 어떤 차던 다르지가 않다.

 

모든 차는 공통적으로 앞, 뒤, 좌우로 이동한다고 여기서는 생각하겠다.

 

 

현재 적용 전 코드를 이렇게 구성했다.

 

차 모델은 First, Second가 있다고 하자. 모든 모델은 색상이 여러개 붙을 수 있는데, 같은 모델이지만 기호에 맞게 색이 다를 수 있다. 여기서는 Black, White만 있다고 하자. 

 

Before

Car.java

public interface Car {
    void forward();
    void back();
    void left();
    void right();
}

FirstBlackCar.java

public class FirstBlackCar implements Car{

    @Override
    public void forward() {
        System.out.println("First - Black Car Move Forward");
    }

    @Override
    public void back() {
        System.out.println("First - Black Car Move Back");
    }

    @Override
    public void left() {
        System.out.println("First - Black Car Move Left");
    }

    @Override
    public void right() {
        System.out.println("First - Black Car Move Right");
    }
}

FirstWhiteCar.java

public class FirstWhiteCar implements Car{

    @Override
    public void forward() {
        System.out.println("First - White Car Move Forward");
    }

    @Override
    public void back() {
        System.out.println("First - White Car Move Back");
    }

    @Override
    public void left() {
        System.out.println("First - White Car Move Left");
    }

    @Override
    public void right() {
        System.out.println("First - White Car Move Right");
    }
}

SecondBlackCar.java

public class SecondBlackCar implements Car{

    @Override
    public void forward() {
        System.out.println("Second - Black Car Move Forward");
    }

    @Override
    public void back() {
        System.out.println("Second - Black Car Move Back");
    }

    @Override
    public void left() {
        System.out.println("Second - Black Car Move Left");
    }

    @Override
    public void right() {
        System.out.println("Second - Black Car Move Right");
    }
}

 무언가 반복되는 코드가 눈에 들어온다. 

 

하나의 계층구조로 다양한 작업들을 수행하려 하다 보니까 구조도 커지고, 하위클래스를 일일이 만들어주는 과정이 무언가 중복되는 과정이 많다. 

 

이것을 브릿지 패턴을 이용해서 한변 바꿔보겠다.

After

Color.java

public interface Color {
    String getColor();
}

DefaultCar.java

public class DefaultCar implements Car {

    private Color color;

    private String modelName;

    public DefaultCar(Color color, String modelName) {
        this.color = color;
        this.modelName = modelName;
    }

    @Override
    public void forward() {
        System.out.printf("%s - %s Move Forward\n", modelName, color.getColor());
    }

    @Override
    public void back() {
        System.out.printf("%s - %s Move Back\n", modelName, color.getColor());
    }

    @Override
    public void left() {
        System.out.printf("%s - %s Move Left\n", modelName, color.getColor());
    }

    @Override
    public void right() {
        System.out.printf("%s - %s Move Right\n", modelName, color.getColor());
    }
}

FirstCar.java

public class FirstCar extends DefaultCar{
    public FirstCar(Color color) {
        super(color, "First");
    }
}

SecondCar.java

package me.whiteship.designpatterns._02_structural_patterns._07_bridge._my_code._after;

public class SecondCar extends DefaultCar{
    public SecondCar(Color color) {
        super(color, "Second");
    }
}

WhiteColor.java

public class WhiteColor implements Color{

    @Override
    public String getColor() {
        return "White";
    }

}

BlackColor.java

//다른 계층구조에 영향을 주지 않고 현재 Color만 변경하면 된다.
public class BlackColor implements Color{

    @Override
    public String getColor() {
        return "Black";
    }

}

DriveCar.java

public class DriveCar {

    public static void main(String[] args) {
        Car firstWhiteCar = new FirstCar(new WhiteColor());
        firstWhiteCar.back();
        firstWhiteCar.forward();

        Car firstBlackCar = new FirstCar(new BlackColor());
        firstBlackCar.back();
        firstBlackCar.forward();
        
        /*
        결과
        
        First - White Move Back
        First - White Move Forward
        First - Black Move Back
        First - Black Move Forward
        */
    }

}

우리는 Color관련 계층 구조, 모델 관련 계층 구조를 분리했다. 

 

구조를 다시 보자. 

  • Abstraction : 추상적 개념에 대한 인터페이스를 제공하고, 객체 구현자에 대한 참조자를 관리
  • Refind Abstraction : 추상적 개념에 정의된 인터페이스를 확장
  • Implementor : 구현 클래스에 대한 인터페이스를 제공
  • ConcreteImplementor : Implementor 인터페이스를 구현. 실제적은 구현 내용을 담음

Implementation에 해당하는 클래스는 Car이다. 현재 Car는 구현 클래스에 대한 인터페이스를 제공한다. 

 

이를 DefaultCar를 통해서 Car를 구현하였다. 이것은 Abstraction에 해당되는걸로 보인다. 

 

이것을 다시 재정의하는 Refined Abstraction이 존재하는데 Color들이 이에 해당할 것이다. 

 

계층이 하나 있는데 다른 계층이 있고, 이것이 서로 서로 상속받고, 다른 곳과 연결되있어서 다리(Bridge)를 연상한 것 같다. 

 

Car의 추상, 구현 & Color의 추상, 구현

 

Car의 구현체 이던지, Color의 구현체 이던지 어느것도 조합이 가능하다. 

 

우리가 구현한 코드에서 이를 확인 가능한데, First 이지만 Black을 원하면 Black을, White를 원하면 White color를 선택할 수 있다. 

 

client는 단지 이것을 적절히 선택하면 된다. 

 

각 계층으로 나뉜 구현체는 서로서로 Cartesian Product인 것이다!

 

이렇게 하니 원래의 코드는 수정없이 확장될 수 있으므로 OCP를 잘 지킨것이다. 

 

또한 이 브릿지 패턴의 의도를 다시 이해할 수 있었다. 

 

구현에서 추상을 분리하여, 이들이 독립적으로 다양성을 가질 수 있도록 한다...

 

계층에 따른 추상을 분리해서 독립적으로 다양한 차 모델과, 색깔을 부여할 수 있었다.

 

즉, 추상적인 것과 구체적인 것을 분리하여 연결하는 패턴이다.

 

이에 대해 이해하기 위해 필자는 다음 영상도 참고했다. 

 

참고 : https://www.youtube.com/watch?v=F1YQ7YRjttI 

 


이렇게 Bridge pattern을 알아봤다. 

 

이상하거나 잘못된 부분은 언제든지 피드백을 주면 감사하겠다.