Bridge (design pattern)
A bridge (English bridge pattern ) is in the software development , a structural pattern (English structural pattern ), the separation of the implementation of their abstraction ( interface ) is used. This means that both can be changed independently of each other. It is a design pattern of the so-called GoF pattern (see Gang of Four ).
problem
Usually an implementation is done by inheriting the abstraction. However, this can lead to both implementations and other abstract classes being found in the inheritance hierarchy. This makes the inheritance hierarchy cluttered and difficult to maintain.
solution
If the abstract classes and the implementations are managed in two different hierarchies, firstly the clarity gains and secondly the application becomes independent of the implementation.
On the left is the hierarchy of the abstractions (List and SortedList). On the right is the hierarchy of the implementations (ListImpl and ArrayList).
General use
A bridge applies when
- Both abstraction and implementation should be expandable and a permanent connection between abstraction and implementation should be prevented,
- Changes to the implementation should have no impact on the client,
- the implementation should remain hidden from the client, or
- the implementation should be used by different classes at the same time.
In practice it often happens that the abstraction side is not subdivided as finely as the implementation side. One speaks of a degenerate bridge .
UML diagram
actors
The abstraction (in the example:) List
on the one hand defines the interface of the abstraction, on the other hand it holds a reference to an implementer. The SpezAbstraktion
(in the example:) SortedList
extends the interface. The implementer (in the example:) ListImpl
defines the interface of the implementation. It can differ considerably from the interface of the abstraction. The KonkrImplementierer
(in the example:) ArrayList
contains a concrete implementation by implementing the interface.
advantages
The advantages of a bridge are that abstraction and implementation are decoupled. The implementation can also be changed dynamically during runtime and the extensibility of abstraction and implementation is improved.
The implementation can be selected by specifying a parameter when generating an abstraction, and the implementation is completely hidden from the client. A large increase in the number of classes can be avoided.
Code examples
Java
abstract class Printer {
protected PrintingImpl impl;
public Printer(PrintingImpl impl) {
this.impl = impl;
}
public abstract void print();
public PrintingImpl getImpl() {
return impl;
}
public void setImpl(PrintingImpl impl) {
this.impl = impl;
}
}
class APrinter extends Printer {
public APrinter(PrintingImpl impl) {
super(impl);
}
@Override
public void print() {
impl.print("A");
}
}
class BPrinter extends Printer {
public BPrinter(PrintingImpl impl) {
super(impl);
}
@Override
public void print() {
impl.print("B");
}
}
interface PrintingImpl {
public void print(String what);
}
class PlainTextPrintingImpl implements PrintingImpl {
@Override
public void print(String what) {
System.out.println(what);
}
}
class HTMLPrintingImpl implements PrintingImpl {
@Override
public void print(String what) {
System.out.println("<p>\n\t<em>" + what + "</em>\n</p>");
}
}
public class Main {
public static void main(String[] args) {
Printer printer;
PrintingImpl plainImpl = new PlainTextPrintingImpl();
PrintingImpl htmlImpl = new HTMLPrintingImpl();
printer = new APrinter(plainImpl);
printer.print();
/* Die PrintingImpl kann problemlos zur Laufzeit ausgetauscht
* werden, da die Implementierung von der Abstraktion
* entkoppelt ist. */
printer.setImpl(htmlImpl);
printer.print();
/* Genauso kann (ähnlich wie beim Strategy-Pattern) der
* Printer selbst zur Laufzeit geändert werden. */
printer = new BPrinter(plainImpl);
printer.print();
printer.setImpl(htmlImpl);
printer.print();
}
}
Output:
A
<p>
<em>A</em>
</p>
B
<p>
<em>B</em>
</p>
Ruby
class Abstraction
def initialize(implementor)
@implementor = implementor
end
def operation
raise 'Implementor-Objekt antwortet nicht auf die operation-Methode'
unless @implementor.respond_to?(:operation)
@implementor.operation
end
end
class RefinedAbstraction < Abstraction
def operation
puts 'Starte Vorgang...'
super
end
end
class Implementor
def operation
puts 'Wichtige Schritte ausführen'
end
end
class ConcreteImplementorA < Implementor
def operation
super
puts 'Zusätzliche Schritte ausführen'
end
end
class ConcreteImplementorB < Implementor
def operation
super
puts 'Andere, zusätzliche Schritte ausführen'
end
end
normal_with_a = Abstraction.new(ConcreteImplementorA.new)
normal_with_a.operation
# Wichtige Schritte ausführen
# Zusätzliche Schritte ausführen
normal_with_b = Abstraction.new(ConcreteImplementorB.new)
normal_with_b.operation
# Wichtige Schritte ausführen
# Andere, zusätzliche Schritte ausführen
refined_with_a = RefinedAbstraction.new(ConcreteImplementorA.new)
refined_with_a.operation
# Starte Vorgang...
# Wichtige Schritte ausführen
# Zusätzliche Schritte ausführen
refined_with_b = RefinedAbstraction.new(ConcreteImplementorB.new)
refined_with_b.operation
# Starte Vorgang...
# Wichtige Schritte ausführen
# Andere, zusätzliche Schritte ausführen
Related design patterns
An abstract factory can be used to generate the implementation object of the bridge .
An adapter is apparently similar to the bridge. However, the adapter is used to subsequently adapt a class to an interface, while the bridge is a targeted decision for decoupling. Both design patterns are thus opposite, but can look very similar in their implementation.
Individual evidence
- ↑ Erich Gamma , Richard Helm , Ralph Johnson , John Vlissides : Design pattern . 5th edition. Addison-Wesley , 1996, ISBN 3-8273-1862-9 , pp. 186 .