Bridge Pattern은 언제 사용합니까? 어댑터 패턴과 어떻게 다릅니 까?


158

실제 응용 프로그램에서 Bridge Pattern 을 사용한 적이 있습니까? 그렇다면 어떻게 사용 했습니까? 나입니까, 아니면 약간의 의존성 주입이 혼합 된 어댑터 패턴입니까? 정말 자체 패턴을 가질 가치가 있습니까?


이 질문에 대해 다른 답변을 받아들이십시오. 현재 받아 들여지는 답변은 정확하지 않으며 도움이되지 않습니다. 최신 답변이 훨씬 우수합니다.
jaco0646

의 GoF 책은 바로이 질문에 대한 대답.
jaco0646

답변:


77

Bridge 패턴의 전형적인 예는 UI 환경에서 모양을 정의하는 데 사용됩니다 ( Bridge pattern Wikipedia 항목 참조 ). Bridge 패턴은 템플릿전략 패턴 의 합성물 입니다 .

브리지 패턴에서 어댑터 패턴의 몇 가지 측면을 공통적으로 볼 수 있습니다. 그러나이 기사 에서 인용하면 다음과 같습니다.

언뜻보기에 Bridge 패턴은 클래스가 한 종류의 인터페이스를 다른 종류로 변환하는 데 사용된다는 점에서 Adapter 패턴과 매우 유사합니다. 그러나 어댑터 패턴의 목적은 하나 이상의 클래스의 인터페이스를 특정 클래스의 인터페이스와 동일하게 만드는 것입니다. Bridge 패턴은 구현에서 클래스의 인터페이스를 분리하도록 설계되었으므로 클라이언트 코드를 변경하지 않고도 구현을 변경하거나 대체 할 수 있습니다.


4
Bridge는 템플릿 또는 전략과 관련이 없습니다. Bridge는 구조적 패턴입니다. 템플릿과 전략은 행동 패턴입니다.
jaco0646

256

FedericoJohn의 답변 이 조합되어 있습니다.

언제:

                   ----Shape---
                  /            \
         Rectangle              Circle
        /         \            /      \
BlueRectangle  RedRectangle BlueCircle RedCircle

리팩터링 :

          ----Shape---                        Color
         /            \                       /   \
Rectangle(Color)   Circle(Color)           Blue   Red

6
색상에 대한 상속을하는 이유는 무엇입니까?
vainolo

10
@vainolo 색상은 인터페이스이고 파란색, 빨간색은 구체적인 색상이기 때문에
Weltschmerz 2014

3
이것은 단지 리팩토링입니다. 브리지 패턴 의도 : "구현에서 추상화를 분리하여 두 가지가 독립적으로 변할 수 있도록합니다." 추상화는 어디에 있으며 구현은 어디에 있습니까?
clapas

1
Rectangle (Color)은 BlueRectangle보다 더 추상적 인 것이 아닙니까?
Anton Shchastnyi

2
@clapas, Abstraction은 속성의 "Shape.color"이므로 Red 클래스와 Blue 클래스가 구현되고 Color 인터페이스는 bridge입니다.
reco dec

232

Bridge 패턴은 "상속보다 컴포지션 선호"라는 오래된 조언을 적용한 것입니다. 서로 직교하는 방식으로 서로 다른 시간을 하위 클래스 화해야 할 때 편리합니다. 컬러 모양의 계층을 구현해야한다고 가정 해 보겠습니다. Rectangle과 Circle을 사용하여 Shape를 하위 클래스로 분류 한 다음 RedRectangle, BlueRectangle 및 GreenRectangle을 사용하여 Rectangle을 하위 클래스로 만들고 Circle에 대해서도 동일하게 하위 클래스를 만들지 않겠습니까? 각 모양 에는 색상 있고 색상 계층 구조를 구현하는 것이 좋습니다. 이것이 바로 브릿지 패턴입니다. 글쎄요, 저는 "색상 계층 구조"를 구현하지 않겠지 만 당신은 아이디어를 얻었습니다.


1
이 설명에 대한 그래픽 설명은 아래의 Anton Shchastnyi 다이어그램을 참조하십시오.
NomadeNumerique 2014

2
색상이 구현 계층 구조의 좋은 예라고 생각하지 않습니다. 다소 혼란 스럽습니다. GoF의 "디자인 패턴"에있는 Bridge 패턴의 좋은 예가 있습니다. 여기서 구현은 플랫폼에 따라 다릅니다. IBM의 PM, UNIX의 X 등
clapas

217

언제:

        A
     /     \
    Aa      Ab
   / \     /  \
 Aa1 Aa2  Ab1 Ab2

리팩터링 :

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2

4
더 나은 인수 분해 하나에 디자인 2) 리팩토링 디자인 / 코드 바로 앞으로 차선을 설명 1) : 나는 패턴과 매우 실용적인 접근 방식이라고 생각
알렉세이

1
수학 개념을 사용하여 Bridge 디자인 패턴을 설명합니다. 매우 흥미 롭다.
Jian Huang

1
이것은 단지 리팩토링입니다. 브리지 패턴 의도 : "구현에서 추상화를 분리하여 두 가지가 독립적으로 변할 수 있도록합니다." 추상화는 어디에 있으며 구현은 어디에 있습니까?
clapas

John은 그것을 블로그 게시물에 멋지게 올렸 습니다. 높은 수준의 개요를 읽기에 좋은 것으로 나타났습니다.
Vaibhav Bhalla

30

어댑터와 브리지는 확실히 관련이 있으며 그 차이는 미묘합니다. 이러한 패턴 중 하나를 사용하고 있다고 생각하는 일부 사람들은 실제로 다른 패턴을 사용하고있을 가능성이 있습니다.

내가 본 설명은 이미 존재 하는 일부 호환되지 않는 클래스의 인터페이스를 통합하려고 할 때 Adapter가 사용된다는 것 입니다. 어댑터는 레거시 로 간주 될 수있는 구현에 대한 일종의 변환기 역할을합니다 .

브리지 패턴은 그린 필드 일 가능성이 더 높은 코드에 사용됩니다. 변경해야하는 구현에 대한 추상 인터페이스를 제공하기 위해 Bridge를 디자인하고 있지만 해당 구현 클래스의 인터페이스도 정의합니다.

장치 드라이버는 Bridge의 자주 인용되는 예이지만 장치 공급 업체에 대한 인터페이스 사양을 정의하는 경우 Bridge라고 말하고 싶지만 기존 장치 드라이버를 가져 와서 래퍼 클래스를 만드는 경우 어댑터입니다. 통합 인터페이스를 제공합니다.

따라서 코드 측면에서 두 패턴은 매우 유사합니다. 비즈니스 측면에서는 다릅니다.

http://c2.com/cgi/wiki?BridgePattern 참조


안녕 빌. 장치 드라이버에서 브리지 패턴을 사용해야하는 이유를 이해하지 못합니다. 다형성을 통해 올바른 클래스에 구현 (읽기, 쓰기, 탐색 등)을 쉽게 위임 할 수 있다는 뜻입니까? 아니면 방문자와 함께? Bridge 여야하는 이유는 무엇입니까? 미리 감사드립니다.
stdout

1
@zgulser, 예, 다형성을 사용합니다. Bridge 패턴은 구현과 추상화를 분리하기위한 한 종류의 서브 클래스 사용을 설명합니다.
Bill Karwin

Let 's day Color abstraction에서 Shape 구현 (즉, Rectangle)을 분리하는 것을 의미 했습니까? 그리고 저는 당신이 그것을 할 수있는 다양한 방법이 있다고 말하고 있다고 믿습니다. 그리고 Bridge는 그 중 하나 일뿐입니다.
stdout

예, 하위 분류에는 다른 용도가 있습니다. 하위 클래스를 사용하는이 특별한 방법이이를 Bridge 패턴으로 만듭니다.
Bill Karwin

그리고 제가 의미하는 분리는 추상적 인 Shape 인터페이스에서 구체적인 Rectangle 구현으로의 것입니다. 따라서 구체적인 객체가 실제로 Shape의 일부 하위 클래스 임에도 불구하고 "Shape"유형의 객체가 필요한 코드를 작성할 수 있습니다.
Bill Karwin

29

내 경험상 Bridge는 도메인에 두 개의 직교 차원이있을 때마다 솔루션이기 때문에 꽤 자주 반복되는 패턴 입니다. 예 : 모양 및 그리기 방법, 동작 및 플랫폼, 파일 형식 및 직렬 변환기 등.

그리고 조언 : 항상 구현 관점이 아닌 개념적 관점에서 디자인 패턴 생각 하십시오. 올바른 관점에서 보면 Bridge는 다른 문제를 해결하기 때문에 Adapter와 혼동 할 수 없으며, 구성은 자체 때문이 아니라 직교 문제를 개별적으로 처리 할 수 ​​있기 때문에 상속보다 우수합니다.


22

BridgeAdapter 의 의도 는 다르며 두 패턴이 별도로 필요합니다.

브리지 패턴 :

  1. 구조적 패턴입니다.
  2. 추상화 및 구현은 컴파일 타임에 바인딩되지 않습니다.
  3. 추상화 및 구현-둘 다 클라이언트에 영향을주지 않고 달라질 수 있습니다.
  4. 상속보다 구성을 사용합니다.

다음과 같은 경우 브리지 패턴을 사용합니다.

  1. 구현의 런타임 바인딩을 원합니다.
  2. 결합 된 인터페이스와 수많은 구현으로 인해 클래스가 급증하고 있습니다.
  3. 여러 개체간에 구현을 공유하려는 경우
  4. 직교 클래스 계층 구조를 매핑해야합니다.

@ John Sonmez 답변은 클래스 계층 구조를 줄이는 데 브리지 패턴의 효과를 명확하게 보여줍니다.

아래 문서 링크를 참조하여 코드 예제로 브리지 패턴에 대한 더 나은 통찰력을 얻을 수 있습니다.

어댑터 패턴 :

  1. 작업 함께 두 관련이없는 인터페이스를 허용 가능한 같은 역할을하고, 다른 객체를 통해.
  2. 원래 인터페이스를 수정합니다.

주요 차이점 :

  1. 어댑터 는 설계된 후에 작동합니다. Bridge 는 그들이 존재하기 전에 작동하도록합니다.
  2. Bridge추상화와 구현이 독립적으로 변경 될 수 있도록 미리 설계되었습니다 . 어댑터 는 관련없는 클래스가 함께 작동하도록 개조되었습니다.
  3. 의도 : 어댑터를 사용하면 관련되지 않은 두 인터페이스가 함께 작동 할 수 있습니다. Bridge를 사용하면 추상화 및 구현이 독립적으로 달라질 수 있습니다.

UML 다이어그램 및 작업 코드와 관련된 SE 질문 :

브리지 패턴과 어댑터 패턴의 차이점

유용한 기사 :

sourcemaking 브리지 패턴 기사

sourcemaking 어댑터 패턴 기사

journaldev 브리지 패턴 기사

편집하다:

Bridge Pattern 실제 사례

브리지 패턴은 구현에서 추상화를 분리하여 둘 다 독립적으로 달라질 수 있습니다. 그것은 상속이 아닌 구성으로 달성되었습니다.

Wikipedia의 브리지 패턴 UML :

Wikipedia의 브리지 패턴 UML

이 패턴에는 네 가지 구성 요소가 있습니다.

Abstraction: 인터페이스를 정의합니다.

RefinedAbstraction: 추상화를 구현합니다.

Implementor: 구현을위한 인터페이스를 정의합니다.

ConcreteImplementor: Implementor 인터페이스를 구현합니다.

The crux of Bridge pattern :컴포지션을 사용하는 두 개의 직교 클래스 계층 (상속 없음). 추상화 계층 및 구현 계층은 독립적으로 다를 수 있습니다. 구현은 추상화를 참조하지 않습니다. 추상화에는 구성을 통해 구현 인터페이스가 구성원으로 포함됩니다. 이 구성은 상속 계층의 수준을 한 단계 더 줄입니다.

실제 단어 사용 사례 :

다른 차량이 수동 및 자동 기어 시스템의 두 버전을 모두 갖도록합니다.

예제 코드 :

/* Implementor interface*/
interface Gear{
    void handleGear();
}

/* Concrete Implementor - 1 */
class ManualGear implements Gear{
    public void handleGear(){
        System.out.println("Manual gear");
    }
}
/* Concrete Implementor - 2 */
class AutoGear implements Gear{
    public void handleGear(){
        System.out.println("Auto gear");
    }
}
/* Abstraction (abstract class) */
abstract class Vehicle {
    Gear gear;
    public Vehicle(Gear gear){
        this.gear = gear;
    }
    abstract void addGear();
}
/* RefinedAbstraction - 1*/
class Car extends Vehicle{
    public Car(Gear gear){
        super(gear);
        // initialize various other Car components to make the car
    }
    public void addGear(){
        System.out.print("Car handles ");
        gear.handleGear();
    }
}
/* RefinedAbstraction - 2 */
class Truck extends Vehicle{
    public Truck(Gear gear){
        super(gear);
        // initialize various other Truck components to make the car
    }
    public void addGear(){
        System.out.print("Truck handles " );
        gear.handleGear();
    }
}
/* Client program */
public class BridgeDemo {    
    public static void main(String args[]){
        Gear gear = new ManualGear();
        Vehicle vehicle = new Car(gear);
        vehicle.addGear();

        gear = new AutoGear();
        vehicle = new Car(gear);
        vehicle.addGear();

        gear = new ManualGear();
        vehicle = new Truck(gear);
        vehicle.addGear();

        gear = new AutoGear();
        vehicle = new Truck(gear);
        vehicle.addGear();
    }
}

산출:

Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear

설명:

  1. Vehicle 추상화입니다.
  2. CarTruck두 가지 구체적인 구현입니다 Vehicle.
  3. Vehicle추상적 인 방법을 정의합니다 : addGear().
  4. Gear 구현 자 인터페이스
  5. ManualGear그리고 AutoGear두 가지 구현입니다 Gear
  6. Vehicleimplementor인터페이스를 구현하는 대신 인터페이스를 포함 합니다. Compositon구현 자 인터페이스의 핵심은이 패턴의 핵심입니다. 추상화와 구현이 독립적으로 달라질 수 있습니다.
  7. CarTruck추상화를위한 구현 (재정의 된 추상화)을 정의합니다. addGear(): : 포함 Gear-둘 중 하나 Manual또는Auto

브리지 패턴의 사용 사례 :

  1. 추상화구현 은 서로 독립적으로 변경 될 수 있으며 컴파일 타임에 바인딩되지 않습니다.
  2. 직교 계층 구조 매핑-하나는 추상화 용 이고 다른 하나는 구현 용 입니다.

"Adapter는 설계가 완료된 후에 작동하게하고 Bridge는 작동하기 전에 작동하도록합니다." 플러그 형 어댑터를 살펴볼 수 있습니다. GoF가 Design Patterns 책의 "Adapter"섹션에서 설명한 Adapter의 변형입니다. 목적은 아직 존재하지 않는 클래스에 대한 인터페이스를 만드는 것입니다. 플러그 형 어댑터는 브리지가 아니므로 첫 번째 포인트가 타당하다고 생각하지 않습니다.
c1moore nov.

수동 및 자동 기어는 트럭과 자동차에 대해 다른 구현이 필요할 수 있지만
andigor

9

나는 직장에서 브리지 패턴을 사용했습니다. 필자는 종종 PIMPL 관용구 (구현 포인터)라고하는 C ++로 프로그래밍합니다. 다음과 같이 보입니다.

class A
{
public: 
  void foo()
  {
    pImpl->foo();
  }
private:
  Aimpl *pImpl;
};

class Aimpl
{
public:
  void foo();
  void bar();
};  

이 예 class A에는 인터페이스가 포함되어 있습니다.class Aimpl 포함하고 구현을 포함합니다.

이 패턴의 한 가지 용도는 구현 클래스의 공용 멤버 중 일부만 노출하고 나머지는 노출하지 않는 것입니다. 이 예제에서는 Aimpl::foo()공용 인터페이스를 통해서만 호출 할 수 있습니다.A 있지만Aimpl::bar()

또 다른 이점은 Aimpl사용자가 포함 할 필요가없는 별도의 헤더 파일에서 정의 할 수 있다는 것 입니다 A. 당신이해야 할 일은 Aimplbefore Ais defined 의 정방향 선언을 사용하고 참조하는 모든 멤버 함수의 정의를 pImpl.cpp 파일로 옮기는 것 입니다. 이를 통해 Aimpl헤더를 비공개 로 유지 하고 컴파일 시간을 줄일 수 있습니다.


2
이 패턴을 사용하면 AImpl은 헤더도 필요하지 않습니다. A 클래스의 구현 파일에 인라인으로 넣었습니다
1800 INFORMATION

귀하의 구현자는 비공개입니다. 이와 관련하여 새로운 질문이 있습니다. stackoverflow.com/questions/17680762/…를
Roland

7

코드에 모양 예제를 넣으려면 :

#include<iostream>
#include<string>
#include<cstdlib>

using namespace std;

class IColor
{
public:
    virtual string Color() = 0;
};

class RedColor: public IColor
{
public:
    string Color()
    {
        return "of Red Color";
    }
};

class BlueColor: public IColor
{
public:
    string Color()
    {
        return "of Blue Color";
    }
};


class IShape
{
public:
virtual string Draw() = 0;
};

class Circle: public IShape
{
        IColor* impl;
    public:
        Circle(IColor *obj):impl(obj){}
        string Draw()
        {
            return "Drawn a Circle "+ impl->Color();
        }
};

class Square: public IShape
{
        IColor* impl;
    public:
        Square(IColor *obj):impl(obj){}
        string Draw()
        {
        return "Drawn a Square "+ impl->Color();;
        }
};

int main()
{
IColor* red = new RedColor();
IColor* blue = new BlueColor();

IShape* sq = new Square(red);
IShape* cr = new Circle(blue);

cout<<"\n"<<sq->Draw();
cout<<"\n"<<cr->Draw();

delete red;
delete blue;
return 1;
}

출력은 다음과 같습니다.

Drawn a Square of Red Color
Drawn a Circle of Blue Color

순열로 인해 하위 클래스가 폭발적으로 증가하지 않고 시스템에 새로운 색상과 모양을 쉽게 추가 할 수 있습니다.


0

저에게는 인터페이스를 교환 할 수있는 메커니즘이라고 생각합니다. 실제 세계에서는 둘 이상의 인터페이스를 사용할 수있는 클래스가있을 수 있습니다. Bridge를 사용하면 스왑 할 수 있습니다.


0

회계, 계약, 청구 등 다양한 종류의 작업을 관리하는 워크 플로 응용 프로그램을 개발하는 보험 회사에서 일하고 있습니다. 이것이 추상화입니다. 구현 측면에서 전자 메일, 팩스, 전자 메시지 등 다양한 소스에서 작업을 생성 할 수 있어야합니다.

다음 클래스로 디자인을 시작합니다.

public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

이제 각 소스를 특정 방식으로 처리해야하므로 각 작업 유형을 전문화하기로 결정합니다.

public class EmailAccountingTask : AccountingTask {...}
public class FaxAccountingTask : AccountingTask {...}
public class EmessagingAccountingTask : AccountingTask {...}

public class EmailContractTask : ContractTask {...}
public class FaxContractTask : ContractTask {...}
public class EmessagingContractTask : ContractTask {...}

public class EmailClaimTask : ClaimTask {...}
public class FaxClaimTask : ClaimTask {...}
public class EmessagingClaimTask : ClaimTask {...}

13 개의 수업으로 끝납니다. 작업 유형 또는 소스 유형을 추가하는 것이 어려워집니다. 브리지 패턴을 사용하면 작업 (추상화)을 소스 (구현 문제)에서 분리하여 유지 관리하기가 더 쉽습니다.

// Source
public class Source {
   public string GetSender();
   public string GetMessage();
   public string GetContractReference();
   (...)
}

public class EmailSource : Source {...}
public class FaxSource : Source {...}
public class EmessagingSource : Source {...}

// Task
public class Task {
   public Task(Source source);
   (...)
}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

이제 작업 유형 또는 소스를 추가하는 것이 훨씬 더 쉬워졌습니다.

참고 : 대부분의 개발자는이 문제를 처리하기 위해 13 개의 클래스 계층 구조를 미리 만들지 않습니다. 그러나 실제 생활에서는 소스 및 작업 유형의 수를 미리 알지 못할 수 있습니다. 소스가 하나만 있고 작업 유형이 두 개이면 소스에서 작업을 분리하지 않을 것입니다. 그런 다음 새로운 소스와 작업 유형이 추가됨에 따라 전반적인 복잡성이 증가합니다. 언젠가는 리팩토링을하게되고 대부분의 경우 브리지와 같은 솔루션으로 끝납니다.


-5
Bridge design pattern we can easily understand helping of service and dao layer.

Dao layer -> create common interface for dao layer ->
public interface Dao<T>{
void save(T t);
}
public class AccountDao<Account> implement Dao<Account>{
public void save(Account){
}
}
public LoginDao<Login> implement Dao<Login>{
public void save(Login){
}
}
Service Layer ->
1) interface
public interface BasicService<T>{
    void save(T t);
}
concrete  implementation of service -
Account service -
public class AccountService<Account> implement BasicService<Account>{
 private Dao<Account> accountDao;
 public AccountService(AccountDao dao){
   this.accountDao=dao;
   }
public void save(Account){
   accountDao.save(Account);
 }
}
login service- 
public class LoginService<Login> implement BasicService<Login>{
 private Dao<Login> loginDao;
 public AccountService(LoginDao dao){
   this.loginDao=dao;
   }
public void save(Login){
   loginDao.save(login);
 }
}

public class BridgePattenDemo{
public static void main(String[] str){
BasicService<Account> aService=new AccountService(new AccountDao<Account>());
Account ac=new Account();
aService.save(ac);
}
}
}

5
나는 이것이 복잡하고 형식이 잘못된 대답이라고 생각하기 때문에 반대 투표했습니다.
Zimano

1
완전히 당신이 코드 들여 쓰기 및 선명도에 최소한의 관심없이이 사이트에 답변을 게시 할 수있는 방법, 동의
마시밀리아노 크라우스
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.