Bridge 패턴
브리지의 목적은 구현 상세 일부를 추상화 뒤로 캡슐화해 물리적 의존성을 최소화 하는 것이다.
의도: ' 추상화를 그 구현과 분리해 그 둘이 독립적으로 달라질 수 있게 한다.'
브리지 디자인 패턴은 장반대인 것을 다룬다 즉, 함께 작동해야 하지만 서로에 대해 너무 많은 상세 내용을 알아서는 안되는 두 기능이 적당한 거리를 유지하며 물리적 의존성을 줄일 수 있게 지원하며 분리를 돕는다.
예시로 자동차를 구현해야한다고 하자. 자동차는 필수로 engine이 필요하기 때문에, 역시 engine 클래스도 같이 들어간다.
#inlcude <gasolineEngine.h>
class SedanCar
{
public:
SedanCar(/* ... */)
void drive();
// ...
private:
GasolineEngine mEngine;
//Wheel, Chair ..
};
SedanCar 클래스는 GasolineEngine 사용하므로, 해당 엔진을 private 변수로 가지고 있다.
mEngine 데이터 멤버 때문에 헤더 파일 gasolineEngine 헤더를 포함하게 되면서 물리적 결합이 발생한다. 즉 SedanCar.h 헤더를 포함하는 모든 파일은 <gasolineEngine.h> 헤더를 물리적으로 의존하기 때문에 이 헤더에서 무언가가 바뀔 때 마다 SedanCar 클래스와 잠재적으로 더 많은 클래스가 영향을 받는다. 게다가 이 디자인은 모든 구현 상세를 모두에게 드러내고 있다.
이런 의존성을 제거하고 언제든 아무도 모르게 구현 상세를 쉽게 변경할 수 있는 호사를 누리려면 추상화를 도입해야 한다. 고전적인 추상화 형식은 추상 클래스를 도입하는 것이다.
구현하려는 모든 차에 대해 엔진 상세 내용을 동일한 방식으로 분리, 즉 동일한 브리지를 도입하는 데 관심이 있다. 중복을 줄이고 DRY 원칙을 따르기 위해 브리지 관련 구현 상세를 Car 기초 클래스로 아래 그림과 같이 추출할 수 있다.
실제 Interface 역할을 하는 Car, Engine 클래스 코드를 보자.
Car 기초 클래스는 브리지를 연관된 Engine에 캡슐화한다.
/** Car.h **/
class Car
{
protected:
explicit Car(std::unique_ptr<Engine> engine)
: pimpl_(std::move(engine))
{}
public:
virtual ~Car() = default;
virtual void drive() = 0;
protected:
Engine* getEngine() { return pimpl_.get(); }
Engine const* getEngine() const { return pimpl_.get(); }
private:
std::unique_ptr<Engine> pimpl_;
};
/** Engine.h **/
class Engine
{
public:
virtual ~Engine() = default;
virtual int32_t start() const = 0;
virtual int32_t stop() const = 0;
};
Car 클래스의 추가로 '추상화'와 '구현' 모두 쉽게 확장할 수 있으며 독립적으로 달라질 수 있다.
이 브리지 관계에서 Engine기초 클래스는 여전히 '구현'을 나타내지만, Car 클래스는 이제 '추상화' 역할을 한다.
생성자는 Engine에 대한 std::unique_ptr을 취하고 이를 pimpl_ 데이터 멤버로 이동한다. 이 포인터 데이터 멤버는 모든 Car에 대한 구현을 가리키는 포인터이며. 흔히 pimple 이라고 한다. 이 불투명 포인터는 캡훌화한 구현 상세에 대한 브리지를 나타내며 본질 적으로 브리지 디자인 패턴 전체를 나타낸다.
SedanCar 클래스 상속시 '생성자 내부에서 GasolineEngine을 생성' 한다.
브리지 패턴은 전략 디자인 패턴과 상당히 비슷한데 차이는 다음과 같다.
Car는 어떤 구현 상세도 모르며 외부에서 요청받은 Engine을 생성한다. -> 전략 패턴
Car는 상속받은 클래스에서 필요한 구현 상세에 대해 알지만, 너무 강하게 의존하지는 않는다. -> 디자인 패턴
아래 코드는 Car, Engine을 상속받아 '구현' 한 코드이다.
/** Gasoline.h */
class GasolineEngine : public Engine
{
public:
explicit GasolineEngine() {}
int32_t start() const override {
printf(" gasoline start \n");
return 0;
}
int32_t stop() const override {
printf(" gasoline stop \n");
return 0;
}
};
/* SedanCar */
class SedanCar : public Car
{
public:
explicit SedanCar() : Car(std::make_unique<GasolineEngine>())
{}
void drive() {
printf(" running sedan car \n");
getEngine()->start();
getEngine()->stop();
printf(" stop sedan car \n");
}
};
아래 main 코드를 보면 알 수 있듯이, SedanCar를 생성하지만 내부 Engine을 어떻게 쓸지는 관여하지 않는다.
int main()
{
Car* myCar = new SedanCar;
myCar->drive();
return 0;
}