Java: Đóng gói (Encapsulation)
Tổng quan
Xét một tình huống trong đó bạn muốn học cách lái xe ôtô. Như một người học, bạn không cần phải hiểu xem ôtô làm việc thế nào, cấu tạo bình xăng thế nào, nguyên lý của việc rẽ phải rẽ trái ra làm sao, ... Như một người lái xe, bạn quan tâm đến việc khởi động và tắt xe cũng như cách cung cấp nhiên liệu cho xe. Ngoài ra, bạn có thể muốn tìm hiểu cách sử dụng cần số và hãm phanh để hiểu một cách đầy đủ về chiếc xe. Vì thế, để lái một ôtô, bạn chỉ cần quan tâm đến giao diện (interface) của nó, mà không cần biết về cách thức hoạt động của các thành phần hay bộ phận của xe.
Tương tự như vậy, trong lập trình hướng đối tượng, khái niệm che giấu đi những chi tiết của một đối tượng được thực hiện bằng cách sử dụng biện pháp Đóng gói (Encapsulation).
Đóng gói là một cơ chế liên kết mã lệnh và dữ liệu với nhau trong một lớp. Tất cả các chi tiết thực thi của một lớp không cần phải hiện diện so với các lớp khác cũng như các đối tượng mà sử dụng nó. Thay vào đó, chỉ những thông tin cần thiết thì mới hiện diện so với các thành phần khác của ứng dụng và phần còn lại có thể được ẩn đi. Điều này được thực hiện thông qua cơ chế đóng gói. Nói cách khác, có thể nói rằng trong các ngôn ngữ lập trình hướng đối tượng, đóng gói bao gồm các hoạt động bên trong của một đối tượng Java. Mục đích chính của việc che dấu trong một lớp là để giảm đi sự phức tạp trong phát triển phần mềm. Bằng cách che dấu những chi tiết thực thi về những gì được yêu cầu để thực hiện những hoạt động cần thiết trong lớp, việc sử dụng các hoạt động trở nên đơn giản hơn. Trong Java, việc che dấu được thực hiện bởi các bổ từ truy cập.
Đóng gói dữ liệu che dấu những biến thể hiện mà thể hiện trạng thái của đối tượng. Vì vậy, việc tương tác hay thay đổi dữ liệu đối với các loại biến thể hiện này được thực hiện thông qua các phương thức. Ví dụ, để thay đổi tên của chủ tài khoản, một phương thức riêng có tên chẳng hạn như setTenChuTaiKhoan() có thể được cung cấp trong lớp TaiKhoan, hoặc muốn xem thông tin về tên của chủ tài khoản ta có thể xây dựng phương thức getTenChuTaiKhoan(). Những phương thức như vậy thường được gọi là các setter và getter.
Các bổ từ truy cập
Bổ từ truy cập xác định cách mà các thành phần của lớp có thể được truy cập từ bên ngoài lớp đó. Nói cách khác, bổ từ truy cập định nghĩa tầm vực hay mức độ che dấu của các thành phần. Các bổ từ truy cập thường được đặt ở vị trí đầu tiên trong khi khai báo các thành phần của lớp. Phạm vi hay tầm vực của các thành phần cũng có liên hệ mật thiết với các gói.
Java cung cấp bốn loại bổ từ truy cập như sau:
- public: Cung cấp cấp độ truy cập rộng nhất. Các thành phần khai báo là public có thể được truy cập từ bất kỳ đâu trong lớp cũng như từ tất cả các lớp khác.
- private: Cung cấp cấp độ truy cập hẹp nhất. Các thành phần private chỉ có khả năng được truy cập từ bên trong lớp mà chúng được khai báo.
- protected: Bổ từ truy cập này cho phép các thành phần lớp có thể được truy cập từ bên trong lớp chứa chúng cũng như từ các lớp thừa kế.
- package (default): Bổ từ truy cập này chỉ cho phép các thành phần public của một lớp có thể truy cập tất cả các lớp có trong cùng một gói. Đây là mức truy cập mặc định cho tất cả các thành phần của lớp trong trường hợp ta không khai báo bổ từ truy cập.
Như là một quy tắc chung trong Java, những chi tiết cũng như sự thực thi của một lớp được che dấu đi từ các lớp khác hoặc những đối tượng mở rộng trong ứng dụng. Điều này được thực hiện bằng cách đặt các biến thể hiện có mức truy cập là private và các phương thức thể hiện có mức truy cập là public.
Đoạn mã 1 dưới đây cho thấy việc sử dụng khái niệm đóng gói trong lớp ChuNhat.
Đoạn mã 1:
//khai báo các biến thể hiện
private int rong;
private int cao;
/* Định nghĩa hàm tạo không tham số */
public ChuNhat() {
System.out.println("Hàm tạo được gọi ...");
rong = 10;
cao = 10;
}
/* Định nghĩa hàm tạo hai tham số */
public ChuNhat(int rong, int cao) {
System.out.println("Hàm tạo hai đối số được gọi ...");
this.rong = rong;
this.cao = cao;
}
/* Hiển thị các chiều của đối tượng hình chữ nhật */
public void hienThiCacChieu(){
System.out.println("Rộng: " + rong);
System.out.println("Cao: " + cao);
}
}
Đoạn mã 1 ở trên thay đổi bổ từ truy cập của các biến thể hiện là rong và cao của lớp ChuNhat từ default thành private. Điều này có nghĩa là không thể trực tiếp truy cập các trường này từ bên ngoài lớp. Mục đích của việc này là để hạn chế việc truy cập đến các thành phần dữ liệu của lớp. Tương tự, bổ từ truy cập cho các phương thức được thay đổi thành public. Do đó, người dùng có thể truy cập các thành phần lớp thông qua các phương thức của nó mà không ảnh hưởng đến việc thực thi bên trong của lớp.
Setter và Getter
Ở đoạn mã 1 bên trên, vì các biến thể hiện rong và cao được đóng gói là private, nên ta không thể truy cập đến chúng từ bên ngoài lớp ChuNhat. Tuy nhiên, có một số tình huống mà ta cần phải lấy hoặc thay đổi giá trị cho những biến thể hiện này, ví dụ như sử dụng một phương thức tử một lớp khác để nhập liệu cho chúng. Để giải quyết vấn đề này, ta xây dựng hai loại phương thức là setter và getter.
Phương thức setter
setter được xây dựng để gán hay thay đổi giá trị của biến thể hiện. Cú pháp xây dựng setter là như sau:
tên_biến_thể_hiện = tham_số;
}
trong đó,
bổ_từ_truy_cập: phải khác private.
tham_số: phải có kiểu tương đương với kiểu của tên_biến_thể_hiện.
Đoạn mã 2 dưới đây định nghĩa các phương thức setter cho hai biến rong và cao của lớp ChuNhat.
Đoạn mã 2:
this.rong = rong;
}
public void setCao(int cao) {
this.cao = cao;
}
Phương thức getter
getter dùng để lấy giá trị của biến thể hiện. Cú pháp cho phương thức getter là như sau:
return tên_biến_thể_hiện;
}
trong đó,
bổ_từ_truy_cập: không được là private.
kiểu_trả_về: giống với kiểu của tên_biến_thể_hiện.
Đoạn mã 3 định nghĩa các phương thức getter cho hai biến thể hiện rong và cao.
Đoạn mã 3:
return rong;
}
public void getCao() {
return cao;
}