Đóng gói có một mục đích, nhưng nó cũng có thể bị lạm dụng hoặc lạm dụng.
Hãy xem xét một cái gì đó giống như API Android có các lớp với hàng chục (nếu không phải hàng trăm) trường. Việc phơi bày những lĩnh vực đó, người tiêu dùng API khiến việc điều hướng và sử dụng trở nên khó khăn hơn, nó cũng mang đến cho người dùng khái niệm sai lầm rằng anh ta có thể làm bất cứ điều gì anh ta muốn với những lĩnh vực có thể xung đột với cách sử dụng chúng. Vì vậy, đóng gói là tuyệt vời theo nghĩa đó cho khả năng duy trì, khả năng sử dụng, khả năng đọc và tránh các lỗi điên.
Mặt khác, POD hoặc các kiểu dữ liệu cũ đơn giản, như một cấu trúc từ C / C ++, trong đó tất cả các trường đều công khai cũng có thể hữu ích. Có các getters / setters vô dụng như những người được tạo bởi chú thích @data trong Lombok chỉ là một cách để giữ "mẫu đóng gói". Một trong số ít lý do chúng tôi thực hiện getters / setters "vô dụng" trong Java là các phương thức cung cấp hợp đồng .
Trong Java, bạn không thể có các trường trong một giao diện, do đó bạn sử dụng getters và setters để chỉ định một thuộc tính chung mà tất cả những người triển khai giao diện đó có. Trong các ngôn ngữ gần đây hơn như Kotlin hoặc C #, chúng tôi thấy khái niệm thuộc tính là các trường mà bạn có thể khai báo setter và getter. Cuối cùng, các getters / setters vô dụng là một di sản mà Java phải sống cùng, trừ khi Oracle thêm các thuộc tính cho nó. Ví dụ, Kotlin, một ngôn ngữ JVM khác do JetBrains phát triển, có các lớp dữ liệu về cơ bản thực hiện những gì chú thích @data thực hiện trong Lombok.
Ngoài ra đây là một vài ví dụ:
class DataClass
{
private int data;
public int getData() { return data; }
public void setData(int data) { this.data = data; }
}
Đây là một trường hợp xấu của đóng gói. Các getter và setter là vô dụng hiệu quả. Đóng gói chủ yếu được sử dụng vì đây là tiêu chuẩn trong các ngôn ngữ như Java. Không thực sự có ích, bên cạnh việc duy trì tính nhất quán trên cơ sở mã.
class DataClass implements IDataInterface
{
private int data;
@Override public int getData() { return data; }
@Override public void setData(int data) { this.data = data; }
}
Đây là một ví dụ tốt về đóng gói. Đóng gói được sử dụng để thực thi hợp đồng, trong trường hợp này là IDataInterface. Mục đích của việc đóng gói trong ví dụ này là làm cho người tiêu dùng của lớp này sử dụng các phương thức được cung cấp bởi giao diện. Mặc dù getter và setter không làm bất cứ điều gì lạ mắt, giờ đây chúng ta đã xác định một đặc điểm chung giữa DataClass và những người triển khai khác của IDataInterface. Vì vậy, tôi có thể có một phương pháp như thế này:
void doSomethingWithData(IDataInterface data) { data.setData(...); }
Bây giờ, khi nói về đóng gói, tôi nghĩ điều quan trọng là cũng phải giải quyết vấn đề cú pháp. Tôi thường thấy mọi người phàn nàn về cú pháp cần thiết để thực thi đóng gói thay vì đóng gói chính nó. Một ví dụ xuất hiện trong tâm trí là từ Casey Muratori (bạn có thể thấy câu nói của anh ấy ở đây ).
Giả sử bạn có một lớp người chơi sử dụng đóng gói và muốn di chuyển vị trí của mình thêm 1 đơn vị. Mã sẽ trông như thế này:
player.setPosX(player.getPosX() + 1);
Nếu không đóng gói, nó sẽ trông như thế này:
player.posX++;
Ở đây, ông lập luận rằng việc đóng gói dẫn đến việc gõ nhiều hơn mà không có lợi ích bổ sung và trong nhiều trường hợp điều này có thể đúng, nhưng hãy chú ý điều gì đó. Đối số là trái với cú pháp, không đóng gói chính nó. Ngay cả trong các ngôn ngữ như C thiếu khái niệm đóng gói, bạn sẽ thường thấy các biến trong các cấu trúc được định sẵn hoặc sufixed với '_' hoặc 'my' hoặc bất cứ điều gì để biểu thị rằng chúng không nên được sử dụng bởi người tiêu dùng API, như thể chúng là riêng tư.
Thực tế của vấn đề là đóng gói có thể giúp làm cho mã dễ bảo trì hơn và dễ sử dụng hơn. Hãy xem xét lớp này:
class VerticalList implements ...
{
private int posX;
private int posY;
... //other members
public void setPosition(int posX, int posY)
{
//change position and move all the objects in the list as well
}
}
Nếu các biến được công khai trong ví dụ này thì người tiêu dùng API này sẽ bị nhầm lẫn khi nào nên sử dụng posX và posY và khi nào nên sử dụng setPocation (). Bằng cách ẩn những chi tiết đó, bạn sẽ giúp người tiêu dùng sử dụng API của bạn một cách trực quan hơn.
Cú pháp là một hạn chế trong nhiều ngôn ngữ mặc dù. Tuy nhiên, các ngôn ngữ mới hơn cung cấp các thuộc tính cho chúng ta cú pháp tốt đẹp của các thành viên publice và lợi ích của việc đóng gói. Bạn sẽ tìm thấy các thuộc tính trong C #, Kotlin, thậm chí trong C ++ nếu bạn sử dụng MSVC. đây là một ví dụ trong Kotlin.
lớp dọcList: ... {var posX: Int set (x) {field = x; ...} var posY: Int set (y) {field = y; ...}}
Ở đây chúng ta đã đạt được điều tương tự như trong ví dụ Java, nhưng chúng ta có thể sử dụng posX và posY như thể chúng là các biến công khai. Khi tôi cố gắng thay đổi giá trị của chúng, phần thân của bộ định cư () sẽ được thực thi.
Ví dụ, trong Kotlin, điều này sẽ tương đương với Java Bean với getters, setters, hashcode, bằng và toString được triển khai:
data class DataClass(var data: Int)
Lưu ý cách cú pháp này cho phép chúng ta thực hiện Java Bean trong một dòng. Bạn đã nhận thấy chính xác vấn đề mà một ngôn ngữ như Java gặp phải khi thực hiện đóng gói, nhưng đó là lỗi của Java không phải do đóng gói.
Bạn nói rằng bạn sử dụng @Data của Lombok để tạo getters và setters. Chú ý tên, @Data. Nó chủ yếu được sử dụng trên các lớp dữ liệu chỉ lưu trữ dữ liệu và có nghĩa là được tuần tự hóa và giải tuần tự hóa. Hãy nghĩ về một cái gì đó giống như một tập tin lưu từ một trò chơi. Nhưng trong các kịch bản khác, như với một thành phần UI, bạn chắc chắn muốn setters vì chỉ cần thay đổi giá trị của một biến có thể không đủ để có được hành vi mong đợi.
"It will create getters, setters and setting constructors for all private fields."
- Cách bạn mô tả công cụ này, có vẻ như nó đang duy trì đóng gói. (Ít nhất là trong một ý nghĩa mô hình lỏng lẻo, tự động, hơi thiếu máu.) Vậy vấn đề chính xác là gì?