Giải pháp chuyển đổi số trọn gói cho doanh nghiệp Việt

Gói Ghém Dữ Liệu Với Getter Và Setter - Lập trình Java

Lập trình Java

← Toàn bộ tài liệu


Bài 27: Gói Ghém Dữ Liệu Với Getter Và Setter

Bài 27/56 Xem dạng sách

Bài học hôm nay chúng ta cùng xem qua cách tổ chức các phương thức getter và setter trong Java. Hai phương thức này là gì? Chúng có liên hệ như thế nào đến việc gói ghém dữ liệu? Và gói ghém dữ liệu là gì? Mời các bạn cùng đọc tiếp nhé.

Làm Quen Với Getter, Setter Và Gói Ghém Dữ Liệu

Getter và setter là hai tên gọi của hai thể loại phương thức. Chúng có liên quan đến đặc tính gói ghém dữ liệu trong lập trình hướng đối tượng, tiếng Anh gọi đặc tính này là encapsulation. Trước đó, các định nghĩa về khả năng truy cập cũng là một dạng của gói ghém dữ liệu.

Chúng ta làm quen với gói ghém dữ liệu. Tính gói ghém dữ liệu là một trong những đặc tính cơ bản trong kế thừa, bên cạnh các đặc tính khác, như tính kế thừa (inheritance) mà bạn đã biết, tính đa hình (polymorphism) và tính trừu tượng (abstraction) mà bạn sẽ làm quen sau. Đặc tính gói ghém dữ liệu giúp che đi các thuộc tính của một lớp, khiến cho các lớp khác không thể “nhìn thấy”, không thể thay đổi hay chỉnh sửa các giá trị của các thuộc tính này. Bạn cũng được biết các khả năng truy cập private hay protected đều giúp ích rất nhiều trong việc gói ghém dữ liệu đúng không nào.

Vậy getter và setter liên quan gì đến gói ghém dữ liệu? Các phương thức này giúp việc quản lý các thuộc tính private hay protected của một lớp được thoải mái và tường minh hơn. Bạn có thể chỉ định các thuộc tính này sao cho chỉ được phép đọc (thông qua phương thức getter), hoặc chỉ được phép ghi (thông qua phương thức setter), hoặc cho phép cả đọc lẫn ghi (thông qua cả getter và setter) từ các lớp bên ngoài.

Định nghĩa trên chắc đủ rõ ràng cho bạn để hiểu loại phương thức này đúng không. Nếu chưa thực sự hiểu về nó thì mời bạn đọc tiếp các phần sau nhé.

Tổ Chức Các Phương Thức Getter Và Setter Như Thế Nào Để Đảm Bảo Tính Gói Ghém Dữ Liệu

Để getter và setter được tạo ra nhưng vẫn đảm bảo sự gói ghém cho các thuộc tính trong một lớp, chúng ta có các nguyên tắc sau.

– Luôn có thói quen định nghĩa khả năng truy cập cho các thuộc tính trong một lớp là private.
– Sau đó tùy nhu cầu mà xây dựng phương thức getter hay setter, hay cả getter và setter cho từng thuộc tính private đó. Với các phương thức setter bạn nên đặt tên theo cấu trúc setTênThuộcTính(). Còn với getter thì bạn nên đặt là getTênThuộcTính(). Việc xuất hiện của getter và setter cho các thuộc tính private là không bắt buộc. Bạn có thể không cần khai báo bất cứ getter hay setter nào cả nếu như bạn không muốn thuộc tính đó được thấy bởi bất kỳ lớp nào. Hoặc bạn chỉ cần xây dựng getter cho một thuộc tính private nếu bạn muốn thuộc tính đó chỉ được xem, không được sửa chữa. Hoặc chỉ xây dựng setter cho một thuộc tính private nếu bạn muốn thuộc tính đó chỉ có khả năng chỉnh sửa từ bên ngoài mà không được xem. Hoặc có cả getter và setter nếu nó được phép xem và sửa.

Trường hợp thuộc tính private có cả getter lẫn setter thoạt nghe đến bạn có thể nghĩ rằng, tại sao không xây dựng public cho các thuộc tính này, tại sao lại phải rườm rà set nó là private, sau đó lại thêm cả getter lẫn setter?

Bạn nên biết, sự rườm rà này rất đáng giá, bạn nên tập làm quen với nguyên tắc gói ghém dữ liệu kiểu này. Đầu tiên nó tạo cho bạn một thói quen làm việc theo nguyên tắc, OOP là một lĩnh vực có khá nhiều nguyên tắc và đây là nguyên tắc đầu tiên mà bạn có thể dễ dàng để thực hiện. Sau đó, cách xây dựng getter và setter như thế này giúp giảm thiểu lỗi cho ứng dụng, ai mà biết với việc khai báo các thuộc tính của lớp là public ngay từ đầu, các lớp khác đều có quyền thấy và sửa chữa các thuộc tính này có gây ra bất kỳ lỗi tiềm ẩn nào hay không. Bạn tưởng tượng xem khi đối tượng HinhTron được khởi tạo với một banKinh được chỉ định ở constructor, nhưng khi bắt đầu vào tính diện tích thì ở đâu đó lại truy xuất đến thuộc tính banKinh public và chỉnh sửa nó, phương thức tính diện tích lúc này có thể sẽ cho ra kết quả không mong muốn. Nhưng với getter và setter được khai báo, bạn có quyền thêm các dòng code đảm bảo tính đúng đắn của dữ liệu truyền vào cho thuộc tính banKinh (bài thực hành số 2 dưới đây giúp bạn rõ hơn), hơn nữa bạn còn có thể kiểm tra các điều kiện xem thuộc tính này có đang được dùng để tính toán ở đâu không, có cần phải thay đổi ngay lúc này hay delay bao lâu để tránh việc sai số cho các tính toán hay không.

Bài Thực Hành Số 1

Nào, để dễ hiểu một chút, chúng ta cùng đến với bài thực hành sau. Chúng ta hãy xây dựng lại lớp HinhHoc theo đúng tiêu chí của gói ghém dữ liệu bằng cách chỉ định các thuộc tính ở lớp này là private.

public class HinhTron {
private final float PI = 3.14f;
private float banKinh;
private float chuVi;
private float dienTich;
 
// Constructor
public HinhTron(float banKinh) {
this.banKinh = banKinh;
}
 
public void tinhChuVi() {
chuVi = 2 * PI * banKinh;
}
 
public void tinhDienTich() {
dienTich = PI * banKinh * banKinh;
}
}

Bạn có thể thấy, lớp HinhTron đã gói ghém các thuộc tính của nó, sao cho banKinh, chuVi và dienTich đều là private. banKinh chỉ được khởi tạo duy nhất khi HinhTron được khởi tạo. Sau đó, việc tinhChuVi() hay tinhDienTich() diễn ra an toàn, banKinh không thể được thay đổi ở bất kỳ một chỗ nào đó khác trong toàn bộ chương trình. Các lớp khác bên ngoài cũng không thể nào tự thay đổi các thuộc tính chuVi và dienTich, nó thuộc quyền quản lý và tùy ý thay đổi của riêng HinhTron mà thôi. Bạn hãy xem main() sử dụng HinhTron như sau.

public class MainClass {
public static void main(String[] args) {
HinhTron hinhTron = new HinhTron(20);
hinhTron.tinhChuVi();
hinhTron.tinhDienTich();
}
}

Nếu giờ đây bạn muốn main() in ra kết quả của các phép tính chu vi và diện tích, hãy thêm getter cho các thuộc tính này bên trong HinhTron.

public class HinhTron {
private final float PI = 3.14f;
private float banKinh;
private float chuVi;
private float dienTich;
 
// Constructor
public HinhTron(float banKinh) {
this.banKinh = banKinh;
}
 
public float getChuVi() {
return chuVi;
}
 
public float getDienTich() {
return dienTich;
}
 
public void tinhChuVi() {
chuVi = 2 * PI * banKinh;
}
 
public void tinhDienTich() {
dienTich = PI * banKinh * banKinh;
}
}

Khi đó main() có thể đọc được các giá trị bên trong HinhTron như thế này.

public class MainClass {
public static void main(String[] args) {
HinhTron hinhTron = new HinhTron(20);
hinhTron.tinhChuVi();
hinhTron.tinhDienTich();
 
float chuVi = hinhTron.getChuVi();
float dienTich = hinhTron.getDienTich();
System.out.println("Chu vi hình tròn: " + chuVi + "; Và diện tích: " + dienTich) ;
}
}

Bạn đã hiểu cách dùng getter và setter rồi đúng không nào. Bài thực hành tiếp theo đây cho chúng ta thấy thêm một lợi ích và cách sử dụng nữa của việc sử dụng chúng.

Bài Thực Hành Số 2

Bài thực hành này xây dựng lớp SinhVien với các thuộc tính private kèm với các phương thức getter và setter cho chúng. Bạn có thể thấy rằng bạn hoàn toàn có thể tùy biến sao cho chính setter là một phương thức private (bạn không cho ghi vào thuộc tính từ bên ngoài lớp luôn, nhưng chính setter private này lại giúp kiểm tra chút ràng buộc điều kiện). Phương thức getter getTuoi() cũng được xử lý một chút trước khi trả kết quả ra bên ngoài.

public class SinhVien {
private String ten;
private int tuoi;
 
public SinhVien(String ten, int tuoi) {
setTen(ten);
setTuoi(tuoi);
}
 
private void setTen(String ten) {
if (ten == null || ten.isEmpty()) {
// Nếu biến ten chưa khởi tạo (mang giá trị null), hoặc biến ten có nội dung rỗng
// Thì hãy lưu với tên là "Không biết"
this.ten = "Không biết";
} else {
this.ten = ten;
}
}
 
public String getTen() {
return ten;
}
 
private void setTuoi(int tuoi) {
// Kiểm tra tuổi có hợp lý hay không, nếu hợp lý thì lưu vào,
// nếu không sẽ tìm cách báo lỗi bằng cách lưu giá trị âm
if (tuoi > 18) {
this.tuoi = tuoi;
} else {
this.tuoi = -1;
}
}
 
public String getTuoi() {
if (this.tuoi != -1) {
// Tuổi hợp lệ
return String.valueOf(tuoi);
} else {
return "Tuổi không hợp lệ";
}
}
}

Với khai báo lớp như trên. Giả sử mình khởi tạo các đối tượng sinh viên này ở main() như sau.

public class MainClass {
public static void main(String[] args) {
// Khởi tạo các đối tượng sinhVien
// với các thông tin về tên và tuổi
SinhVien sinhVien1 = new SinhVien("", 23);
SinhVien sinhVien2 = new SinhVien("Peter", 17);
 
// In thông tin các sinh viên
System.out.println("Sinh viên 1 có tên: " + sinhVien1.getTen() + ", tuổi: " + sinhVien1.getTuoi());
System.out.println("Sinh viên 2 có tên: " + sinhVien2.getTen() + ", tuổi: " + sinhVien2.getTuoi());
}
}

Sau đây là kết quả in ra console.

Sinh viên 1 có tên: Không biết, tuổi: 23
Sinh viên 2 có tên: Peter, tuổi: Tuổi không hợp lệ

Dùng InteliJ Để Khai Báo Getter Và Setter Tự Động

Như trên mình có nói, việc đặt tên cho getter và setter nên là getTênThuộcTính() và setTênThuộcTính(). Tuy nhiên, InteliJ đã hỗ trợ chúng ta một công cụ tự động phát sinh tên cho các phương thức getter và setter của thuộc tính private nào đó .

Bạn hãy cùng mình thử nghiệm tính năng sinh code tự động qua các bước sau. Đầu tiên bạn hãy thử tạo một lớp ToaDo có khai báo một hay nhiều thuộc tính private như sau.

Khai báo lớp có các thuộc tính privatehttps://i0.wp.com/yellowcodebooks.com/wp-content/uploads/2022/12/Screenshot-2022-12-20-at-11.23-1.png?resize=300%2C202&ssl=1 300w" data-lazy-loaded="1" sizes="(max-width: 636px) 100vw, 636px" loading="eager" style="box-sizing: border-box; height: auto; max-width: 100%; border-style: none; margin: 0px; vertical-align: bottom;">
Khai báo lớp có các thuộc tính private

Nếu bạn click lên một trong các thuộc tính private này, bạn sẽ thấy icon cảnh báo màu vàng bên thanh trái của editor. Vì hệ thống thấy rằng chúng ta khai báo private cho thuộc tính, mà không sử dụng chúng ở bên trong lớp, thì hiển nhiên là có vấn đề rồi, vì bên ngoài lớp có nhìn thấy các giá trị này đâu.

Khi này bạn cứ click vào một trong các cảnh báo này, một dialog nhỏ xuất hiện như sau.

Dialog xuất hiện giúp bạn có những chọn lựa thú vị
Dialog xuất hiện giúp bạn có những chọn lựa thú vị

Bạn đã thấy có các tùy chọn Create getter for ‘x’ và Create setter for ‘x’. Vâng, đó là các cách mà InteliJ sẽ thêm tự động các phương thức getter và setter. Bạn có thể tự trải nghiệm nhé, mình toàn dùng chúng cho việc tạo nhanh code đấy.

Kết Luận

Vậy là chúng ta vừa xem qua kiến thức về xây dựng các phương thức getter và setter trong hướng đối tượng. Thực ra, việc có nên xây dựng getter và setter cho các thuộc tính của lớp trong Java hay cứ khai báo public hết cho chúng, từng là chủ đề tranh cãi. Vì nhìn chung lại getter và setter sẽ làm cho các lớp trở nên rườm rà hơn. Có một số ý kiến cũng cho rằng hiệu năng của ứng dụng cũng giảm đi với getter và setter. Nhưng như bài học có nói đến, rằng đây là một trong những cách gói ghém dữ liệu của lớp. Với getter và setter, bạn sẽ có thể chỉ định được thuộc tính đó hoàn toàn ẩn, hay chỉ đọc, hay chỉ ghi, hay có thể đọc và ghi. Một cách để làm cho code bạn được tường minh hơn, ít lỗi hơn, và tuân thủ nguyên tắc OOP hơn.

Sơ đồ quan hệ các bảng

Table Name: {{table.name}}
Num Name Type
{{field_index+1}}
{{field.name}} {{field.type}}

Hỏi đáp

Gửi với tên {{comment_full_name}}

Chưa có câu hỏi nào cho bài này.

Zalo 0936.006.058 Hotline 0936.006.058