[JAVA] Hiểu khái niệm Immutable như thế nào cho đúng?

22 tháng 03, 2017 – 3729 lượt xemKhái niệm về immutable luôn chiếm một phần quan trọng trong nhiều ngôn từ lập trình ngày này, Java không phải là ngoại lệ. Java 8 sinh ra kèm theo functional programming và java.time. API khiến immutable càng trở nên quan trọng hơn .

#1 Immutable là gì?

Nếu định nghĩa một cách ngắn gọn, ta có immutable là một đặc tính của một đối tượng nào đó trong lập trình.

Ví dụ đơn cử với class. Một class được xếp loại là immutable class nếu những bản thể ( instance ) mà nó tạo ra không hề đổi khác được. Cụ thể, thông tin lưu trong object đó được gán ngay trong quy trình khởi tạo object, kể từ đó, ta không hề biến hóa thông tin cho object này. Nếu ta cần object đó mang giá trị khác, ta buộc phải làm một object mới .

#2 Ưu điểm của tính immutable

Nếu bạn chưa từng thao tác hoặc chưa nghe đến immutable, có lẽ rằng bạn sẽ nghĩ nó là tính năng thừa. Tuy nhiên immutable mang đến rất nhiều lơị ích cho mạng lưới hệ thống. Cụ thể như sau :

Thứ nhất: hỗ trợ xây dựng hệ thống ổn định. Ưu điểm này đến từ chính đặc tính vốn có của Immutable là không thể thay đổi giá trị sau khi khởi tạo .

Lấy ví dụ về class Bank, đại diện thay mặt cho một ngân hàng nhà nước. Ta xét thực trạng sau : khi cuộc khủng hoảng cục bộ kinh tế tài chính trôi qua, nhà băng không được cho phép thông tin tài khoản người dùng có thẻ có giá trị số dư là âm. Để làm được điều này, người ta sẽ thêm một method kiểm tra và những luật sao cho khi có thông tin tài khoản bị âm, nó sẽ bắn ra IllegalArgumentException. Kiểu luật này gọi là invariant – luật không bao giờ thay đổi .

public class BankAccount{

[...]

    private void validate(long balance) {

        if (balance < 0) {

            throw new IllegalArgumentException("balance must not be negative:"+ balance);

        }

    }

}

Trong một class thường thì, hàm validedate ( ) hoàn toàn có thể được gọi bất kỳ thời gian nào nếu có xảy ra biến hóa số dư thông tin tài khoản. Tuy nhiên với immutable class, ta chỉ cần gọi validate ( ) một lần duy nhất trong constructor .

public BankAccount(long balance) {

    validate(balance);

    this.balance = balance;

}

Không thể biến hóa giá trị của Immutable object. Điều này đúng với một object kể từ lúc nó được khởi tạo cho đến khi bị " quét dọn " bởi Garbage Collector. Mỗi khi số dư thông tin tài khoản bị đổi khác, một object mới sẽ được tạo ra. Vì thế ta chỉ cần kiểm tra giá trị thông tin tài khoản tại thời gian object tương ứng được khởi tạo. Đồng nghĩa với gọi validate ( ) một lần duy nhất trong constructor. Từ đó, ta hoàn toàn có thể tập trung chuyên sâu những luật không bao giờ thay đổi trong ứng dụng mà ta đang thiết kế xây dựng và bảo vệ tính đồng điệu cho những object trong suốt vòng đời của chúng .

Thứ hai, tính immutable có thể được áp dụng cho các hệ thống đặc thù yêu cầu "khả năng chịu lỗi" (fault-tolerance).

Hãy tưởng tượng bạn cần rút tiền từ ngân hàng nhà nước. Tại thời gian mà thông tin tài khoản của bạn bị trừ đi số tiền mà ATM sắp nhả ra thì Open lỗi. Như vậy với normal class, bạn đã mất tiền. Nhưng với immutable class, lỗi sẽ được bắn ra kèm theo đó là thông tin tài khoản của bạn không hề đổi khác trừ khi bạn đã nhận được tiền .

public ImmutableAccount withdraw(long amount) {

    long newBalance = newBalance(amount);

    return new ImmutableAccount(newBalance);

}

private long newBalance(long amount) {

    // exception during balance calculation

}

Immutable object không khi nào rơi vào trạng thái phi đồng điệu ( inconsistence ), ngay cả khi xảy ra exception. Điều này góp thêm phần tăng tính không thay đổi cho mạng lưới hệ thống .

Thứ ba, tính immutable có thể được chia sẻ giữa các object.

Ví dụ : ta có một object kiểu Account, trong object Account, có thuộc tính kiểu Holder và Balance ( tất yếu chúng cũng là object ) .Khi ta copy object Account, ta hoàn toàn có thể để 2 bản thể đó sử dụng chung thuộc tính Balance. Và nếu ta biến hóa Balance ở một object Account, nó sẽ không ảnh hưởng tác động tới object Account còn lại. Object còn lại tạo một bản thể ( vẫn giữ tính Immutable ) của class Account và 2 object cũ - mới này không hề tương quan đến nhau nữa .Một Immutable object không cần copy constructor khi copy object. Immutable object hoàn toàn có thể được san sẻ tự do khi sử dụng thuật toán lock-free trong thiên nhiên và môi trường multithread .Cuối cùng, immutable object là một lựa chọn sáng giá khi sử dụng làm key của Map hoặc làm element của Set .

#3 Nhược điểm của tính Immutable

Nhược điểm lớn nhất của immutable là nó có rủi ro tiềm ẩn ảnh hưởng tác động xấu đi lên performance. Ta sẽ cần khởi tạo nhiều immutable object so với mutable object, và cứ mỗi object tạo ra thì tài nguyên dự trữ của mạng lưới hệ thống lại vơi đi một chút ít .

Vì vậy, để tránh vấn đề đó ảnh hưởng tới quá trình xây dựng ứng dụng, ta cần khảo sát kỹ các yếu tố như: loại thiết bị triển khai ứng dụng, đặc điểm phần cứng của thiết bị đó, kích thước của ứng dụng... Kết quả khảo sát tổng hợp từ các yếu tố này sẽ giúp bạn có quyết định đúng đắn về việc có nên sử dụng immutable object hay không.

#4 Áp dụng

Bây giờ tôi sẽ đưa ra một ví dụ để những bạn thấy tầm quan trọng của immutable trong thực tiễn và cách tạo ra immutable class .

    public String name;

    public Destination destination;

    public Spaceship(String name) {

        this.name = name;

        this.destination = Destination.NONE;

    }

    public Spaceship(String name, Destination destination) {

        this.name = name;

        this.destination = destination;

    }

    public Destination currentDestination() {

        return destination;

    }

    public Spaceship exploreGalaxy() {

        destination = Destination.OUTER_SPACE;

    }

[…]

}

Để hô biến một class từ Mutable thành Immutable, ta cần thực hienj 4 bước :

  1. Đặt tất cả các trường (field) thành private và final
  2. Loại bỏ các method làm thay đổi state của object
  3. Đảm bảo class hiện tại không thể extend được
  4. Đảm bảo truy cập độc quyền tới mutable field (các trường có tính chất mutable)

Lý thuyết là vậy, tất cả chúng ta hãy vận dụng nó vào ví dụ trên, từng bước một .Bước 1 :Thêm modifier là private giúp những trường không hề bị biến hóa bởi những class nằm ngoài class chứa nó .Thêm final nhằm mục đích mục tiêu ngăn những tác nhân bên ngoài gán giá trị cho những trường trong class này trải qua biến tham chiếu ( nếu có ai đó cố ý làm thế, tất yếu sẽ xảy ra lỗi )Bước 2 :Tiếp theo, ta cần " bảo vệ " những object state khỏi sự đổi khác .

public ImmutableSpaceship exploreGalaxy() {

    return new ImmutableSpaceship(name, Destination.OUTER_SPACE);

}

Bất kỳ trường nào mà ta không biến hóa thì giá trị của chúng đều hoàn toàn có thể được copy từ object hiện thời. Nếu có đổi khác, object mới sẽ được khởi tạo .Bước 3 :Để ngăn cản sự đổi khác của class, ta không được phép cho những class khác extend class hiện thời. Lý do : những class con hoàn toàn có thể Override những method ở class hiện thời và những method được Override đó trọn vẹn hoàn toàn có thể đổi khác object tạo bởi class hiện thời, ví dụ luôn :

public class EvilSpaceship extends Spaceship {

    [...]

    @Override

    public EvilSpaceship exploreGalaxy() {

        this.destination = Destination.OUTER_SPACE;

        return this;

    }

}

Để tránh điều này xảy ra, ta thêm từ khóa final vào phần khai báo class :

public final class Spaceship

Bước 4 :Bước ở đầu cuối này có vai trò ngăn ngừa những truy vấn trực tiếp tới mutable field. Cụ thể, tất cả chúng ta không nên return những tham chiếu trực tiếp tới Destination object. Thay vào đó, cần tạo một bản copy của mutable object và thao tác với nó .Với ví dụ trên, ta cần check sự hiện hữu của những tham chiếu tới Destination tại những public method và constructor ...

Ở method curentDestination, ra thấy object Destination được return - đây chính là vấn đề. Thay vì return tham chiếu thật, hãy tạo bản copy của Destination object và trả về reference trỏ đến bản copy.

public Destination currentDestination() {

    return new Destination(destination);

}
private ImmutableSpaceship(String name, Destination destination) {

    this.name = name;

    this.destination = new Destination(destination);

}

Sau 4 bước, ta thu được loại sản phẩm :

public final class ImmutableSpaceship {

    private final String name;

    private final Destination destination;

    public ImmutableSpaceship(String name) {

        this.name = name;

        this.destination = new Destination("NONE");

    }

    private ImmutableSpaceship(String name, Destination destination) {

        this.name = name;

        this.destination = new Destination(destination);

    }

    public Destination currentDestination() {

        return new Destination(destination);

    }

    public ImmutableSpaceship newDestination(Destination newDestination) {

        return new ImmutableSpaceship(this.name, newDestination);

    }

[…]

}

Xem thêm phần tranh luận tại đây .Techmaster via Dzone

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *