Bridge

chris·2021년 6월 20일
0

design pattern

목록 보기
6/11

Intent

Bridge는 큰 클래스 또는 밀접하게 관련된 클래스 집합을 서로 독립적으로 개발할 수 있는 추상화 및 구현이라는 두개의 개별 계층으로 분할 할 수 있는 구조적 디자인 패턴입니다.

Problem

한 쌍의 subclasses인 CircleSquare가있는 Shape 클래스가 있다고 가정 해 보겠습니다. 이 클래스 계층 구조를 확장하여 색상을 통합하려고 하므로 빨강 및 파랑 모양 subclasses를 만들 계획입니다. 그러나 이미 2개의 subclasses가 있으므로 BlueCircleRedSquare와 같은 4개의 클래스 조합을 만들어야합니다.

새로운 모양 유형과 색상을 계층 구조에 추가하면 기하급수적으로 늘어납니다. 예를 들어 삼각형 모양을 추가하려면 각 색상에 하나씩 두 개의 하위 subclasses를 도입해야합니다. 그 후 새 색상을 추가하려면 각 모양 유형에 대해 하나씩 세 개의 subclasses를 만들어야 합니다. 더 멀리 갈수록 더 나빠집니다.

Solution

이 문제는 모양 클래스를 양식 및 색상의 두 가지 독립된 차원으로 확장하려고 하기 때문에 발생합니다. 이것은 클래스 상속과 관련된 매우 일반적인 문제입니다.

Bridge 패턴은 상속에서 개체 구성으로 전환하여 이 문제를 해결하려고 합니다. 이것이 의미하는 바는 차원 중 하나를 별도의 클래스 계층으로 추출하여 원래 클래스가 하나의 클래스 내에 모든 상태 및 동작을 갖는 대신 새 계층의 객체를 참조하도록 한다는 것입니다.

이 접근 방식에 따라 색상 관련 코드를 RedBlue의 두 가지 subclass가 있는 자체 클래스로 추출 할 수 있습니다. 그런 다음 Shape 클래스는 색상 객체 중 하나를 가리키는 참조 필드를 가져옵니다. 이제 모양은 색상 관련 작업을 연결된 색상 객체에 위임 할 수 있습니다. 이 참조는 Shape 클래스와 Color 클래스 사이의 다리 역할을합니다. 이제부터는 새 색상을 추가 할 때 모양 계층을 변경할 필요가 없으며 그 반대의 경우도 마찬가지입니다.

Abstraction and Implementation

GoF 책은 Bridge 정의의 일부로 Abstraction and Implementation이라는 용어를 소개합니다. 제 생각에는 용어가 너무 학문적으로 들리며 패턴이 실제보다 더 복잡해 보입니다. 모양과 색상이있는 간단한 예를 읽은 후 GoF 책의 무서운 단어 뒤에 숨겨진 의미를 해독 해 봅시다.

추상화 (인터페이스라고도 함)는 일부 엔터티에 대한 높은 수준의 제어 계층입니다. 이 레이어는 자체적으로 실제 작업을 수행해서는 안됩니다. 작업을 구현 계층 (플랫폼이라고도 함)에 위임해야합니다.

프로그래밍 언어의 인터페이스 또는 추상 클래스에 대해 이야기하는 것이 아닙니다. 이것들은 같은 것이 아닙니다.
실제 애플리케이션에 대해 이야기 할 때 추상화는 그래픽 사용자 인터페이스 (GUI)로 나타낼 수 있으며 구현은 GUI 계층이 사용자 상호 작용에 대한 응답으로 호출하는 기본 운영 체제 코드 (API) 일 수 있습니다.

일반적으로 이러한 앱을 두 개의 독립적인 방향으로 확장 할 수 있습니다.

  • 여러 가지 GUI가 있습니다 (예 : 일반 고객 또는 관리자에 맞게 조정 됨).
  • 여러 가지 API를 지원합니다 (예 : Windows, Linux 및 macOS에서 앱을 시작할 수 있음).

최악의 시나리오에서 이 앱은 수백 개의 조건문이 코드 전체에서 다양한 API와 다양한 유형의 GUI를 연결하는 거대한 스파게티 그릇처럼 보일 수 있습니다.

특정 인터페이스-플랫폼 조합과 관련된 코드를 별도의 클래스로 추출하여 이 혼란에 질서를 가져올 수 있습니다. 그러나 곧 이러한 수업이 많이 있다는 것을 알게 될 것입니다. 새로운 GUI를 추가하거나 다른 API를 지원하려면 더 많은 클래스를 생성해야 하기 때문에 클래스 계층 구조가 기하 급수적으로 증가 할 것입니다.

이 문제를 Bridge 패턴으로 해결해 보겠습니다. 클래스를 두 개의 계층으로 나눌 것을 제안합니다.

  • 추상화 : 앱의 GUI 계층.
  • 구현 : 운영 체제의 API.

추상화 개체는 앱의 모양을 제어하여 실제 작업을 연결된 구현 개체에 위임합니다. 공통 인터페이스를 따르는 한 서로 다른 구현을 상호 교환 할 수 있으므로 동일한 GUI가 Windows 및 Linux에서 작동 할 수 있습니다.
결과적으로 API 관련 클래스를 건드리지 않고도 GUI 클래스를 변경할 수 있습니다. 또한 다른 운영 체제에 대한 지원을 추가하려면 구현 계층 구조에서 하위 클래스를 만들어야합니다.

Structure

  1. Abstraction는 높은 수준의 제어 로직을 제공합니다. 실제 저수준 작업을 수행하기 위해 구현 개체에 의존합니다.
  2. Implementation은 모든 구체적인 구현에 공통적인 인터페이스를 선언합니다. 추상화는 여기에 선언 된 메서드를 통해서만 구현 개체와 통신 할 수 있습니다.
    추상화는 구현과 동일한 메서드를 나열 할 수 있지만 일반적으로 추상화는 구현에 의해 선언 된 다양한 기본 작업에 의존하는 몇 가지 복잡한 동작을 선언합니다.
  3. Concrete Implementations에는 플랫폼 별 코드가 포함됩니다.
  4. Refined Abstractions는 다양한 제어 로직을 제공합니다. 부모와 마찬가지로 일반 구현 인터페이스를 통해 다른 구현으로 작업합니다.
  5. 일반적으로 Client는 추상화 작업에만 관심이 있습니다. 그러나 추상화 개체를 구현 개체 중 하나와 연결하는 것은 클라이언트의 작업입니다.

Pseudocode

이 예제는 Bridge 패턴이 장치와 원격 제어를 관리하는 앱의 monolithic 코드를 분할하는데 어떻게 도움이 되는지 보여줍니다. Device 클래스는 구현으로 작동하는 반면 Remote는 추상화로 작동합니다.

기본 원격 제어 클래스는 장치 개체와 연결하는 참조 필드를 선언합니다. 모든 리모컨은 일반 장치 인터페이스를 통해 장치와 작동하므로 동일한 리모컨이 여러 장치 유형을 지원할 수 있습니다.

장치 클래스와 독립적으로 원격 제어 클래스를 개발할 수 있습니다. 필요한 것은 새 원격 subclass를 만드는 것입니다. 예를 들어 기본 리모컨에는 버튼이 두 개만 있을 수 있지만 추가 배터리 또는 터치 스크린과 같은 추가 기능을 사용하여 확장 할 수 있습니다.

클라이언트 코드는 원격의 생성자를 통해 원하는 유형의 원격 제어를 특정 장치 개체와 연결합니다.

// The "abstraction" defines the interface for the "control"
// part of the two class hierarchies. It maintains a reference
// to an object of the "implementation" hierarchy and delegates
// all of the real work to this object.
class RemoteControl is
    protected field device: Device
    constructor RemoteControl(device: Device) is
        this.device = device
    method togglePower() is
        if (device.isEnabled()) then
            device.disable()
        else
            device.enable()
    method volumeDown() is
        device.setVolume(device.getVolume() - 10)
    method volumeUp() is
        device.setVolume(device.getVolume() + 10)
    method channelDown() is
        device.setChannel(device.getChannel() - 1)
    method channelUp() is
        device.setChannel(device.getChannel() + 1)


// You can extend classes from the abstraction hierarchy
// independently from device classes.
class AdvancedRemoteControl extends RemoteControl is
    method mute() is
        device.setVolume(0)


// The "implementation" interface declares methods common to all
// concrete implementation classes. It doesn't have to match the
// abstraction's interface. In fact, the two interfaces can be
// entirely different. Typically the implementation interface
// provides only primitive operations, while the abstraction
// defines higher-level operations based on those primitives.
interface Device is
    method isEnabled()
    method enable()
    method disable()
    method getVolume()
    method setVolume(percent)
    method getChannel()
    method setChannel(channel)


// All devices follow the same interface.
class Tv implements Device is
    // ...

class Radio implements Device is
    // ...


// Somewhere in client code.
tv = new Tv()
remote = new RemoteControl(tv)
remote.togglePower()

radio = new Radio()
remote = new AdvancedRemoteControl(radio)

Applicability

일부 기능의 여러 변형이있는 모 놀리 식 클래스를 나누고 구성하려는 경우 (예 : 클래스가 다양한 데이터베이스 서버에서 작동 할 수있는 경우) Bridge 패턴을 사용하십시오.

클래스가 커질수록 어떻게 작동하는지 파악하기가 더 어려워지고 변경하는 데 더 오래 걸립니다. 기능 변형 중 하나를 변경하려면 전체 클래스를 변경해야 할 수 있으며, 이로 인해 종종 오류가 발생하거나 몇 가지 중요한 부작용이 해결되지 않습니다.

Bridge 패턴을 사용하면 모 놀리 식 클래스를 여러 클래스 계층으로 분할 할 수 있습니다. 그런 다음 다른 계층의 클래스와 독립적으로 각 계층의 클래스를 변경할 수 있습니다. 이 접근 방식은 코드 유지 관리를 단순화하고 기존 코드를 손상시킬 위험을 최소화합니다.

여러 직교 (독립) 차원에서 클래스를 확장해야하는 경우 패턴을 사용하십시오.

Bridge는 각 차원에 대해 별도의 클래스 계층을 추출 할 것을 제안합니다. 원래 클래스는 모든 작업을 자체적으로 수행하는 대신 해당 계층에 속한 개체에 관련 작업을 위임합니다.

런타임에 구현을 전환 할 수 있어야하는 경우 Bridge를 사용하십시오.

선택 사항이지만 Bridge 패턴을 사용하면 추상화 내부의 구현 개체를 바꿀 수 있습니다. 필드에 새 값을 할당하는 것만 큼 쉽습니다.

그건 그렇고,이 마지막 항목은 많은 사람들이 Bridge를 전략 패턴과 혼동하는 주된 이유입니다. 패턴은 클래스를 구조화하는 특정 방법 이상이라는 것을 기억하십시오. 또한 의도와 해결중인 문제를 전달할 수 있습니다.

How to Implement

  1. 클래스에서 직교 차원을 식별하십시오. 이러한 독립적 인 개념은 abstraction/platform, domain/infrastructure, front-end/back-end, or interface/implementation 일 수 있습니다.

  2. 클라이언트에 필요한 작업을 확인하고 기본 추상화 클래스에서 정의합니다.

  3. 모든 플랫폼에서 사용할 수 있는 작업을 결정합니다. 일반 구현 인터페이스에서 추상화에 필요한 것을 선언하십시오.

  4. 도메인의 모든 플랫폼에 대해 구체적인 구현 클래스를 생성하되 모두 구현 인터페이스를 따르는지 확인하십시오.

  5. 추상화 클래스 내에 구현 유형에 대한 참조 필드를 추가하십시오. 추상화는 대부분의 작업을 해당 필드에서 참조되는 구현 개체에 위임합니다.

  6. 고수준 로직의 여러 변형이 있는 경우 기본 추상화 클래스를 확장하여 각 변형에 대해 정제된 추상화를 만듭니다.

  7. 클라이언트 코드는 구현 객체를 추상화의 생성자에 전달하여 하나를 다른 객체와 연결해야 합니다. 그 후에 클라이언트는 구현을 잊어버리고 추상화 개체로만 작업 할 수 있습니다.

Pros and Cons

O 플랫폼에 독립적 인 클래스와 앱을 만들 수 있습니다.

O 클라이언트 코드는 높은 수준의 추상화와 함께 작동합니다. 플랫폼 세부 정보에 노출되지 않습니다.

O 개방 / 폐쇄 원칙. 서로 독립적으로 새로운 추상화와 구현을 도입 할 수 있습니다.

O 단일 책임 원칙. 추상화의 고수준 논리와 구현의 플랫폼 세부 사항에 집중할 수 있습니다.

X 응집력이 높은 클래스에 패턴을 적용하여 코드를 더 복잡하게 만들 수 있습니다.

Relations with Other Patterns

  • Bridge는 일반적으로 미리 설계되어 응용 프로그램의 일부를 서로 독립적으로 개발할 수 있습니다. 반면에 Adapter는 일반적으로 기존 앱과 함께 사용되어 호환되지 않는 일부 클래스가 잘 작동하도록 합니다.
  • Bridge, State, Strategy (and to some degree Adapter)는 매우 유사한 구조를 가지고 있습니다. 실제로 이러한 모든 패턴은 작업을 다른 개체에 위임하는 구성을 기반으로 합니다. 그러나 그들은 모두 다른 문제를 해결합니다. 패턴은 특정 방식으로 코드를 구조화하기 위한 단순한 방법이 아닙니다. 또한 패턴이 해결하는 문제를 다른 개발자에게 전달할 수 있습니다.
  • Bridge와 함께 Abstract Factory를 사용할 수 있습니다. 이 쌍은 Bridge에서 정의한 일부 추상화가 특정 구현에서만 작동 할 수있는 경우에 유용합니다. 이 경우 Abstract Factory는 이러한 관계를 캡슐화하고 클라이언트 코드에서 복잡성을 숨길 수 있습니다.
  • BuilderBridge를 결합 할 수 있습니다. director 클래스는 추상화 역할을하는 반면 다른 빌더는 구현 역할을합니다.
profile
software engineer

0개의 댓글