Bridge Pattern
Giới thiệu
Định nghĩa Pattern
Bridge Pattern là một mẫu thiết kế thuộc loại cấu trúc. Nó giúp tách rời abstraction (lớp trừu tượng) và implementation (lớp thực thi) ra khỏi nhau, giúp cả hai có thể thay đổi và phát triển một cách độc lập, không làm ảnh hưởng đến nhau.
Mục đích
- Tách biệt abstraction khỏi implementation để cả hai có thể thay đổi mà không ảnh hưởng đến nhau.
- Khắc phục vấn đề kết hợp giữa lớp cha và lớp con, giúp giảm sự phức tạp trong thiết kế và giúp mở rộng mã nguồn một cách linh hoạt hơn.
- Cung cấp khả năng thay thế và tái sử dụng implementation mà không cần sửa đổi code của lớp trừu tượng.
Ý tưởng chính của Pattern
Ý tưởng chính của Bridge Pattern là sử dụng “cầu nối” giữa abstraction và implementation. Thay vì một lớp trừu tượng kiểm soát và mở rộng từ một lớp cụ thể (lớp thực thi), mẫu thiết kế này đề xuất việc tạo ra một interface (cầu nối) giữa chúng. Khi cần mở rộng chức năng, bạn có thể thêm vào phía abstraction mà không ảnh hưởng đến phía implementation và ngược lại. Nhờ có “cầu nối” này, việc thay đổi và phát triển mã nguồn trở nên linh hoạt và ít gặp rủi ro hơn.
Đặt vấn đề
Hãy tưởng tượng bạn có một lớp Vehicle
với hai subclass là BicycleBike
và MotorBike
. Bây giờ, bạn muốn mở rộng tính năng bằng cách thêm màu sắc cho mỗi loại phương tiện, và bạn tạo ra hai thuộc tính là Red
và Blue
. Với cách tiếp cận này, bạn sẽ phải tạo ra tổng cộng bốn lớp con như BlueBicycleBike
và RedMotorBike
.
classDiagram
class Vehicle {
}
class BicycleBike {
}
class MotorBike {
}
class Red {
}
class Blue {
}
class BlueBicycleBike {
}
class RedBicycleBike {
}
class BlueMotorBike {
}
class RedMotorBike {
}
Vehicle <|-- BicycleBike
Vehicle <|-- MotorBike
BicycleBike <|-- BlueBicycleBike
BicycleBike <|-- RedBicycleBike
MotorBike <|-- BlueMotorBike
MotorBike <|-- RedMotorBike
Red <--o BlueBicycleBike
Red <--o RedBicycleBike
Blue <--o BlueMotorBike
Blue <--o RedMotorBike
Khi bạn muốn thêm một loại phương tiện hoặc một màu sắc mới, bạn sẽ cần tạo thêm nhiều lớp con, làm cho hệ thống trở nên phức tạp và khó kiểm soát.
Giải pháp
Vấn đề ở đây là chúng ta đang cố gắng tích hợp quá nhiều tính năng vào một lớp trừu tượng, trong khi mỗi tính năng đều có tiềm năng phát triển theo hướng riêng biệt.
classDiagram
class Vehicle {
+Color color
+void manage()
}
class Color {
+void bePainted()
}
class BicycleBike {
+void manage()
}
class MotorBike {
+void manage()
}
class Red {
+void bePainted()
}
class Blue {
+void bePainted()
}
Vehicle <|-- BicycleBike: is a
Vehicle <|-- MotorBike: is a
Vehicle "1" *-- "1" Color: has a
Color <|-- Red: is a
Color <|-- Blue: is a
Bridge Pattern giải quyết vấn đề này bằng cách tách biệt lớp trừu tượng (abstraction) và các đối tượng thực thi (implementation), sau đó kết nối chúng lại với nhau thông qua một “cầu” (bridge). Trong ví dụ này, một phương tiện sẽ có một thuộc tính màu sắc, tạo ra mối quan hệ “has-a” (có một) thay vì “is-a” (là một).
Cấu Trúc
classDiagram
class Abstraction {
+Implementor implementor
+operation() void
}
class Implementor {
+operationImpl() void
}
class ConcreteImplementorA {
+operationImpl() void
}
class ConcreteImplementorB {
+operationImpl() void
}
class RefinedAbstraction {
+operation() void
}
Abstraction <|-- RefinedAbstraction: is a
Abstraction "1" *-- "1" Implementor: has a
Implementor <|-- ConcreteImplementorA: is a
Implementor <|-- ConcreteImplementorB: is a
- The Abstraction (
Abstraction
): Lớp cơ sở cung cấp mức độ trừu tượng cho việc quản lý các công việc do các đối tượng cụ thể thực hiện (Implementation). - The Implementation (
Implementor
): Giao diện định nghĩa các tác vụ cần thiết cho lớp trừu tượng. Đây thường là một giao diện xác định các tác vụ cần thiết cho lớp trừu tượng. - Concrete Implementations (
ConcreteImplementor
): Các lớp này chứa logic cụ thể và thực hiện các tác vụ được định nghĩa bởiImplementor
. - Refined Abstractions (
RefinedAbstraction
): Các lớp con củaAbstraction
thực hiện và mở rộng các phương thức được xác định trong lớpAbstraction
.
Cách triển khai
Bước 1: Xác định Abstraction và Implementation
Đầu tiên, ta cần phân biệt giữa abstractions (trừu tượng) và implementations (thực thi).
// Abstraction
public abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
public abstract void draw();
}
// Implementation
public interface Color {
void applyColor();
}
Bước 2: Mở rộng Abstraction
Chúng ta có thể mở rộng abstraction để tạo ra các phân lớp khác nhau.
public class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
public void draw() {
System.out.print("Draw Circle in ");
color.applyColor();
}
}
public class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
public void draw() {
System.out.print("Draw Square in ");
color.applyColor();
}
}
Bước 3: Cung cấp các Implementations cụ thể
public class RedColor implements Color {
@Override
public void applyColor() {
System.out.println("Red color.");
}
}
public class BlueColor implements Color {
@Override
public void applyColor() {
System.out.println("Blue color.");
}
}
Bước 4: Sử dụng Bridge Pattern
public class BridgePatternDemo {
public static void main(String[] args) {
Shape redCircle = new Circle(new RedColor());
Shape blueSquare = new Square(new BlueColor());
redCircle.draw();
blueSquare.draw();
}
}
Khi chạy đoạn mã trên, kết quả sẽ là:
Draw Circle in Red color.
Draw Square in Blue color.
Ví dụ áp dụng Bridge Pattern
Để hiểu rõ hơn về cách Bridge Pattern hoạt động, hãy xem xét ví dụ về một hệ thống ngân hàng cung cấp các loại tài khoản khác nhau:
Account.java
public interface Account {
void openAccount();
}
CheckingAccount.java
public class CheckingAccount implements Account{
@Override
public void openAccount() {
System.out.println("Opening a Checking Account!");
}
}
TutorialAccount.java
public class TutorialAccount implements Account{
@Override
public void openAccount() {
System.out.println("Please select your language");
}
}
Bank.java
public abstract class Bank {
protected Account account;
public Bank(Account account) {
this.account = account;
}
public abstract void openAccount();
}
MMBank.java
public class MMBank extends Bank {
public MMBank(Account account) {
super(account);
}
@Override
public void openAccount() {
System.out.println("Welcome to MMBank");
account.openAccount();
}
}
TPBank.java
public class TPBank
extends Bank {
public TPBank(Account account) {
super(account);
}
@Override
public void openAccount() {
System.out.println("Welcome to TPBank");
account.openAccount();
}
}
demo.java
public class demo {
public static void main(String[] args) {
Bank tpBank = new TPBank(new TutorialAccount());
tpBank.openAccount();
System.out.println();
Bank mmBank = new MMBank(new CheckingAccount());
mmBank.openAccount();
}
}
Kết quả khi chạy chương trình:
Welcome to TPBank
Please select your language
Welcome to MMBank
Opening a Checking Account!
Như bạn thấy trong ví dụ trên, Bank
là một lớp trừu tượng kết hợp với interface Account
. Các lớp cụ thể như TPBank
và MMBank
kế thừa từ lớp Bank
và quyết định cách họ muốn mở tài khoản dựa trên loại tài khoản cụ thể (như CheckingAccount
hoặc TutorialAccount
). Nhờ sử dụng Bridge Pattern, chúng ta có thể mở rộng cả hai hệ thống (loại ngân hàng và loại tài khoản) một cách độc lập mà không làm ảnh hưởng đến nhau.
So sánh
1. Bridge vs Adapter:
- Bridge: Như chúng ta đã biết, mục tiêu chính của Bridge Pattern là tách rời abstraction (trừu tượng) ra khỏi implementation (thực thi) của nó, giúp cho cả hai có thể thay đổi độc lập.
- Adapter: Mẫu thiết kế này cho phép các đối tượng với interfaces không tương thích có thể làm việc cùng nhau thông qua một lớp trung gian.
2. Bridge vs Composite:
- Bridge: Tách rời abstraction và implementation để chúng phát triển độc lập.
- Composite: Cung cấp một cách để bạn có thể làm việc với các đối tượng đơn lẻ hoặc với nhóm các đối tượng theo cùng một cách.
3. Bridge vs Decorator:
- Bridge: Phân tách interface (trừu tượng) và thực thi, cho phép chúng thay đổi độc lập.
- Decorator: Thêm các trách nhiệm cho đối tượng mà không cần sửa đổi chúng, giữ nguyên interface của đối tượng và thêm chức năng mở rộng.
4. Bridge vs Facade:
- Bridge: Đảm bảo tính linh hoạt giữa abstraction và implementation bằng cách tách chúng ra.
- Facade: Cung cấp một giao diện đơn giản hoá cho một hệ thống con, giúp giảm sự phức tạp khi giao tiếp với các hệ thống phức tạp.
5. Bridge vs Proxy:
- Bridge: Tập trung vào việc tách rời và linh hoạt giữa abstractions và implementations.
- Proxy: Cung cấp một đại diện cho một đối tượng khác, kiểm soát việc truy cập đến đối tượng gốc.
Kết luận
Bridge Pattern là một mẫu thiết kế hiệu quả giúp tách rời các khía cạnh trừu tượng và thực thi của một lớp, giúp chúng có thể thay đổi và phát triển một cách độc lập. Điều này không chỉ giúp giảm thiểu sự phức tạp khi mở rộng hệ thống mà còn cung cấp khả năng linh hoạt hơn trong việc quản lý và mở rộng code.