Bridge

Bridge is a design pattern to isolate (not just encapsulate) volatile abstractions from the existing abstractions, making it simpler to integrate variable from multiple degrees. Example, say a threading scheduler has:

  1. variable scheduling strategy
  2. each strategy must support various operating systems.

Hence, we got a native design pattern like this:

Using such design, although faster introduces a lot of duplication for the 2nd degree variable: operating system support. Hence, it is better to isolate the 2nd degree variable using what we called "Bridge" manner into this design:

This way, both the 1st degree variable (types of threading) and the 2nd degree variable (operating system supports) get completely isolated.


The diagram is shown as follows:

When to Use (Problem)

  • When the problem involves multiple degrees of design variables in the design architecture.
  • When the problem requires isolation between 2 or more different sets of design variables.

Example (Solution)

Here is an example in Go:

package main

import (
        "fmt"
)

const (
        PrinterA2 = uint(0)
        PrinterB2 = uint(1)
        PrinterCO = uint(2)
)

/********************************************************************
 * Printer (2nd Degree Variable)                                    *
 ********************************************************************/
// Printer is the platform printer interfaces for Print functions
type Printer interface {
        LocalPrint(data string)
}

// A2Printer is a type of printer
type A2Printer struct {
}

func (p *A2Printer) LocalPrint(data string) {
        fmt.Printf("A2 Print - %v\n", data)
}

// B2Printer is a type of printer
type B2Printer struct {
}

func (p *B2Printer) LocalPrint(data string) {
        fmt.Printf("B2 Print - %v\n", data)
}

// COPrinter is a type of printer
type COPrinter struct {
}

func (p *COPrinter) LocalPrint(data string) {
        fmt.Printf("CO Print - %v\n", data)
}

/********************************************************************
 * Pre-processing (1st Degree Variable)                             *
 ********************************************************************/
// Terminal is the object client interacts with.
type Terminal struct {
        printer Printer
}

// Init initializes the terminal with a specific printer.
func (t *Terminal) Init(printerID uint) {
        switch printerID {
        case PrinterA2:
                t.printer = &A2Printer{}
        case PrinterB2:
                t.printer = &B2Printer{}
        case PrinterCO:
                t.printer = &COPrinter{}
        }
}

// Print asks user to print something.
func (t *Terminal) Print(data string) {
        if t.printer == nil {
                return
        }

        d := fmt.Sprintf("term: %v", data)
        t.printer.LocalPrint(d)
}

/********************************************************************
 * Client                                                           *
 ********************************************************************/
func main() {
        t := &Terminal{}

        // first print using A2 configurations
        t.Init(PrinterA2)
        t.Print("Hello World")

        // first print using B2 configurations
        t.Init(PrinterB2)
        t.Print("Bonjour Le Monde")

        // third print using CO configurations
        t.Init(PrinterCO)
        t.Print("こんにちは世界")
}

// Output:
// A2 Print - term: Hello World
// B2 Print - term: Bonjour Le Monde
// CO Print - term: こんにちは世界

Notice that there are 3 layers of interface:

  1. The client, in which it is only interacting with the 1st degree variable interface. It setups the terminal and process the client's input.
  2. The 1st degree variable interface is bridged to 2nd degree variable interface, which is the Printer interface. It selects the correct printer based on the user input and use the Printer accordingly.
  3. The 2nd degree variable is isolated as long as all printers comply to the Printer interface. It facilitates its own evolution and growth (add more printer) independently from the Terminal structure. Hence, they are isolated from each other.

Expected Outcome (Consequences)

  1. Multiple design variables are isolated in the application architecture.
  2. Each design variables are free to grow on its own. Using other structural approach like Adapter design pattern together, one can fully isolate the design variables strategically.