1. Bridge
- 비즈니스 로직이나 대규모 클래스를 독립적으로 개발할 수 있는 별도의 클래스 계층으로 나누는 것
- 구현에서 추상화를 분리하여 각자 독립적으로 변형이 가능하고 확장이 가능하도록 함
1.1. 의도
- 브릿지는 대규모 클래스 또는 밀접하게 관련된 클래스들의 집합을 두 개의 개별 계층구조(추상화 및 구현)로 나눈 후 각각 독립적으로 개발할 수 있도록 하는 구조적 디자인 패턴
1.2. 문제
- 원과 직사각형이 한 쌍의 자식 클래스들이 있는 모양 클래스가 있다고 가정
- 이 클래스 계층 구조를 확장하여 색상을 도입하기 위해 빨간색, 파란색 모양의 자식클래스들을 만들 계획이면, 이미 2개의 자식클래스가 있으므로 아래와 같이 4개의 클래스 조합을 만들어야 함
- 새로운 모양이나 색상이 추가할 때마다 기하급수적으로 늘게 됨
- 따라서 코드는 점점 복잡해짐
1.3. 해결책
- 모양과 색상의 2가지 독립적인 부분에서 모양 클래스들을 확장하려고 하기 때문에 발생
- 클래스 상속과 관련된 General한 문제임
- 브릿지 패턴은 상속에서 객체 composition으로 바꿔서 이를 해결
- 이는 독립적인 부분들 중 하나를 별도의 클래스 계층구조로 추출하여 각 클래스들이 한 클래스 내에서 모든 상태와 행동들을 갖는 대신 새계층 구조의 객체를 참조하도록 함
- 위의 예시로 보면 모양과 색상 관련해서 각각의 클래스로 출출하여 모양 클래스가 색상 객체들 중 하나를 가리키는 필드로 받게 함
- 이제 모양클래스는 연결된 색상 관련 객체에 모든 색상 관련 작업을 위임할 수 있음
- 이 참조는 모양과 색상 클래스들 사이에서 브릿지 역할을 함
1.4. 추상화와 구현
- 추상화 (인터페이스)는 일부 개체에 대한 상위 레이어로서 자체적으로 작업을 수행해서는 안되며,
- 작업들은 구현 레이어(플랫폼)에 위임해야 됨
- 이는 프로그래밍 언어의 인터페이스나 추상 클래스를 의미하는 것이 아님
- 예시
- 앱에서 추상화는 그래픽 사용자 인터페이스이며 구현은 그래픽 사용자 인터페이스 레이어가 사용자와 상호작용하여 결과로 호출하는 배경(API)임
- 이러한 앱은 두 가지 독립적인 방향으로 확장이 가능
- 다른 여러가지의 그래픽 사용자 인터페이스를 가짐(예를 들어 일반 고객, 관리자용 인터페이스 등)
- 여러 다른 API를 지원(Mac, Windows, Linux 등)
- 크로스 플랫폼 앱을 구성하는 방법 중 하나
- 추상화 : 앱의 그래픽 사용자 인터페이스
- 구현 : 운영 체제의 API
- 추상화 객체는 앱의 드러나는 모습을 제어하고 연결된 구현 객체에 실제 작업들을 위임
- 서로 다른 구현들은 공통 인터페이스를 따르는 한 상호 호환이 가능
- 이에 따라 같은 그래픽 사용자 인터페이스는 리눅스와 윈도우에 동시에 동작이 가능
2. 구조
- Abstraction
- 상위 수준의 제어를 제공하며 Implementation 객체에서 실제 작업을 수행
- Implementation
- 모든 Concrete Implementation 객체들의 공통적인 인터페이스를 선언하며 추상화는 여기에 선언된 메소드들을 통해서만 소통이 가능
- Concrete Implementation
- Refined Abstraction
- Abstraction에서 인터페이스를 확장하거나 변형이 가능
- Client
- 추상화 객체를 구현 객체들 중 하나와 연결하는 역할을 해야 됨
3. 사용
- 어떤 기능의 여러 변형을 가진 monolithic 클래스로 나누려고 할 때 사용(예를 들어 클래스가 다양한 데이터베이스 서버들과 동작하는 경우)
- 문제
- monolithic 클래스가 커질수록 동작 방식을 파악하기 어려워지고 변경하는데 오랜 시간이 걸림
- 해결
- 따라서 브릿지 패턴을 사용하여 monolithic 클래스를 여러 클래스 계층구조로 나눠서 각각의 클래스들은 독립적으로 변경할 수 있음
- 코드의 유지관리를 단순화 및 기존 코드 손상을 최소화
- 여러 독립 차원에서 클래스를 확장해야 할 때 사용
- 각 차원에 대해 별도의 클래스 계층구조를 추출하는 것이 좋음
- 원래 클래스는 모든 작업을 자체적으로 수행하는 대신 추출된 계층구조들에 속한 객체들에 관련 작업들을 위임
- 런타임에 구현을 전환할 수 있어야 할 때에 사용
- 추상화 내부의 구현 객체를 바꿀 수 있으며, 그렇게 하기 위해 필드에 새로운 값을 할당하기만 하면 됨
4. Pros and Cons
4.1. Pros
- 플랫폼 독립적인 앱을 만들 수 있음
- 클라이언트 코드는 추상화와 함께 동작하며 플랫폼의 세부 정보에 노출되지 않음
- Open/Closed Principle, 새로운 추상화와 구현을 서로 독립적으로 도입 가능
- Single Responsibility Principle, 추상화 레벨의 상위 수준의 로직과 구현레벨의 플랫폼 세부 정보에 집중이 가능
4.2. Cons
- 응집력 있는 클래스에 패턴을 적용하여 코드를 더 복잡하게 만들 수 있음
2. 코드로 알아보기
#include <iostream>
#include <string>
// 구현의 인터페이스
class Implementation {
public:
virtual ~Implementation() {}
// 순수가상함수로 서브클래스에 구현
virtual std::string OperationImplementation() const = 0;
};
// 서로 다른 플랫폼 A
class ConcreteImplementationA : public Implementation {
public:
std::string OperationImplementation() const override {
return "ConcreteImplementationA: Here's the result on the platform A.\n";
}
};
// 플랫폼 B
class ConcreteImplementationB : public Implementation {
public:
std::string OperationImplementation() const override {
return "ConcreteImplementationB: Here's the result on the platform B.\n";
}
};
// 제어를 위한 추상화 인터페이스
class Abstraction {
protected:
// implementation을 필드로 가짐
Implementation* implementation_;
public:
// 생성자에서 Implementation을 가지고 객체 생성
Abstraction(Implementation* implementation) : implementation_(implementation) {
}
virtual ~Abstraction() {}
virtual std::string Operation() const {
// 동작에서 implementation 의 함수를 사용
return "Abstraction: Base operation with:\n" +
this->implementation_->OperationImplementation();
}
};
// Implementation을 그대로 사용하며 추상화 인터페이스를 확장하는 것도 가능
class ExtendedAbstraction : public Abstraction {
public:
ExtendedAbstraction(Implementation* implementation) : Abstraction(implementation) {
}
std::string Operation() const override {
return "ExtendedAbstraction: Extended operation with:\n" +
this->implementation_->OperationImplementation();
}
};
// 클라이언트는 추상화 객체만으로 핸들링
void ClientCode(const Abstraction& abstraction) {
// ...
std::cout << abstraction.Operation();
// ...
}
int main() {
// 플랫폼 A
Implementation* implementation = new ConcreteImplementationA;
Abstraction* abstraction = new Abstraction(implementation);
ClientCode(*abstraction);
std::cout << std::endl;
delete implementation;
delete abstraction;
// 플랫폼 B
implementation = new ConcreteImplementationB;
abstraction = new ExtendedAbstraction(implementation);
ClientCode(*abstraction);
delete implementation;
delete abstraction;
return 0;
}
참고
wikipedia
refactoring.guru
This is personal diary for study documents.
Please comment if I'm wrong or missing something else 😄.
Top
댓글남기기