Có thể khi đọc tiêu đề bài viết này, nhiều bạn bỡ ngỡ, giật mình vì đây là lần đầu tiên bạn gặp những từ ngữ và những khái niệm như này.

MutableImmutable Objects không phải là 1 khái niệm mới hay 1 kỹ thuật lập trình OOP gì đó cao siêu cả. Nó hoàn toàn cơ bản, và mọi người gọi chúng là Mutable Objects hay Immutable Objects để làm rõ đặc điểm cũng như tính năng của đối tượng đó có những gì và không có những gì.

Mutable Immutable Objects khá gần gũi và quen thuộc với bạn trong quá trình bạn viết các chương trình Java hay OOP nói chung.

Vậy Mutable (trạng thái - thay đổi được)Immutable (trạng thái - không thay đổi được) Objects là gì?

Đơn thuần, các đối tượng dạng Mutable và Immutable đều là những Class mà bạn tạo ra hay những Class dạng này được cung cấp sẵn trong Java. Nhưng để phân biệt các đối tượng Mutable và Immutable không phải dựa vào tên Class, hay tính thừa kế hoặc những cấu trúc đối tượng phức tạp mà dựa vào đặc điểm cũng như hành vi của đối tượng đó bằng các cặp phương thức Getter/Setter.

Tóm lược lại, tôi rút ra những khái niệm về Mutable và Immutable Object như sau:
- Mutable Object: khi khởi tạo 1 đối tượng, tức ta có 1 tham chiếu tới 1 thể hiện của 1 lớp, thì trạng thái của đối tượng có thể thay đổi được sau khi việc khởi tạo đối tượng thành công. (Trạng thái đối tượng ở đây có thể là các trường thông tin mà đối tượng đó nắm giữ. Ví dụ: tên, tuổi của 1 đối tượng sinh viên chẳng hạn)
- Immutable Object: khi khởi tạo 1 đối tượng, thì trạng thái của tối tượng đó không thể thay đổi được sau khi việc khởi tạo đối tượng thành công. (Điều này có nghĩa là, bạn chỉ có thể get mà không thể set).

1. Xây dựng Mutable Class:

/**
 *
 * @author Code4LifeVn
 */
public class MutableClass {

    private String first_name;
    private String last_name;

    public MutableClass(String first_name, String last_name) {
        this.first_name = first_name;
        this.last_name = last_name;
    }

    //Mutator
    public String getFirstName() {
        return first_name;
    }

    public void setFirstName(String first_name) {
        this.first_name = first_name;
    }

    public String getLastName() {
        return last_name;
    }

    public void setLastName(String last_name) {
        this.last_name = last_name;
    }
}

2. Xây dựng Immutable Class:

import java.util.Date;
/**
 *
 * @author Code4LifeVn
 */
public final class ImmutableClass {

    private String first_name;
    private String last_name;
    private Date dateOfBirth;

    public ImmutableClass(String first_name, String last_name, Date dob) {
        this.first_name = first_name;
        this.last_name = last_name;
        dateOfBirth = dob;
    }

    public String getFirstName() {
        return first_name;
    }

    public String getLastName() {
        return last_name;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }
}

Class StudentRunner:
import java.util.Date;
/**
 *
 * @author Code4LifeVn
 */
public class StudentRunner {
    public static void main(String[] args) {
        Date myDOB = new Date();
        ImmutableClass student = new ImmutableClass("Manh", "Do", myDOB);
    }
}

3. Khắc phục sự cố trong Immutable Class:

Ở ví dụ trên, myDOB là 1 đối tượng Mutable, mặc dù trong ImmutableClass mà chúng ta thiết kế chỉ cung cấp phương thức getDateOfBirth để trả về 1 đối tượng Date chứa thông tin ngày sinh của sinh viên. Vì lí do rằng Date là 1 Mutable Class, nó có chứa các phương thức get/set để thay đổi trạng thái, dữ liệu vì vậy dù ta không cung cấp phương thức setDateOfBirth nhưng vẫn thay đổi được giá trị DateOfBirth bởi đối tượng student giữ 1 tham chiếu tới đối tượng myDOB. Tôi sẽ chỉnh sửa lại code như sau để chứng minh điều này:

StudentRunner sau khi sửa:
import java.util.Date;
/**
 *
 * @author Code4LifeVn
 */

public class StudentRunner {

    public static void main(String[] args) {
        Date myDOB = new Date();
        
        ImmutableClass student = new ImmutableClass("Manh", "Do", myDOB);
        
        System.out.println(student.getDateOfBirth());
        
        myDOB.setMonth(myDOB.getMonth() + 2);
        
        System.out.println(student.getDateOfBirth());
        
}

Output:
Wed Apr 03 01:02:42 ICT 2013
Mon Jun 03 01:02:42 ICT 2013

Như bạn thấy ngày sinh của sinh viên đã bị thay đổi, điều này là vi phạm quy tắc thiết kế Immutable Class. Do đó, ta phải khắc phục sự cố này bằng cách sau:

Class ImmutableClass được sửa lại tốt hơn:
import java.util.Date;
/**
 *
 * @author Code4LifeVn
 */
public final class ImmutableClass {

    private String first_name;
    private String last_name;
    private Date dateOfBirth;

    public ImmutableClass(String first_name, String last_name, Date dob) {
        this.first_name = first_name;
        this.last_name = last_name;
        dateOfBirth = new Date(dob.getTime()); //Sao chép ra 1 đối tượng mới hơn là tham chiếu tới chính đối tượng truyền vào
    }

    public String getFirstName() {
        return first_name;
    }

    public String getLastName() {
        return last_name;
    }

    public Date getDateOfBirth() {
        return new Date(dateOfBirth.getTime()); //The same ^^
    }
}

Class StudentRunner được sửa lại tốt hơn:
import java.util.Date;

/**
 *
 * @author Code4LifeVn
 */
public class StudentRunner {
    public static void main(String[] args) {
        ImmutableClass student = new ImmutableClass("Do", "Manh", new Date());

        System.out.println(student.getDateOfBirth());

        Date myDOB = student.getDateOfBirth();

        myDOB.setMonth(myDOB.getMonth() + 2);

        System.out.println(student.getDateOfBirth());
    }
}
Output:
Wed Apr 03 01:02:42 ICT 2013
Wed Apr 03 01:02:42 ICT 2013

4. Tiêu chí thiết kế Immutable Class:


  1. Thiết kế tất cả các trường thuộc tính với phạm vi private.
  2. Không cung cấp các phương thức setter cho các trường thuộc tính
  3. Đảm bảo rằng phương thức không thể bị override bằng cách đặt final class (mạnh) hoặc đặt final method (yếu).
  4. Nếu 1 trường thuộc tính không phải là kiểu dữ liệu nguyên thủy hay không phải là Immutable, bạn hãy áp dụng cách khắc phục như ở trên, sao chép đối tượng dựa trên tham số truyền vào thay vì phụ thuộc vào nó.


5. Mutable và Immutable Objects bạn có thể bắt gặp ở đâu?

Trong Java Framework có rất nhiều lớp dạng Mutable và Immutable, tôi đơn cử vài lớp cơ bản để bạn dễ nắm bắt như các lớp Long, Integer, Double, Byte, Character, Float, Boolean, Short trong package java.lang là những lớp Immutable. Lớp Date như ví dụ ở trên là 1 lớp Mutable chẳng hạn. Bạn sẽ còn bắt gặp rất nhiều những lớp như này trong quá trình làm việc. Hy vọng, qua bài viết này bạn đã hiểu và nắm được khái niệm về Mutable / Immutable Objects cũng như kỹ thuật triển khai nó, và có được 1 Glossary tốt khi làm việc với đồng nghiệp khác.

6. Làm việc với các đối tượng Mutable trong Apache Commons Lang.

Đây là 1 phần Bonus khi đề cập tới các khái niệm về Mutable và Immutable Objects mà tôi muốn đưa vào giới thiệu với bạn để tăng tốc độ x2 kiến thức đạt được.

Như ở mục 5, tôi đã trích dẫn ra được 1 vài lớp cơ bản mà chúng ta hay dùng là Long, Integer....những lớp này là những lớp Immutable. Trong quá trình làm việc lâu dài và cần tăng tốc độ code để giải quyết 1 vài vấn đề nào đó về dữ liệu thì tôi sử dụng bộ thư viện Apache Commons, bộ thư viện này có rất nhiều Components. Trong bài này tôi sử dụng Apache Commons Lang để sử dụng những lớp tiện ích mở rộng cho  package java.lang - những lớp mà nó không có hay chưa thực sự hỗ trợ tốt cho chúng ta.

Bạn có thể tải bộ Apache Commons ở đây: http://commons.apache.org/

Trong trang này, bạn có thể thấy rất nhiều Components, hãy chọn và chỉ download về component Lang, đừng ham download hết những cái chưa thực sự cần bây giờ. Tôi sẽ cố gắng làm những ví dụ hay, thú vị và thực tế về những component khác. Tạm thời, chỉ cần Apache Commons Lang.

Quay trở lại vấn đề chính, những lớp Immutable thì không thể thay đổi trạng thái của nó được. Vì thế, trong Apache Commons Lang cung cấp cho chúng ta package org.apache.commons.lang3.mutable. Package này được dùng để khắc phục những hạn chế trong gói java.lang, cụ thể ở đây là mở rộng tính năng của các lớp Immutable cơ bản: Long, Short, Integer....thành MutableLong, MutableShort, MutableInt. Bạn có thể thấy trong hình sau:

Thực chất những lớp này được implements các Core Interface (POJI) trong Java và viết lại các phương thức giúp chúng ta khắc phục nhược điểm của các đối tượng cũ.

Vậy, những lớp Mutable trong package này sẽ giúp ta làm những gì và thực sự có giúp ta tăng tốc code hay không? Tôi sẽ viết thử 1 chương trình nho nhỏ, và bạn hãy gõ lại rồi kiểm chứng điều này.

Giả sử bình thường, tôi có 1 phương thức set số tiền muốn gửi vào ngân hàng, tôi làm như sau:

Class MutableBank
/**
 *
 * @author Code4LifeVn
 */
public class MutableBank {
    private double balance;

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    
}

Class BankRunner
/**
 *
 * @author Code4LifeVn
 */
public class BankRunner {
    public static void main(String[] args) {
        MutableBank bank = new MutableBank();
        bank.setBalance(5000.0);
        System.out.println(bank.getBalance());
    }
}


OK, mọi chuyện diễn ra suôn sẻ và không có gì rắc rối cả. Giả sử bây giờ, tôi muốn gửi thêm 1000.0đ nữa. Tức là 15000.0đ. Bạn sẽ làm thế nào?? Tôi sẽ làm thế này:
/**
 *
 * @author Code4LifeVn
 */
public class BankRunner {
    public static void main(String[] args) {
        MutableBank bank = new MutableBank();
        
        bank.setBalance(5000.0);
        
        double newBalance = bank.getBalance() + 10000.0;
        
        bank.setBalance(newBalance);
        //hoặc
        //bank.setBalance(bank.getBalance() + 10000.0);
        
        System.out.println(bank.getBalance());
    }
}


Như bạn thấy, giải quyết được vấn đề, dù cách trên hay cách dưới đều set số tiền lên 15000.0đ. Nhưng...khá tốn sức khi phải làm công việc getBalance cũ rồi cộng thêm tiền mới vào. Ví dụ đơn giản thì nó dễ, nhưng làm những dự án với số lượng data phức tạp thì việc này có thể gây ra những Bugs to đùng mà phải mất hàng giờ để ngồi sửa. Vì vậy, tôi sửa lại như sau, cách thức mà nó làm việc như lấy ra 1 con trỏ và xử lý dữ liệu trên con trỏ đó:

Class MutableBank:
import org.apache.commons.lang3.mutable.MutableDouble;

/**
 *
 * @author Code4LifeVn
 */
public class MutableBank {
    private MutableDouble balance;

    public MutableBank() {
        balance = new MutableDouble();
    }

    public MutableDouble getBalance() {
        return balance;
    }

    public void addBalance(double balance) {
        this.balance.add(balance);
    }

}

Class MutableBankRunner
/**
 *
 * @author Code4LifeVn
 */
public class MutableBankRunner {
    public static void main(String[] args) {
        MutableBank bank = new MutableBank();
        
        bank.addBalance(5000.0);
        
        System.out.println(bank.getBalance());
        
        bank.addBalance(10000.0);
        
        System.out.println(bank.getBalance());
    }
}


Bạn thấy 2 đoạn mã khác hẳn nhau sau khi đã được "nâng cấp" 1 chút. Phần logic của chương trình không bị rắc rối, loằng ngoằng như trước nữa mà rất ngắn gọn và rõ ràng.

Các lớp khác chức năng tương tự...Hy vọng bạn học tốt bài viết này!

0 nhận xét:

Đăng nhận xét

 

code4lifevn team

Thanh niên nghiêm túc :)

Name: Manh Do

Age: years old

Job: Senior Java and Mobile Developer

Country: VietNam

Name: Hung Nguyen

Age: years old

Job: Android Developer

Country: VietNam

Name: Trung PH

Age: years old

Job: Senior iOS and Android Developer

Country: VietNam

Name: Điệp NT

Age: years old

Job: Senior .Net and Android Developer

Country: VietNam