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:
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:
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:
Printer
interface. It selects the correct printer based on the user input and use the Printer accordingly.Printer
interface. It facilitates its own evolution and growth (add more printer) independently from the Terminal
structure. Hence, they are isolated from each other.