Bridge Pattern

실리콘·2023년 2월 22일
0

study of Design Patterns (1994)

Intent

Decouple an abstraction from its implementation so that the two can vary independently.

Also Known As

Handle/Body

Motivation

Inheritance is usually the way when an abstraction can have several implementations. Sometimes it is not flexible enough.

Say, Window abstract class has many product subclasses IconWindow, ButtonWindow etc. Then, to support platforms, you have explosion of subclasses. (# of product kind) x (# of platforms to support). Better managed in two hiearchies Window for type, WindowImpl for platform.

Also, client code becomes dependent on the platform. for example , to use window on macOS, you would need to instantiate MacIconWindow, MacButtonWindow etc., not just IconWindow or ButtonWindow. This makes porting really difficult. Clients should be able to create a window w/o commiting to a concrete information. i.e. instantiate windows w/o mentioning specific platforms.
diagram from book

This relationship between Window andWindowImpl abstract classes is Bridge.

Applicability

Use when

  • You want to avoid a permanent binding between abstraction and its implementaion. For example, when implementaion must be selected or switched at run-time.
  • Both the abstraction and implementation should be extensible by subclassing. Birdge Pattern lets you combine different abstracion and implementaions and also extend them independently.

Structure

Participants

  • Abstraction (Window)
    • defines the abstraction's interface
    • maintains a reference to an object of type Implementor.
  • RefmedAbstraction (IconWindow)
    • extends the interface defined by Abstraction.
  • Implementor (WindowImp)
    • Defines the inteerface for implementaion classes. Doesn't have to correspond exactly to Abstraction's interface; can be different.
    • Typically, Implementor defines primitives, Abstraction has higher-level operations based on these primitives.
  • ConcreteImplementor (XWindowImp, PMWindowImp)
    • implements the Implementor interface and defines its concrete implementation

Collaborations

  • Abstraction forwards client requests to its Implementor object.

Consequences

  1. Decoupling interface and implementation. An implementation is not bound permanently to an interface. It can be configured at run-time. Even possible to change its implementation at run-time.

Also eliminates compile-time dependencies. Changing an implementation class doesn’t require recompiling the Abstraction class and its clients. Essential when binary compatibility for difference versions of a class library.

Encourage layering that can lead to a better-structured system.

  1. Improved Extensibility. You can extend the Abstraction and Implementor hiearchies independently.
  2. Hiding implementation details from clients. Hide stuff like sharing of implementor objects and the accompanying reference count mechanism (if any).

Implementation

  1. Only one Implementor. then its a degenerate case. why bother? but still can be useful when a change in implementation of a class must not effect existing clients (ie. when recompile no, but relink ok). In C++, can hide class interface of Implementor class in a private header file that isn't provided to clients.
  2. Creating the right Implementor object.
    If Abstraction know about all ConcreteImplementor classes, then it can instantiate one of them in its constructor, i.e. decide using parameters passed to its constructor. ex. a collection class takes small size, then use linked list. if larger size, hash table etc.

Another approach is to choose a default, then change it later accordingly. ex. If collection grows too big, change to hash table.

Also possible to delegate the decision to another oject altogeter. In the Window/WindowImp example, we can introduce a factory object (see Abstract Factory (87)) whose sole duty is to encapsulate platform-specifics. The factory knows what kind of WindowImp object to create for the platform in use; a Window simply asks it for a WindowImp, and it returns the right kind. A benefit of this approach is that Abstraction is not coupled directly to any of the Implementor classes.

  1. Sharing implementors.
    from book about reference count managing for shared Implementors.
Handle& Handle::operator= (const Handle& other) {
	other._body->Ref();
    _body->Unref();
    
    if(_body->RefCount() == 0_ {
    	delete _body;
    }
    _body = other._body;
    
    return *this;
       
  1. Using multiple inheritance. In C++, you can use multiple inheritance to combine an interface with its implementaion. Possible in typescript and python too. Ex. a class can inherit publicly from Abstraction, and privately from a ConcreteImplementor. But this relies on static inheritance, binding implementation to its interface. Therefore you can't implement a true Bridge w/ multiple inheritance, at least not in C++.

Sample Code

In book, C++. I rewrite it in python3 skipping details.

Known Uses

only skim thru. What is an ET++? libg++? NXImage?

Abstract Factory to create and configure a particular Bridge.
The Adapter pattern is geared toward making unrelated classes work together. It is usually applied to systems after they’re designed. Bridge, on the other hand, is used up-front in a design to let abstractions and implementations vary independently.

profile
software engineer

0개의 댓글