The bridge pattern

Remember that, at the beginning of the chapter, we discussed dynamic composition that allows you to change the implementation of an object at runtime. The bridge pattern is another structural pattern that allows this.

Roles

The bridge pattern decouples an abstraction from its implementation. This means that this pattern separates the implementation of an object from its representation and interface.

Thus, firstly, the implementation can fully be encapsulated, and secondly, the implementation and representation can independently change, without having any constraints between them.

This pattern can be used:

  • To avoid a strong link between the object representation and its implementations
  • To avoid any impact between the interaction of the objects and their clients when the implementations of objects are modified
  • To allow the representation of objects and their implementations to keep their extension capability by creating new subclasses
  • To avoid to obtain very complex classes hierarchies

Design

In the class diagram of the bridge pattern, the separation between the abstraction and the implementation is very well represented—notice the left-hand side and right-hand side of the following diagram:

Design

Participants

The bridge pattern uses a minimum of four participants:

  • The AbstractClass represents the domain objects. This class contains the interface used by clients and contains a reference to an object that implements the Implementation interface.
  • The ConcreteClass is the class that implements the methods defined in the AbstractClass.
  • The ImplementationBase class defines the method signature of the concrete implementation classes. The methods defined here differ from the methods of the Abstract class. These two sets of methods are different. Generally, methods of the AbstractClass are high-level methods, while the methods of the implementation class are low-level methods.
  • The ConcreteImplementationA (B …) classes are concrete classes that realize methods introduced in the ImplementationBase interface.

Tip

The ImplementationBase interface represents the bridge.

Collaboration

The operation of AbstractClass and its subclasses invokes the methods defined in the ImplementationBase interface, which represent the bridge.

Illustration

We should be able to turn on the light or TV using the same object. Once my code to turn on the light or TV will be implemented in my client, I don't need to modify it if the ConcreteImplementation structure changes. Using the bridge pattern, I will use an object that inherits from AbstractClass. This object contains a method that the client will consume. This method doesn't turn on the TV, but it calls the method defined in the ImplementationBase class; thus, depending on the object that our abstract object uses, it will run the actions that are defined in the ConcreteImplementation class, which are represented by the TV or the light.

Implementation

Given the preceding problem, we will first define the method and a property that contains the object we want to manipulate. This object will implement the ImplementationBase interface, which represents the bridge.

The object that will be manipulated by clients will have a turnOn() method. This is the only method known by the client:

// IAbstractBridge
protocol IAbstractBridge {
  var concreteImpl: ImplementationBase {get set}
  func turnOn()
}

Now, we will define the ImplementationBase interface. It contains the run()method that each ConcreteImplementation class will implement:

//Bridge
protocol ImplementationBase {
  func run()
}

Our interfaces are now ready; we can create the RemoteControl class that our clients will use. Depending on the object referred in the concreteImpl property, the turnOn() method will call the run method of the concreteImpl object. To obtain a reference to the concreteImpl object, we will add an argument to the constructor (init) of the RemoteControl class:

/* Concrete Abstraction */
class RemoteControl: IAbstractBridge {
  var concreteImpl: ImplementationBase
  
  func turnOn() {
    self.concreteImpl.run()
  }
  
  init(impl: ImplementationBase) {
    self.concreteImpl = impl
  }
}

Finally, we implement our ImplementationBase class for the TV and Light classes. A run() method is needed in each of them. The run() method contains all the needed logic that will permit you to turn on the light or TV. In our example, we only display a text that indicates the action has been completed:

/* Implementation Classes 1 */
class TV: ImplementationBase {
  func run() {
    println("tv turned on");
  }
}

/* Implementation Classes 2 */
class Light: ImplementationBase {
  func run() {
    println("light turned on")
  }
}

Usage

From the client's perspective, we will use our RemoteControl abstraction class, pass the final object to the constructor when we want to manipulate (the Light or TV class), and call the turnOn() method of the RemoteControl object to execute the action:

let tvRemoteControl = RemoteControl(impl: TV())
tvRemoteControl.turnOn()

let lightRemoteControl = RemoteControl(impl: Light())
lightRemoteControl.turnOn()

Thanks to Playground, we can now see the live result, which is as follows:

Usage

We can see two messages on the right-hand side of our Playground file: tv turned on and light turned on, which means that the run() method of each final object has been correctly executed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset