Java: Đối tượng bất biến


Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên

Một đối tượng được coi là bất biến nếu trạng thái của nó không thể thay đổi sau khi nó được xây dựng. Sự phụ thuộc tối đa trên đối tượng bất biến được chấp nhận rộng rãi như là một chiến lược hợp lý để tạo mã lệnh đơn giản và đáng tin cậy.

Đối tượng bất biến đặc biệt hữu ích trong các ứng dụng đồng thời. Vì không thể thay đổi trạng thái, nên đối tượng bất biến không thể bị lỗi do sự giao thoa luồng hoặc không thể bị phát hiện trong một trạng thái không phù hợp.

Các lập trình viên thường không muốn sử dụng đối tượng bất biến, bởi vì họ lo lắng về chi phí của việc tạo ra một đối tượng mới trái ngược với việc cập nhật một đối tượng tại chỗ. Tác động của việc tạo đối tượng thường được đánh giá quá cao, và có thể được bù đắp bởi một số các kết hợp hiệu quả với đối tượng bất biến. Chúng bao gồm giảm chi phí do thu dọn rác, và loại bỏ các mã lệnh cần thiết để bảo vệ các đối tượng có thể thay đổi do lỗi.

Các phần dưới đây sẽ trình bày một lớp có thể hiện có thể thay đổi và dẫn xuất từ một lớp mà có các thể hiện không thay đổi. Trong quá trình thực thi, chúng đưa ra những quy tắc chung cho các loại chuyển đổi và thể hiện một số trong những lợi thế của các đối tượng không thay đổi.

Ví dụ về lớp đồng bộ hóa

Lớp SynchronizedRGB định nghĩa các đối tượng đại diện cho màu sắc (color). Mỗi đối tượng đại diện cho màu sắc gồm ba số nguyên thể hiện ba giá trị màu cơ bản và một chuỗi đại diện cho tên của màu.

public class SynchronizedRGB {

    // Values must be between 0 and 255.
    private int red;
    private int green;
    private int blue;
    private String name;

    private void check(int red,
                       int green,
                       int blue) {
        if (red < 0 || red > 255
            || green < 0 || green > 255
            || blue < 0 || blue > 255) {
            throw new IllegalArgumentException();
        }
    }

    public SynchronizedRGB(int red,
                           int green,
                           int blue,
                           String name) {
        check(red, green, blue);
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.name = name;
    }

    public void set(int red,
                    int green,
                    int blue,
                    String name) {
        check(red, green, blue);
        synchronized (this) {
            this.red = red;
            this.green = green;
            this.blue = blue;
            this.name = name;
        }
    }

    public synchronized int getRGB() {
        return ((red << 16) | (green << 8) | blue);
    }

    public synchronized String getName() {
        return name;
    }

    public synchronized void invert() {
        red = 255 - red;
        green = 255 - green;
        blue = 255 - blue;
        name = "Inverse of " + name;
    }
}

Lớp SynchronizedRGB phải được sử dụng một cách cẩn thận để tránh bị phát hiện trạng thái không phù hợp. Giả sử có một luồng thực thi đoạn mã sau:

SynchronizedRGB color =
    new SynchronizedRGB(0, 0, 0, "Pitch Black");
...
int myColorInt = color.getRGB();      //Statement 1
String myColorName = color.getName(); //Statement 2

Nếu có một luồng khác gọi color.set sau câu lệnh Statement 1 nhưng trước câu lệnh Statement 2 thì giá trị của myColorInt sẽ không phù hợp với giá trị của myColorName. Để tránh điều này, hai câu lệnh phải được gắn kết với nhau:

synchronized (color) {
    int myColorInt = color.getRGB();
    String myColorName = color.getName();
}

Dạng không thống nhất chỉ có thể áp dụng cho các đối tượng có thể thay đổi - nó sẽ không phải là một vấn đề đối với các phiên bản bất biến của lớp SynchronizedRGB.

Chiến lược cho Xác định đối tượng bất biến

Các quy tắc sau đây xác định một chiến lược đơn giản để tạo đối tượng bất biến. Không phải tất cả các lớp đều sẽ là "bất biến" khi áp dụng theo quy tắc này. Điều này không nhất thiết có nghĩa là người tạo ra các lớp này là cẩu thả - họ có thể có lý do để tin rằng các thể hiện của lớp của họ không bao giờ thay đổi sau khi xây dựng. Tuy nhiên, chiến lược này đòi hỏi phải phân tích sâu và không dành cho người mới học Java.

  1. Không có các phương thức setter.

  2. Tất cả các trường của lớp đều phải là final và private.

  3. Không cho phép các lớp con ghi đè các phương thức. Cách đơn giản nhất để làm điều này khai báo lớp final. Một cách khác phức tạp hơn là xây dựng các hàm tạo private và xây dựng các thể hiện của lớp bên trong các phương thức factory.

  4. Nếu các thể hiện của lớp bao gồm cả các tham chiếu tới các đối tượng có thể thay đổi thì không cho phép các đối tượng này được thay đổi:

    • Không cung cấp các phương thức để sửa các đối tượng có thể thay đổi.

    • Không dùng chung các tham chiếu với các đối tượng có thể thay đổi. Không bao giờ lưu trữ tham chiếu ra bên ngoài, vì các đối tượng có thể thay đổi sẽ truyền tới hàm tạo; nếu cần thiết bạn hãy tạo các bản sao và lưu các tham chiếu vào những bản sao này. Tương tự như vậy, hãy tạo các bản sao của các đối tượng có thể thay đổi nội bộ khi cần thiết để tránh trả về bản gốc bằng các phương thức.

Áp dụng chiến lược trên cho lớp SynchronizedRGB ở trên, ta thấy những những điều sau:

  1. Có hai phương thức setter có trong lớp này, phương thức thứ nhất là set, sẽ tự ý chuyển thành đối tượng, và nó không được đặt trong phiên bản bất biến của lớp; phương thức thứ hai là invert có thể được điều chỉnh bằng cách để nó tạo ra một đối tượng mới thay vì sửa đối tượng hiện có.

  2. Tất cả các trường đều đã private, do vậy ta chỉ cần thiết lập thêm final cho chúng.

  3. Bản thân lớp cần được khai báo là final.

  4. Chỉ có một trường của lớp là tham chiếu đến một đối tượng, và đối tượng này bản thân nó là không thay đổi. Vì vậy, ta không cần có biện pháp bảo vệ chống lại sự thay đổi trạng thái "đã chứa" của các đối tượng có thể thay đổi.

Từ đó, ta có lớp ImmutableRGB như sau:

final public class ImmutableRGB {

    // Values must be between 0 and 255.
    final private int red;
    final private int green;
    final private int blue;
    final private String name;

    private void check(int red,
                       int green,
                       int blue) {
        if (red < 0 || red > 255
            || green < 0 || green > 255
            || blue < 0 || blue > 255) {
            throw new IllegalArgumentException();
        }
    }

    public ImmutableRGB(int red,
                        int green,
                        int blue,
                        String name) {
        check(red, green, blue);
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.name = name;
    }

    public int getRGB() {
        return ((red << 16) | (green << 8) | blue);
    }

    public String getName() {
        return name;
    }

    public ImmutableRGB invert() {
        return new ImmutableRGB(255 - red,
                       255 - green,
                       255 - blue,
                       "Inverse of " + name);
    }
}

» Tiếp: Đối tượng Lock
« Trước: Khối bảo vệ
Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên
Copied !!!