EDIT: Tôi đã trả lời câu hỏi này bởi vì có rất nhiều người học lập trình hỏi điều này và hầu hết các câu trả lời đều rất có năng lực về mặt kỹ thuật, nhưng chúng không dễ hiểu nếu bạn là người mới. Chúng tôi đều là người mới, vì vậy tôi nghĩ tôi sẽ thử một câu trả lời thân thiện với người mới hơn.
Hai cái chính là đa hình và xác nhận. Ngay cả khi đó chỉ là một cấu trúc dữ liệu ngu ngốc.
Hãy nói rằng chúng ta có lớp đơn giản này:
public class Bottle {
public int amountOfWaterMl;
public int capacityMl;
}
Một lớp rất đơn giản chứa được bao nhiêu chất lỏng trong đó, và công suất của nó là bao nhiêu (tính bằng mililit).
Điều gì xảy ra khi tôi làm:
Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacityMl = 1000;
Chà, bạn sẽ không mong đợi nó hoạt động chứ? Bạn muốn có một số loại kiểm tra vệ sinh. Và tệ hơn, nếu tôi không bao giờ chỉ định công suất tối đa thì sao? Trời ơi, chúng ta có một vấn đề.
Nhưng cũng có một vấn đề khác. Nếu chai chỉ là một loại container thì sao? Điều gì sẽ xảy ra nếu chúng ta có một số thùng chứa, tất cả đều có dung tích và lượng chất lỏng chứa đầy? Nếu chúng ta có thể tạo giao diện, chúng ta có thể để phần còn lại của chương trình chấp nhận giao diện đó, và các chai, jerrycans và tất cả các loại công cụ sẽ hoạt động thay thế cho nhau. Điều đó có tốt hơn không? Vì giao diện yêu cầu phương thức, đây cũng là một điều tốt.
Chúng tôi sẽ kết thúc với một cái gì đó như:
public interface LiquidContainer {
public int getAmountMl();
public void setAmountMl(int amountMl);
public int getCapacityMl();
}
Tuyệt quá! Và bây giờ chúng ta chỉ cần thay đổi Chai thành này:
public class Bottle extends LiquidContainer {
private int capacityMl;
private int amountFilledMl;
public Bottle(int capacityMl, int amountFilledMl) {
this.capacityMl = capacityMl;
this.amountFilledMl = amountFilledMl;
checkNotOverFlow();
}
public int getAmountMl() {
return amountFilledMl;
}
public void setAmountMl(int amountMl) {
this.amountFilled = amountMl;
checkNotOverFlow();
}
public int getCapacityMl() {
return capacityMl;
}
private void checkNotOverFlow() {
if(amountOfWaterMl > capacityMl) {
throw new BottleOverflowException();
}
}
Tôi sẽ để lại định nghĩa về BottleOverflowException như một bài tập cho người đọc.
Bây giờ hãy chú ý xem điều này mạnh hơn bao nhiêu. Chúng tôi có thể đối phó với bất kỳ loại container nào trong mã của chúng tôi ngay bây giờ bằng cách chấp nhận LiquidContainer thay vì Chai. Và làm thế nào những chai này đối phó với loại công cụ này tất cả có thể khác nhau. Bạn có thể có các chai ghi trạng thái của chúng vào đĩa khi nó thay đổi hoặc các chai lưu trên cơ sở dữ liệu SQL hoặc GNU biết những gì khác.
Và tất cả những điều này có thể có những cách khác nhau để xử lý các ca phẫu thuật khác nhau. Chai chỉ kiểm tra và nếu nó tràn ra, nó sẽ ném RuntimeException. Nhưng đó có thể là điều sai trái. (Có một cuộc thảo luận hữu ích để xử lý lỗi, nhưng tôi cố tình giữ nó rất đơn giản. Mọi người trong các bình luận có thể sẽ chỉ ra những sai sót của phương pháp đơn giản này.))
Và vâng, có vẻ như chúng ta đi từ một ý tưởng rất đơn giản để nhận được câu trả lời tốt hơn nhiều một cách nhanh chóng.
Cũng xin lưu ý rằng bạn không thể thay đổi dung tích của một chai. Bây giờ nó được đặt trong đá. Bạn có thể làm điều này với một int bằng cách khai báo nó cuối cùng. Nhưng nếu đây là một danh sách, bạn có thể làm trống nó, thêm những thứ mới vào nó, v.v. Bạn không thể giới hạn quyền truy cập để chạm vào các bộ phận bên trong.
Ngoài ra còn có điều thứ ba mà không phải ai cũng giải quyết: getters và setters sử dụng các cuộc gọi phương thức. Điều đó có nghĩa là chúng trông giống như các phương pháp bình thường ở mọi nơi khác. Thay vì có cú pháp cụ thể kỳ lạ cho DTO và công cụ, bạn có điều tương tự ở mọi nơi.