Poznaj wzorzec projektowy Bridge
Bridge (most) to strukturalny wzorzec projektowy, który pozwala podzielić dużą klasę lub zestaw spokrewnionych klas na dwie oddzielne hierarchie — abstrakcję i implementację, nad którymi można pracować niezależnie.
Wyobraź sobie, że masz klasę Shape z dwiema podklasami: Circle i Square. Hierarchia klas musi zostać rozszerzona o kolory, dlatego należy utworzyć podklasy Shape: Red i Blue. Ponieważ masz już dwie podklasy, będziesz musiał utworzyć cztery kombinacje klas, takie jak BlueCircle i RedCircle. Hierarchia będzie rosła wykładniczo po dodaniu nowych kształtów (Shape) i kolorów (Color).
Napotykamy ten problem, ponieważ próbujemy rozszerzyć klasy kształtów w dwóch wymiarach, kształtu i koloru.
Wzorzec projektowy Bridge
Wzorzec mostu próbuje rozwiązać ten problem poprzez zamianę dziedziczenia na kompozycję. Oznacza to, że wyodrębnia się jeden wymiar do osobnej hierarchii klas, dzięki czemu oryginalne klasy będą odwoływać się do obiektów w nowej hierarchii, a nie do całego stanu i zachowania zawartego w jednej klasie.
Diagram klas UML
Kroki implementacji
- Określ wymiary ortogonalne swoich klas. Te niezależne koncepcje to abstrakcja/platforma, domena/infrastruktura, front-end/back-end lub interfejs/implementacja.
- Dowiedz się, jakich operacji potrzebuje klient, i zdefiniuj je w podstawowej klasie abstrakcji.
- Określ, jakie operacje są dostępne na wszystkich platformach. Do ogólnego interfejsu implementacji należy włączyć te, które są potrzebne abstrakcji.
- Utwórz konkretne klasy implementujące dla wszystkich platform w domenie, ale upewnij się, że wszystkie są zgodne z interfejsem implementacji.
- Dodaj do klasy abstrakcji pole referencyjne dla typu implementacji. Abstrakcja przekazuje większość pracy obiektowi implementacyjnemu, do którego odwołuje się to pole.
- Twórz dopracowane abstrakcje dla każdego wariantu logiki wysokiego poziomu, rozszerzając podstawową klasę abstrakcji.
- Niezależny obiekt powinien zostać przekazany do konstruktora abstrakcji, aby powiązać jedno z drugim, po czym klient może zapomnieć o implementacji i pracować tylko z obiektem abstrakcji.
Implementacja kodu źródłowego
Klasa abstrakcyjna Shape zapewnia wysokopoziomową logikę kontroli. Rzeczywistą niskopoziomową pracę wykonuje obiekt Color.
package com.learncsdesign;
public abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
abstract public void fillColor();
}
Color definiuje interfejs, który jest wspólny dla wszystkich konkretnych implementacji RedColor i BlueColor. Shape może komunikować się z obiektem Color tylko za pomocą zadeklarowanych tutaj metod.
package com.learncsdesign;
public interface Color {
public void fillColor();
}
BlueColor jest konkretną implementacją interfejsu Color.
package com.learncsdesign;
public class BlueColor implements Color {
@Override
public void fillColor() {
System.out.println("Blue color");
System.out.println("--------------------------------------");
}
}
BlueColor jest konkretną implementacją interfejsu Color.
package com.learncsdesign;
public class RedColor implements Color {
@Override
public void fillColor() {
System.out.println("Red color");
System.out.println("--------------------------------------");
}
}
Klasa Circle rozszerza klasę Shape i wywołuje metodę Color.
package com.learncsdesign;
public class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
public void fillColor() {
System.out.println("Filling " + this.getClass());
color.fillColor();
}
}
Klasa Square rozszerza klasę Shape i wywołuje metodę Color.
package com.learncsdesign;
public class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
public void fillColor() {
System.out.println("Filling " + this.getClass());
color.fillColor();
}
}
BridgeClient działa tylko z klasą abstrakcyjną Shape. W tym przypadku obowiązkiem klienta jest powiązanie obiektu abstrakcji (Shape) z jednym z obiektów implementacji (Color).
package com.learncsdesign;
public class BridgeClient {
public static void main(String[] args) {
Shape circle = new Circle(new RedColor());
circle.fillColor();
Shape square = new Square(new BlueColor());
square.fillColor();
}
}
// Output
Filling class com.learncsdesign.Circle
Red color
--------------------------------------
Filling class com.learncsdesign.Square
Blue color
Kiedy stosować Bridge:
Jeśli chcesz podzielić i uporządkować monolityczną klasę, która ma kilka wariantów pewnej funkcjonalności, zastosuj wzorzec Bridge. Używając go podzielisz klasę monolityczną na kilka klas hierarchicznych, a dzięki temu będziesz w stanie zmieniać klasy w każdej hierarchii niezależnie od pozostałych. Upraszcza to utrzymanie kodu i minimalizuje ryzyko uszkodzenia istniejącego.
Wzorzec ten może być stosowany, kiedy występuje potrzeba rozszerzenia klasy w kilku ortogonalnych (niezależnych) wymiarach.
Jeśli potrzebujesz przełączyć się między implementacjami w runtime’ie, dobrze jest zastosować most. Jest to opcjonalne, ale wzorzec ten pozwala na zastąpienie obiektu implementacji wewnątrz abstrakcji. Przy okazji, ten ostatni element jest głównym powodem, dla którego tak wiele osób myli wzorzec Bridge ze wzorcem Strategii.
Zalety wzorca projektowego Bridge
- Można tworzyć aplikacje i klasy, które są niezależne od platformy.
- Kod klienta jest oparty na wysokopoziomowych abstrakcjach.
- Można niezależnie wprowadzać nowe abstrakcje i implementacje.
- Możliwe jest skupienie się na wysokopoziomowej logice w abstrakcji i na szczegółach platformy w implementacji.
Źródła — książki
- Wzorce projektowe: Elementy oprogramowania obiektowego wielokrotnego użytku,
- Rusz głową: Wzorce projektowe,
- Wzorce projektowe: Nowoczesny podręcznik.
Oryginał tekstu w języku angielskim przeczytasz tutaj.