Hướng dẫn Java Design Pattern – Bridge

Một thành phần trong OOP thường có 2 phần: Phần trừu tượng (abstraction) định nghĩa các chức năng và phần thực thi (implementation) các chức năng được định nghĩa trong phần trừu tượng. Hai phần này liên hệ với nhau thông qua quan hệ kế thừa. Những thay đổi trong phần trừu tượng dẫn đến các thay đổi trong phần thực thi.

Bridge Pattern được sử dụng để tách thành phần trừu tượng (abstraction) và thành phần thực thi (implementation) riêng biệt. Do đó, các thành phần này có thể thay đổi một cách độc lập mà không ảnh hưởng đến các thành phần khác. Thay vì liên hệ với nhau bằng quan hệ kế thừa, hai thành phần này liên hệ với nhau thông qua quan hệ “chứa trong” (object composition).

1. Bridge Pattern là gì?

Decouple an abstraction from its implementation so that the two can vary independently.

Bridge Pattern là một trong những Pattern thuộc nhóm cấu trúc (Structural Pattern). Ý tưởng của nó là tách tính trừu tượng (abstraction) ra khỏi tính hiện thực (implementation) của nó. Từ đó có thể dễ dàng chỉnh sửa hoặc thay thế mà không làm ảnh hưởng đến những nơi có sử dụng lớp ban đầu.

Điều đó có nghĩa là, ban đầu chúng ta thiết kế một class với rất nhiều xử lý, bây giờ chúng ta không muốn để những xử lý đó trong class đó nữa. Vì thế, chúng ta sẽ tạo ra một class khác và move các xử lý đó qua class mới. Khi đó, trong lớp cũ sẽ giữ một đối tượng thuộc về lớp mới, và đối tượng này sẽ chịu trách nhiệm xử lý thay cho lớp ban đầu.

Bridge Pattern khá giống với mẫu Adapter Pattern ở chỗ là sẽ nhờ vào một lớp khác để thực hiện một số xử lý nào đó. Tuy nhiên, ý nghĩa và mục đích sử dụng của hai mẫu thiết kế này hoàn toàn khác nhau:

  • Adapter Pattern hay còn gọi là Wrapper pattern được dùng để biến đổi một class/ interface sang một dạng khác có thể sử dụng được. Adapter Pattern giúp các lớp không tương thích hoạt động cùng nhau mà bình thường là không thể.
  • Bridge Pattern được sử dụng được sử dụng để tách thành phần trừu tượng (abstraction) và thành phần thực thi (implementation) riêng biệt.
  • Adapter Pattern làm cho mọi thứ có thể hoạt động với nhau sau khi chúng đã được thiết kế (đã tồn tại). Bridge Pattern nên được thiết kế trước khi phát triển hệ thống để Abstraction và Implementation có thể thực hiện một cách độc lập.

2. Cài đặt Bridge Pattern như thế nào?

Một Bridge Pattern bao gồm các thành phần sau:

  • Client: đại diện cho khách hàng sử dụng các chức năng thông qua Abstraction.
  • Abstraction : định ra một abstract interface quản lý việc tham chiếu đến đối tượng hiện thực cụ thể (Implementor).
  • Refined Abstraction (AbstractionImpl) : hiện thực (implement) các phương thức đã được định ra trong Abstraction bằng cách sử dụng một tham chiếu đến một đối tượng của Implementer.
  • Implementor : định ra các interface cho các lớp hiện thực. Thông thường nó là interface định ra các tác vụ nào đó của Abstraction.
  • ConcreteImplementor : hiện thực Implementor interface.

Ví dụ:

Một hệ thống ngân hàng cung cấp các loại tài khoản khác nhau cho khách hàng, chẳng hạn: Checking account và Saving account. Chúng ta có sơ đồ như sau:

Với cách thiết kế như vậy, khi hệ thống cần cung cấp thêm một loại tài khoản khác, chúng ta phải tạo class mới cho tất cả các ngân hàng, số lượng class tăng lên rất nhiều.

Bây giờ, chúng ta sẽ sử dụng Bridge Pattern để tái cấu trúc lại hệ thống trên như sau:

Với cấu trúc mới như vậy, khi có thêm một loại tài khoản mới, chúng ta đơn chỉ việc thêm vào một implement mới cho Account, các thành phần khác của Bank không bị ảnh hưởng. Hoặc cần thêm một ngân hàng mới, chẳng hạn VietinBank chúng ta chỉ cần thêm implement mới cho Bank, các thành phần khác cũng không bị ảnh hưởng và số lượng class chỉ tăng lên 1.

Code cho chương trên như sau:

Account:

public interface Account {
    void openAccount();
}

CheckingAccount:

public class CheckingAccount implements Account {
 
    @Override
    public void openAccount() {
        System.out.println("Checking Account");
    }
}

SavingAccount:

public class SavingAccount implements Account {
 
    @Override
    public void openAccount() {
        System.out.println("Saving Account");
    }
}

Bank:

public abstract class Bank {
 
    protected Account account;
 
    public Bank(Account account) {
        this.account = account;
    }
 
    public abstract void openAccount();
}

VietcomBank:

public class VietcomBank extends Bank {
 
    public VietcomBank(Account account) {
        super(account);
    }
 
    @Override
    public void openAccount() {
        System.out.print("Open your account at VietcomBank is a ");
        account.openAccount();
    }
}

TPBank:

public class TPBank extends Bank {
 
    public TPBank(Account account) {
        super(account);
    }
 
    @Override
    public void openAccount() {
        System.out.print("Open your account at TPBank is a ");
        account.openAccount();
    }
}

Client:

public class Client {
 
    public static void main(String[] args) {
        Bank vietcomBank = new VietcomBank(new CheckingAccount());
        vietcomBank.openAccount();
 
        Bank tpBank = new TPBank(new CheckingAccount());
        tpBank.openAccount();
    }
}

Output của chương trình trên:

Open your account at VietcomBank is a Checking Account
Open your account at TPBank is a Checking Account

3. Lợi ích của Bridge Pattern là gì?

  • Giảm sự phục thuộc giữa abstraction và implementation (loose coupling): tính kế thừa trong OOP thường gắn chặt abstraction và implementation lúc build chương trình. Bridge Pattern có thể được dùng để cắt đứt sự phụ thuộc này và cho phép chúng ta chọn implementation phù hợp lúc runtime.
  • Giảm số lượng những lớp con không cần thiết: một số trường hợp sử dụng tính inheritance sẽ tăng số lượng subclass rất nhiều. Ví dụ: trường hợp chương trình view hình ảnh trên các hệ điều hành khác nhau, ta có 6 loại hình (JPG, PNG, GIF, BMP, JPEG, TIFF) và 3 hệ điều hành (Window, MacOS, Ubuntu). Sử dụng inheritance trong trường hợp này sẽ làm ta thiết kế 18 lớp: JpgWindow, PngWindow, GifWindow, …. Trong khi áp dụng Bridge sẽ giảm số lượng lớp xuống 9 lớp: 6 lớp ứng với từng implement của Image và 3 lớp ứng với từng hệ điều hành, mỗi hệ điều hành sẽ gồm một tham chiếu đến đối tượng Image cụ thể.
  • Code sẽ gọn gàn hơn và kích thước ứng dụng sẽ nhỏ hơn: do giảm được số class không cần thiết.
  • Dễ bảo trì hơn: các Abstraction và Implementation của nó sẽ dễ dàng thay đổi lúc runtime cũng như khi cần thay đổi thêm bớt trong tương lai.
  • Dễ dàng mở rộng về sau: thông thường các ứng dụng lớn thường yêu cầu chúng ta thêm module cho ứng dụng có sẵn nhưng không được sửa đổi framework/ứng dụng có sẵn vì các framework/ứng dụng đó có thể được công ty nâng cấp lên version mới. Bridge Pattern sẽ giúp chúng ta trong trường hợp này.
  • Cho phép ẩn các chi tiết implement từ client: do abstraction và implementation hoàn toàn độc lập nên chúng ta có thể thay đổi một thành phần mà không ảnh hưởng đến phía Client. Ví dụ, các lớp của chương trình view ảnh sẽ độc lập với thuật toán vẽ ảnh trong các implementation. Như vậy ta có thể update chương trình xem ảnh khi có một thuật toán vẽ ảnh mới mà không cần phải sửa đổi nhiều.

4. Sử dụng Bridge Pattern khi nào?

  • Khi bạn muốn tách ràng buộc giữa Abstraction và Implementation, để có thể dễ dàng mở rộng độc lập nhau.
  • Cả Abstraction và Implementation của chúng nên được mở rộng bằng subsclass.
  • Sử dụng ở những nơi mà những thay đổi được thực hiện trong implement không ảnh hưởng đến phía client.

Related posts:

Java Program to Describe the Representation of Graph using Adjacency Matrix
Java Program to Implement CountMinSketch
HashMap trong Java hoạt động như thế nào?
Java InputStream to String
Hướng dẫn sử dụng Java Annotation
Java Program to Generate N Number of Passwords of Length M Each
Java Program to Create a Minimal Set of All Edges Whose Addition will Convert it to a Strongly Conne...
Java Program to Implement Fisher-Yates Algorithm for Array Shuffling
Java – Write to File
Different Ways to Capture Java Heap Dumps
Creating a Web Application with Spring 5
Spring Boot - Internationalization
New Features in Java 14
Lớp lồng nhau trong java (Java inner class)
Converting between an Array and a List in Java
Java Program to Perform Postorder Non-Recursive Traversal of a Given Binary Tree
Java Program to Generate Randomized Sequence of Given Range of Numbers
Java Program to Implement Graph Coloring Algorithm
New in Spring Security OAuth2 – Verify Claims
Custom Error Pages with Spring MVC
Comparing Arrays in Java
Tìm hiểu về Web Service
Java Program to Implement Queue using Two Stacks
Spring Boot - Rest Controller Unit Test
@Order in Spring
Java Program to Implement Sorting of Less than 100 Numbers in O(n) Complexity
Generate Spring Boot REST Client with Swagger
Notify User of Login From New Device or Location
Hướng dẫn sử dụng luồng vào ra nhị phân trong Java
A Guide to Queries in Spring Data MongoDB
Java Program to Print the Kind of Rotation the AVL Tree is Undergoing
Guide to the Java TransferQueue