Là đóng gói vẫn là một trong những con voi OOP đứng trên?


97

Đóng gói bảo tôi làm cho tất cả hoặc gần như tất cả các trường riêng tư và hiển thị chúng bằng getters / setters. Nhưng bây giờ các thư viện như Lombok xuất hiện cho phép chúng tôi trưng bày tất cả các trường riêng tư bằng một chú thích ngắn @Data. Nó sẽ tạo getters, setters và thiết lập constructor cho tất cả các trường riêng.

Ai đó có thể giải thích cho tôi ý nghĩa của việc che giấu tất cả các lĩnh vực là riêng tư và sau đó để phơi bày tất cả chúng bằng một số công nghệ bổ sung? Tại sao chúng ta chỉ đơn giản là không sử dụng các lĩnh vực công cộng sau đó? Tôi cảm thấy chúng tôi đã đi một chặng đường dài và khó khăn chỉ để trở về điểm xuất phát.

Vâng, có những công nghệ khác hoạt động thông qua getters và setters. Và chúng ta không thể sử dụng chúng thông qua các lĩnh vực công cộng đơn giản. Nhưng những công nghệ này chỉ xuất hiện bởi vì chúng tôi nhiều tài sản đó - các lĩnh vực riêng tư đằng sau getters / setters công cộng. Nếu chúng ta không có tài sản, các công nghệ này sẽ phát triển theo cách khác và sẽ hỗ trợ các lĩnh vực công cộng. Và mọi thứ sẽ đơn giản và chúng tôi sẽ không có nhu cầu về Lombok bây giờ.

Ý nghĩa của toàn bộ chu kỳ này là gì? Và có đóng gói thực sự bất kỳ ý nghĩa bây giờ trong lập trình thực tế?


19
"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ì?
David

78
Đóng gói là ẩn nội bộ thực hiện của đối tượng đằng sau hợp đồng công khai của nó (thường là một giao diện). Getters và setters thực hiện chính xác điều ngược lại - chúng phơi bày phần bên trong của đối tượng, vì vậy vấn đề nằm ở getters / setters chứ không phải đóng gói.

22
Các lớp dữ liệu @VinceEmigh không được đóng gói . Thể hiện của chúng là các giá trị theo đúng nghĩa của người nguyên thủy
Caleth

10
@VinceEmigh sử dụng JavaBeans không phảiOO , nó là Thủ tục . Rằng văn học gọi chúng là "Đối tượng" là một sai lầm của lịch sử.
Caleth

28
Tôi đã suy nghĩ rất nhiều về vấn đề này trong nhiều năm qua; Tôi nghĩ rằng đây là một trường hợp mà ý định của OOP khác với việc thực hiện. Sau khi nghiên cứu Smalltalk thì rõ ràng những gì OOP của đóng gói đã được dự định để được (tức là, mỗi lớp là giống như một máy tính độc lập, với các phương pháp như giao thức chia sẻ), mặc dù vì lý do cho đến nay chưa biết đến tôi, nó đã chắc chắn bắt được trên phổ biến để thực hiện những các đối tượng getter / setter không đóng gói khái niệm (chúng không che giấu gì, không quản lý gì và không có trách nhiệm nào ngoài dữ liệu), nhưng chúng vẫn sử dụng các thuộc tính.
jrh

Câu trả lời:


82

Nếu bạn trưng bày tất cả các thuộc tính của mình bằng getters / setters, bạn sẽ chỉ nhận được cấu trúc dữ liệu vẫn được sử dụng trong C hoặc bất kỳ ngôn ngữ thủ tục nào khác. Nó không được đóng gói và Lombok chỉ làm việc với mã thủ tục ít đau đớn hơn. Getters / setters xấu như các lĩnh vực công cộng đơn giản. Không có sự khác biệt thực sự.

Và cấu trúc dữ liệu không phải là một đối tượng. Nếu bạn sẽ bắt đầu tạo một đối tượng từ việc viết một giao diện, bạn sẽ không bao giờ thêm getters / setters vào giao diện. Việc phơi bày các thuộc tính của bạn dẫn đến mã thủ tục spaghetti trong đó thao tác với dữ liệu nằm bên ngoài một đối tượng và lan truyền mã cơ sở. Bây giờ bạn đang xử lý dữ liệu và thao tác với dữ liệu thay vì nói chuyện với các đối tượng. Với getters / setters, bạn sẽ có lập trình thủ tục dựa trên dữ liệu trong đó thao tác với điều đó được thực hiện theo cách bắt buộc thẳng. Nhận dữ liệu - làm một cái gì đó - thiết lập dữ liệu.

Trong đóng gói OOP là một con voi nếu được thực hiện đúng cách. Bạn nên đóng gói chi tiết trạng thái và triển khai để đối tượng có toàn quyền kiểm soát điều đó. Logic sẽ được tập trung bên trong đối tượng và sẽ không được trải đều trên cơ sở mã. Và có - đóng gói vẫn là điều cần thiết trong lập trình vì mã sẽ được duy trì nhiều hơn.

EDITS

Sau khi thấy các cuộc thảo luận đang diễn ra, tôi muốn thêm một số điều:

  • Không quan trọng bạn có bao nhiêu thuộc tính mà bạn thể hiện thông qua getters / setters và mức độ cẩn thận của bạn đang làm điều đó. Được lựa chọn nhiều hơn sẽ không làm cho mã của bạn OOP được đóng gói. Mỗi thuộc tính bạn phơi bày sẽ dẫn đến một số thủ tục làm việc với dữ liệu trần trụi đó theo cách thủ tục bắt buộc. Bạn sẽ chỉ truyền bá mã của mình chậm hơn với sự chọn lọc hơn. Điều đó không thay đổi cốt lõi.
  • Có, trong các ranh giới trong hệ thống, bạn có được dữ liệu trần từ các hệ thống hoặc cơ sở dữ liệu khác. Nhưng dữ liệu này chỉ là một điểm đóng gói khác.
  • Đối tượng nên đáng tin cậy . Toàn bộ ý tưởng của các đối tượng là chịu trách nhiệm để bạn không cần phải đưa ra các mệnh lệnh thẳng và bắt buộc. Thay vào đó, bạn yêu cầu một đối tượng thực hiện những gì nó làm tốt thông qua hợp đồng. Bạn ủy thác một cách an toàn một phần cho đối tượng. Đối tượng đóng gói trạng thái và chi tiết thực hiện.

Vì vậy, nếu chúng ta trở lại câu hỏi tại sao chúng ta nên làm điều này. Hãy xem xét ví dụ đơn giản này:

public class Document {
    private String title;

    public String getTitle() {
        return title;
    }
}

public class SomeDocumentServiceOrHandler {

    public void printDocument(Document document) {
        System.out.println("Title is " + document.getTitle());
    }
}

Ở đây chúng ta có Tài liệu phơi bày chi tiết nội bộ bằng getter và có mã thủ tục bên ngoài trong printDocumentchức năng hoạt động với bên ngoài đối tượng. Tại sao điều này là xấu? Bởi vì bây giờ bạn chỉ có mã kiểu C. Vâng, nó có cấu trúc nhưng sự khác biệt thực sự là gì? Bạn có thể cấu trúc các hàm C trong các tệp khác nhau và có tên. Và những người được gọi là lớp đang làm chính xác điều đó. Lớp dịch vụ chỉ là một loạt các thủ tục làm việc với dữ liệu. Mã đó ít được bảo trì và có nhiều nhược điểm.

public interface Printable {
    void print();
}

public final class PrintableDocument implements Printable {
    private final String title;

    public PrintableDocument(String title) {
        this.title = title;
    }

    @Override
    public void print() {
        System.out.println("Title is " + title);
    }
}

So sánh với cái này. Bây giờ chúng tôi có một hợp đồng và các chi tiết giả định của hợp đồng này được ẩn bên trong đối tượng. Bây giờ bạn thực sự có thể kiểm tra lớp đó và lớp đó đang gói gọn một số dữ liệu. Làm thế nào nó hoạt động với dữ liệu đó là một đối tượng quan tâm. Để nói chuyện với đối tượng bây giờ bạn cần yêu cầu anh ta tự in. Đó là sự gói gọn và đó là một đối tượng. Bạn sẽ có được toàn bộ sức mạnh của việc tiêm phụ thuộc, chế giễu, thử nghiệm, các trách nhiệm đơn lẻ và rất nhiều lợi ích với OOP.


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
maple_shaft

1
"Getters / setters xấu như các lĩnh vực công cộng đơn giản" - điều đó không đúng, ít nhất là trong nhiều ngôn ngữ. Bạn thường không thể ghi đè truy cập trường đơn giản, nhưng có thể ghi đè getters / setters. Điều đó mang lại sự linh hoạt cho các lớp học trẻ em. Nó cũng làm cho nó dễ dàng thay đổi hành vi của các lớp. ví dụ: bạn có thể bắt đầu với một getter / setter được hỗ trợ bởi một trường riêng và sau đó chuyển sang một trường khác hoặc tính giá trị từ các giá trị khác. Không thể được thực hiện với các lĩnh vực đơn giản. Một số ngôn ngữ cho phép các trường có những gì cơ bản là getters và setters, nhưng Java không phải là ngôn ngữ như vậy.
Kat

Ơ, vẫn chưa thuyết phục. Ví dụ của bạn là ổn nhưng chỉ biểu thị một phong cách mã hóa khác, không phải là "đúng đúng" - không tồn tại. Hãy nhớ rằng hầu hết các ngôn ngữ hiện nay là đa ngôn ngữ và hiếm khi là OO thuần túy theo thủ tục. Gắn bó bản thân quá khó với "khái niệm OO thuần túy". Ví dụ - bạn không thể sử dụng DI trên ví dụ của mình vì bạn đã ghép logic in với một lớp con. In không nên là một phần của tài liệu - printableDocument sẽ hiển thị phần có thể in của nó (thông qua một getter) cho Dịch vụ Máy in.
T. Sar

Một cách tiếp cận OO thích hợp cho vấn đề này, nếu chúng ta đang đi đến một cách tiếp cận "Pure OO", sẽ sử dụng thứ gì đó thực hiện một lớp Máy in trừu tượng yêu cầu, thông qua một tin nhắn, cái gì sẽ được in - sử dụng GetPrintablePart. Thứ thực hiện Dịch vụ Máy in có thể là bất kỳ loại máy in nào - in ra PDF, ra màn hình, tới TXT ... Giải pháp của bạn khiến không thể trao đổi logic in ấn cho một thứ khác, kết thúc được ghép nối nhiều hơn và ít bảo trì hơn hơn ví dụ "sai" của bạn.
T. Sar

Điểm mấu chốt: Getters và Setters không xấu xa hoặc phá vỡ OOP - Những người không hiểu cách sử dụng chúng. Trường hợp ví dụ của bạn là một ví dụ trong sách giáo khoa về những người thiếu toàn bộ cách thức hoạt động của DI. Ví dụ bạn "đã sửa" đã được kích hoạt DI, tách rời, có thể bị chế giễu một cách dễ dàng ... Thực sự - có nhớ toàn bộ điều "Ưu tiên thành phần hơn kế thừa" không? Một trong những trụ cột OO? Bạn chỉ cần ném nó qua cửa sổ bằng cách bạn tái cấu trúc mã. Điều này sẽ không bay trong bất kỳ xem xét mã nghiêm trọng.
T. Sar

76

Ai đó có thể giải thích cho tôi, ý nghĩa của việc che giấu tất cả các lĩnh vực là riêng tư và sau đó để phơi bày tất cả chúng bằng một số công nghệ bổ sung? Tại sao chúng ta không chỉ đơn giản sử dụng các lĩnh vực công cộng sau đó?

Ý nghĩa là bạn không cần phải làm điều đó .

Đóng gói có nghĩa là bạn chỉ hiển thị những trường mà bạn thực sự cần các lớp khác để truy cập và bạn rất chọn lọc và cẩn thận về nó.

Đừng không chỉ cung cấp cho tất cả các lĩnh vực getter và setter mỗi mặc định!

Điều đó hoàn toàn trái với tinh thần của đặc tả JavaBeans, điều trớ trêu là khái niệm về các công cụ và setters công cộng xuất phát từ đâu.

Nhưng nếu bạn nhìn vào thông số kỹ thuật, bạn sẽ thấy rằng nó dự định việc tạo ra các getters và setters sẽ rất chọn lọc và nó nói về các thuộc tính "chỉ đọc" (không có setter) và các thuộc tính "chỉ ghi" (không nhận được).

Một yếu tố khác là getters và setters không nhất thiết phải truy cập đơn giản vào lĩnh vực riêng tư . Một getter có thể tính toán giá trị mà nó trả về theo cách phức tạp tùy ý, có thể lưu trữ nó. Một setter có thể xác nhận giá trị hoặc thông báo cho người nghe.

Vì vậy, có bạn: đóng gói có nghĩa là bạn chỉ phơi bày chức năng mà bạn thực sự cần phải phơi bày. Nhưng nếu bạn không nghĩ về những gì bạn cần phơi bày và chỉ cần phơi bày mọi thứ bằng cách làm theo một số chuyển đổi cú pháp thì tất nhiên đó không thực sự là sự gói gọn.


20
"[G] etters và setters không nhất thiết phải là truy cập đơn giản" - Tôi nghĩ đó là một điểm chính. Nếu mã của bạn đang truy cập vào một trường theo tên, bạn không thể thay đổi hành vi sau này. Nếu bạn đang sử dụng obj.get (), bạn có thể.
Dan Ambrogio

4
@jrh: Tôi chưa bao giờ nghe thấy nó được gọi là một thực tiễn xấu trong Java và nó khá phổ biến.
Michael Borgwardt

2
@MichaelBorgwardt Thú vị; hơi lạc đề nhưng tôi luôn tự hỏi tại sao Microsoft lại khuyên không nên làm điều đó cho C #. Tôi đoán những nguyên tắc đó ngụ ý rằng trong C #, điều duy nhất setters có thể được sử dụng là chuyển đổi một giá trị được cung cấp cho một số giá trị khác được sử dụng trong nội bộ, theo cách không thể thất bại hoặc có giá trị không hợp lệ (ví dụ: có thuộc tính PositionInches và thuộc tính PositionCentimet nơi nó tự động chuyển đổi nội bộ thành mm)? Không phải là một ví dụ tuyệt vời nhưng đó là điều tốt nhất tôi có thể đưa ra vào lúc này.
jrh

2
@jrh nguồn đó nói không làm điều đó cho getters, trái ngược với setters.
Thuyền trưởng Man

3
@Gangnus và bạn thực sự thấy ai đó đang ẩn một số logic bên trong getter / setter? Chúng tôi đã quá quen với việc getFieldName()trở thành một hợp đồng tự động cho chúng tôi. Chúng tôi sẽ không mong đợi một số hành vi phức tạp đằng sau đó. Trong hầu hết các trường hợp, nó chỉ đơn giản là phá vỡ đóng gói.
Izbassar Tolegen

17

Tôi nghĩ rằng mấu chốt của vấn đề được giải thích bằng bình luận của bạn:

Tôi hoàn toàn đồng ý với suy nghĩ của bạn. Nhưng ở đâu đó chúng ta phải tải các đối tượng với dữ liệu. Ví dụ: từ XML. Và các nền tảng hiện tại hỗ trợ nó thực hiện điều đó thông qua getters / setters, do đó làm giảm chất lượng mã và cách suy nghĩ của chúng tôi. Lombok, thực sự, bản thân nó không tệ, nhưng chính sự tồn tại của nó cho thấy chúng ta có một cái gì đó xấu.

Vấn đề bạn gặp phải là bạn đang trộn datamodel bền bỉ với datamodel đang hoạt động .

Một ứng dụng thường sẽ có nhiều datamodels:

  • một datamodel để nói chuyện với cơ sở dữ liệu,
  • một datamodel để đọc tệp cấu hình,
  • một datamodel để nói chuyện với một ứng dụng khác,
  • ...

trên đỉnh của datamodel, nó thực sự sử dụng để thực hiện các tính toán của nó.

Nói chung, các datamodel được sử dụng để liên lạc với bên ngoài nên được tách biệtđộc lập với datamodel bên trong (mô hình đối tượng kinh doanh, BOM) trên đó tính toán được thực hiện:

  • độc lập : để bạn có thể thêm / xóa các thuộc tính trên BOM phù hợp với yêu cầu của bạn mà không phải thay đổi tất cả máy khách / máy chủ, ...
  • bị cô lập : để tất cả các tính toán được thực hiện trên BOM, nơi bất biến sống và việc thay đổi từ dịch vụ này sang dịch vụ khác hoặc nâng cấp một dịch vụ, không gây ra sự gợn sóng trong cơ sở mã.

Trong kịch bản này, việc các đối tượng được sử dụng trong các lớp truyền thông hoàn toàn công khai hoặc phơi bày bởi getters / setters là hoàn toàn tốt. Đó là những đối tượng đơn giản không có bất cứ điều gì.

Mặt khác, BOM của bạn nên có bất biến, thường không có nhiều setters (getters không ảnh hưởng đến bất biến, mặc dù chúng làm giảm đóng gói ở mức độ).


3
Tôi sẽ không tạo ra getters và setters cho các đối tượng truyền thông. Tôi chỉ muốn công khai các lĩnh vực. Tại sao tạo ra nhiều công việc hơn là hữu ích?
Immibis

3
@immibis: Tôi đồng ý nói chung, tuy nhiên theo ghi nhận của OP, một số khung yêu cầu getters / setters, trong trường hợp đó bạn chỉ cần tuân thủ. Lưu ý rằng OP đang sử dụng một thư viện tự động tạo chúng với ứng dụng của một thuộc tính duy nhất, vì vậy đó là công việc nhỏ đối với anh ta.
Matthieu M.

1
@Gangnus: Ý tưởng ở đây là các getters / setters được cách ly với lớp ranh giới (I / O), nơi nó bị bẩn bình thường, nhưng phần còn lại của ứng dụng (mã thuần) không bị ô nhiễm và vẫn thanh lịch.
Matthieu M.

2
@kubanchot: định nghĩa chính thức là Mô hình đối tượng kinh doanh theo như tôi biết. Nó tương ứng ở đây với cái mà tôi gọi là "datamodel bên trong", datamodel mà bạn thực thi logic trong lõi "thuần" của ứng dụng của bạn.
Matthieu M.

1
Đây là cách tiếp cận thực dụng. Chúng ta sống trong một thế giới lai. Nếu các thư viện bên ngoài có thể biết dữ liệu nào sẽ truyền cho các nhà xây dựng BOM, thì chúng ta sẽ chỉ có BOM.
Adrian Iftode

12

Hãy xem xét những điều sau đây ..

Bạn có một Userlớp học với một tài sản int age:

class User {
    int age;
}

Bạn muốn mở rộng quy mô này để Usercó ngày sinh, trái ngược với độ tuổi. Sử dụng một getter:

class User {
    private int age;

    public int getAge() {
        return age;
    }
}

Chúng ta có thể trao đổi int agetrường cho phức tạp hơn LocalDate dateOfBirth:

class User {
    private LocalDate dateOfBirth;

    public int getAge() {
        LocalDate now = LocalDate.now();
        int year = ...; // calculate using dateOfBirth and now
        return year;
    }

    // other behaviors can now make use of dateOfBirth
}

Không vi phạm hợp đồng, không phá vỡ mã .. Không gì khác hơn là tăng quy mô đại diện nội bộ để chuẩn bị cho các hành vi phức tạp hơn.

Các lĩnh vực được đóng gói.


Bây giờ, để xóa các mối quan tâm ..

@DataChú thích của Lombok tương tự như các lớp dữ liệu của Kotlin .

Không phải tất cả các lớp đại diện cho các đối tượng hành vi. Đối với việc phá vỡ đóng gói, nó phụ thuộc vào việc sử dụng tính năng của bạn. Bạn không nên để lộ tất cả các lĩnh vực của bạn thông qua getters.

Đóng gói được sử dụng để ẩn các giá trị hoặc trạng thái của một đối tượng dữ liệu có cấu trúc bên trong một lớp

Nói một cách khái quát hơn, đóng gói là hành động che giấu thông tin. Nếu bạn lạm dụng @Data, thật dễ dàng để cho rằng bạn có thể phá vỡ đóng gói. Nhưng điều đó không có nghĩa là nó không có mục đích. JavaBeans, ví dụ, được một số người cau mày. Tuy nhiên, nó được sử dụng rộng rãi trong phát triển doanh nghiệp.

Bạn có thể kết luận rằng phát triển doanh nghiệp là xấu, do sử dụng đậu? Dĩ nhiên là không! Các yêu cầu khác với phát triển tiêu chuẩn. Đậu có thể bị lạm dụng? Tất nhiên! Họ bị lạm dụng mọi lúc!

Lombok cũng hỗ trợ @Getter@Setterđộc lập - sử dụng những gì yêu cầu của bạn yêu cầu.


2
Điều này không liên quan đến việc tát @Datachú thích vào một loại, mà theo thiết kế không bao gồm tất cả các trường
Caleth

1
Không, các lĩnh vực không được ẩn . Bởi vì bất cứ điều gì cũng có thể đi cùng vàsetAge(xyz)
Caleth

9
@Caleth Các trường được ẩn. Bạn đang hành động như thể setters không thể có các điều kiện trước và sau, đây là trường hợp sử dụng rất phổ biến để sử dụng setters. Bạn đang hành động như thể một thuộc tính trường ==, thậm chí trong các ngôn ngữ như C #, không đúng, vì cả hai đều có xu hướng được hỗ trợ (như trong C #). Trường có thể được chuyển sang một lớp khác, nó có thể được hoán đổi cho một đại diện phức tạp hơn ...
Vince Emigh

1
Và điều tôi đang nói là điều đó không quan trọng
Caleth

2
@Caleth Làm thế nào nó không quan trọng? Điều đó không có nghĩa gì cả. Bạn đang nói đóng gói không áp dụng vì bạn cảm thấy nó không quan trọng trong tình huống này, mặc dù nó được áp dụng theo định nghĩa và có trường hợp sử dụng không?
Vince Emigh

11

Đóng gói bảo tôi làm cho tất cả hoặc gần như tất cả các trường riêng tư và hiển thị chúng bằng getters / setters.

Đó không phải là cách đóng gói được định nghĩa trong lập trình hướng đối tượng. Đóng gói có nghĩa là mỗi đối tượng phải giống như một viên nang, có vỏ ngoài (api công khai) bảo vệ và điều chỉnh quyền truy cập vào bên trong của nó (các phương thức và trường riêng) và che giấu nó khỏi tầm nhìn. Bằng cách ẩn nội bộ, người gọi không phụ thuộc vào nội bộ, cho phép thay đổi nội bộ mà không thay đổi (hoặc thậm chí biên dịch lại) người gọi. Ngoài ra, đóng gói cho phép mỗi đối tượng thực thi các bất biến riêng của mình, bằng cách chỉ cung cấp các hoạt động an toàn cho người gọi.

Do đó, đóng gói là một trường hợp đặc biệt của việc che giấu thông tin, trong đó mỗi đối tượng che giấu phần bên trong của nó và thực thi các bất biến của nó.

Tạo getters và setters cho tất cả các trường là một hình thức đóng gói khá yếu, bởi vì cấu trúc của dữ liệu nội bộ không bị ẩn và bất biến không thể được thực thi. Tuy nhiên, có một lợi thế là bạn có thể thay đổi cách lưu trữ dữ liệu bên trong (miễn là bạn có thể chuyển đổi sang và từ cấu trúc cũ) mà không phải thay đổi (hoặc thậm chí biên dịch lại) người gọi.

Ai đó có thể giải thích cho tôi ý nghĩa của việc che giấu tất cả các lĩnh vực là riêng tư và sau đó để phơi bày tất cả chúng bằng một số công nghệ bổ sung? Tại sao chúng ta chỉ đơn giản là không sử dụng các lĩnh vực công cộng sau đó? Tôi cảm thấy chúng tôi đã đi một chặng đường dài và khó khăn chỉ để trở về điểm xuất phát.

Một phần, đây là do một tai nạn lịch sử. Điều đáng chú ý là, trong Java, các biểu thức gọi phương thức và biểu thức truy cập trường khác nhau về mặt cú pháp tại trang web cuộc gọi, tức là thay thế một truy cập trường bằng lệnh gọi getter hoặc setter phá vỡ API của một lớp. Do đó, nếu bạn có thể cần một người truy cập, bạn phải viết ngay bây giờ hoặc có thể phá vỡ API. Sự vắng mặt của hỗ trợ thuộc tính cấp ngôn ngữ này trái ngược hoàn toàn với các ngôn ngữ hiện đại khác, đáng chú ý nhất là C #EcmaScript .

Đột kích 2 là đặc tả JavaBeans xác định các thuộc tính là getters / setters, các trường không phải là thuộc tính. Kết quả là, hầu hết các khung doanh nghiệp ban đầu đều hỗ trợ getters / setters, nhưng không phải các trường. Điều đó đã tồn tại từ lâu ( API liên tục Java (JPA) , Xác thực Bean , Kiến trúc Java cho liên kết XML (JAXB) , Jacksonbây giờ tất cả các lĩnh vực hỗ trợ đều ổn), nhưng các hướng dẫn và sách cũ vẫn tiếp tục tồn tại và không phải ai cũng biết rằng mọi thứ đã thay đổi. Sự vắng mặt của hỗ trợ thuộc tính cấp độ ngôn ngữ vẫn có thể là một vấn đề khó khăn trong một số trường hợp (ví dụ vì JPA lười tải các thực thể đơn lẻ không kích hoạt khi các trường công cộng được đọc), nhưng hầu hết các trường công cộng chỉ hoạt động tốt. Nói một cách dí dỏm, công ty của tôi viết tất cả các DTO cho API REST của họ bằng các trường công khai (sau tất cả, nó không được công khai hơn truyền qua internet :-).

Điều đó nói rằng, Lombok @Datakhông chỉ tạo ra getters / setters: Nó cũng tạo ra toString(), hashCode()equals(Object), có thể khá có giá trị.

Và có đóng gói thực sự bất kỳ ý nghĩa bây giờ trong lập trình thực tế?

Đóng gói có thể là vô giá hoặc hoàn toàn vô dụng, nó phụ thuộc vào đối tượng được đóng gói. Nói chung, logic càng phức tạp trong lớp, lợi ích của việc đóng gói càng lớn.

Các getters và setters được tạo tự động cho mọi trường thường được sử dụng quá mức, nhưng có thể hữu ích để làm việc với các khung kế thừa hoặc để sử dụng tính năng khung thỉnh thoảng không được hỗ trợ cho các trường.

Đóng gói có thể đạt được với getters và phương pháp lệnh. Setters thường không phù hợp, bởi vì chúng được dự kiến ​​sẽ chỉ thay đổi một trường duy nhất, trong khi việc duy trì bất biến có thể yêu cầu một số trường phải được thay đổi cùng một lúc.

Tóm lược

getters / setters cung cấp đóng gói khá nghèo.

Sự phổ biến của getters / setters trong Java xuất phát từ việc thiếu hỗ trợ mức độ ngôn ngữ cho các thuộc tính và các lựa chọn thiết kế đáng ngờ trong mô hình thành phần lịch sử của nó đã được lưu giữ trong nhiều tài liệu giảng dạy và các lập trình viên được dạy bởi chúng.

Các ngôn ngữ hướng đối tượng khác, chẳng hạn như EcmaScript, thực hiện các thuộc tính hỗ trợ ở cấp ngôn ngữ, do đó, getters có thể được giới thiệu mà không phá vỡ API. Trong các ngôn ngữ như vậy, getters có thể được giới thiệu khi bạn thực sự cần chúng, thay vì trước thời hạn, chỉ trong trường hợp bạn có thể cần nó một ngày, mang lại trải nghiệm lập trình dễ chịu hơn nhiều.


1
thực thi bất biến của nó? Có phải tiếng anh không Bạn có thể giải thích nó cho một người không phải người Anh, xin vui lòng? Đừng quá phức tạp - một số người ở đây không biết tiếng Anh đủ tốt.
Gangnus

Tôi thích cơ sở của bạn, tôi thích suy nghĩ của bạn (+1), nhưng không phải là kết quả thực tế. Tôi thà sử dụng trường riêng và một số thư viện đặc biệt để tải dữ liệu cho họ bằng phản xạ. Tôi chỉ không hiểu tại sao bạn lại đặt câu trả lời của bạn như một cuộc tranh cãi với tôi?
Gangnus

@Gangnus Một bất biến là một điều kiện logic không thay đổi (có nghĩa là không và ý nghĩa khác nhau thay đổi / thay đổi). Nhiều hàm có các quy tắc phải đúng trước và sau khi chúng thực thi (được gọi là điều kiện trước và sau điều kiện) khác có lỗi trong mã. Ví dụ, một hàm có thể yêu cầu đối số của nó không phải là null (điều kiện trước) và có thể đưa ra một ngoại lệ nếu đối số của nó là null vì trường hợp đó sẽ là một lỗi trong mã (tức là trong khoa học máy tính, mã đã bị hỏng bất biến).
Pharap

@Gangus: Không hoàn toàn chắc chắn nơi phản ánh tham gia vào cuộc thảo luận này; Lombok là một bộ xử lý chú thích, tức là một plugin cho trình biên dịch Java phát ra mã bổ sung trong quá trình biên dịch. Nhưng chắc chắn, bạn có thể sử dụng lombok. Tôi chỉ nói rằng trong nhiều trường hợp, các trường công cộng cũng hoạt động tốt và dễ cài đặt hơn (không phải tất cả các trình biên dịch đều tự động phát hiện các bộ xử lý chú thích ...).
meriton

1
@Gangnus: Bất biến là giống nhau trong toán học và CS, nhưng trong CS có một quan điểm bổ sung: Từ góc nhìn của một đối tượng, bất biến phải được thực thi và thiết lập. Từ quan điểm của một người gọi đối tượng đó, bất biến luôn luôn đúng.
meriton

7

Tôi đã tự hỏi mình câu hỏi đó thực sự.

Nó không hoàn toàn đúng mặc dù. Sự phổ biến của getters / setters IMO là do đặc tả Java Bean, đòi hỏi nó; vì vậy, đây là một tính năng không phải là lập trình hướng đối tượng mà là lập trình hướng Bean, nếu bạn muốn. Sự khác biệt giữa hai là lớp trừu tượng mà chúng tồn tại; Đậu có nhiều giao diện hệ thống, tức là ở lớp cao hơn. Chúng trừu tượng từ nền tảng OO, hoặc ít nhất là - như mọi khi, mọi thứ đang được điều khiển quá xa thường xuyên.

Tôi có thể nói hơi đáng tiếc rằng điều Bean này có mặt ở khắp nơi trong lập trình Java không đi kèm với việc bổ sung một tính năng ngôn ngữ Java tương ứng - Tôi đang nghĩ về một cái gì đó giống như khái niệm Properties trong C #. Đối với những người không biết về nó, đó là một cấu trúc ngôn ngữ trông như thế này:

class MyClass {
    string MyProperty { get; set; }
}

Dù sao, nitty-gritty của việc thực hiện thực tế vẫn còn rất nhiều lợi ích từ việc đóng gói.


Python có một tính năng tương tự trong đó các thuộc tính có thể có getters / setters trong suốt. Tôi chắc chắn thậm chí còn có nhiều ngôn ngữ có tính năng tương tự.
JAB

1
"Sự phổ biến của getters / setters IMO là do đặc tả Java Bean, đòi hỏi nó" Có, bạn đã đúng. Và ai nói nó phải được yêu cầu? Lý do, xin vui lòng?
Gangnus

2
Cách C # hoàn toàn giống với Lombok. Tôi không thấy bất kỳ sự khác biệt thực sự. Tiện lợi hơn thực tế, nhưng rõ ràng là xấu trong việc thụ thai.
Gangnus

1
@Gangni Các đặc điểm kỹ thuật đòi hỏi nó. Tại sao? Kiểm tra câu trả lời của tôi để biết ví dụ cụ thể về lý do tại sao getters không giống như phơi bày các trường - hãy thử đạt được điều tương tự mà không có getter.
Vince Emigh

@VinceEmigh gangnUs, vui lòng :-). (gang-walk, nus - nut). Và những gì về việc sử dụng get / set chỉ khi chúng ta đặc biệt cần nó và tải hàng loạt vào các trường riêng bằng cách phản ánh trong các trường hợp khác?
Gangnus

6

Ai đó có thể giải thích cho tôi, ý nghĩa của việc che giấu tất cả các lĩnh vực là riêng tư và sau đó để phơi bày tất cả chúng bằng một số công nghệ bổ sung? Tại sao chúng ta không chỉ đơn giản sử dụng các lĩnh vực công cộng sau đó? Tôi cảm thấy chúng tôi đã đi một chặng đường dài và khó khăn chỉ để trở lại điểm bắt đầu.

Câu trả lời đơn giản ở đây là: bạn hoàn toàn đúng. Getters và setters loại bỏ hầu hết (nhưng không phải tất cả) giá trị của đóng gói. Điều này không có nghĩa là bất cứ khi nào bạn có một phương thức get và / hoặc set mà bạn đang phá vỡ đóng gói nhưng nếu bạn đang mù quáng thêm người truy cập vào tất cả các thành viên riêng của lớp, bạn đã làm sai.

Vâng, có những công nghệ khác hoạt động thông qua getters và setters. Và chúng ta không thể sử dụng chúng thông qua các lĩnh vực công cộng đơn giản. Nhưng những công nghệ này chỉ xuất hiện bởi vì chúng tôi có nhiều tài sản đó - các lĩnh vực riêng tư đằng sau getters / setters công cộng. Nếu chúng ta không có tài sản, các công nghệ này sẽ phát triển theo cách khác và sẽ hỗ trợ các lĩnh vực công cộng. Và mọi thứ sẽ đơn giản và chúng tôi sẽ không có nhu cầu về Lombok bây giờ. Ý nghĩa của toàn bộ chu kỳ này là gì? Và có đóng gói thực sự bất kỳ ý nghĩa bây giờ trong lập trình thực tế?

Getters là setters có mặt khắp nơi trong lập trình Java vì khái niệm JavaBean được đẩy như một cách để tự động liên kết chức năng với mã được xây dựng trước. Ví dụ: bạn có thể có một biểu mẫu trong một applet (có ai nhớ chúng không?) Sẽ kiểm tra đối tượng của bạn, tìm tất cả các thuộc tính và hiển thị dưới dạng các trường. Sau đó, UI có thể sửa đổi các thuộc tính đó dựa trên đầu vào của người dùng. Bạn là nhà phát triển sau đó chỉ lo lắng về việc viết lớp và đặt bất kỳ xác nhận hợp lý hoặc logic kinh doanh nào ở đó, v.v.

nhập mô tả hình ảnh ở đây

Sử dụng đậu ví dụ

Đây không phải là một ý tưởng tồi tệ nhưng tôi chưa bao giờ là một fan hâm mộ lớn của cách tiếp cận trong Java. Nó chỉ đi ngược lại với ngũ cốc. Sử dụng Python, Groovy, vv Một cái gì đó hỗ trợ cách tiếp cận này một cách tự nhiên hơn.

Điều JavaBean loại bỏ khỏi tầm kiểm soát vì nó đã tạo ra JOBOL tức là các nhà phát triển viết Java không hiểu OO. Về cơ bản, các đối tượng trở thành không có gì khác ngoài các túi dữ liệu và tất cả logic được viết bên ngoài theo các phương thức dài. Bởi vì nó được coi là bình thường, những người như bạn và tôi, những người nghi ngờ điều này được coi là kooks. Gần đây tôi đã thấy một sự thay đổi và đây không phải là một vị trí bên ngoài

Điều ràng buộc XML là một điều khó khăn. Đây có lẽ không phải là một chiến trường tốt để tạo lập trường chống lại JavaBeans. Nếu bạn phải xây dựng các JavaBeans này, hãy cố gắng tránh chúng ra khỏi mã thực. Coi chúng là một phần của lớp tuần tự hóa


2
Những suy nghĩ cụ thể và khôn ngoan nhất. +1. Nhưng tại sao không viết một nhóm các lớp, trong đó mỗi lớp sẽ ồ ạt tải / lưu các trường riêng vào / từ một số cấu trúc bên ngoài? JSON, XML, đối tượng khác. Nó có thể hoạt động với POJO hoặc, có thể, chỉ với các trường, được đánh dấu bằng chú thích cho điều đó. Bây giờ tôi đang suy nghĩ về sự thay thế đó.
Gangnus

@Gangnus Tại một dự đoán bởi vì điều đó có nghĩa là rất nhiều mã viết thêm. Nếu sự phản chiếu được sử dụng thì chỉ một khối mã tuần tự phải được viết và nó có thể tuần tự hóa bất kỳ lớp nào theo mẫu cụ thể. C # hỗ trợ điều này thông qua [Serializable]thuộc tính và họ hàng của nó. Tại sao viết 101 phương thức / lớp tuần tự hóa cụ thể khi bạn chỉ có thể viết một phương thức bằng cách tận dụng sự phản chiếu?
Pharap

@Pharap Tôi rất xin lỗi, nhưng nhận xét của bạn quá ngắn đối với tôi. "Tận dụng sự phản chiếu"? Và ý nghĩa của việc giấu những thứ khác nhau vào một lớp là gì? Bạn có thể giải thích những gì bạn có ý nghĩa?
Gangnus

@Gangnus Ý tôi là sự phản chiếu , khả năng mã để kiểm tra cấu trúc của chính nó. Trong Java (và các ngôn ngữ khác), bạn có thể nhận được một danh sách tất cả các hàm mà một lớp trưng ra và sau đó sử dụng chúng như một cách trích xuất dữ liệu từ lớp được yêu cầu để tuần tự hóa nó thành ví dụ XML / JSON. Sử dụng sự phản chiếu sẽ là công việc một lần thay vì liên tục tạo ra các phương thức mới để lưu các cấu trúc trên cơ sở mỗi lớp. Đây là một ví dụ Java đơn giản .
Pharap

@Pharap Đó chỉ là những gì tôi đã nói. Nhưng tại sao bạn gọi nó là "đòn bẩy"? Và lựa chọn thay thế mà tôi đã đề cập trong bình luận đầu tiên ở đây vẫn còn - các trường đóng gói có nên chú thích đặc biệt hay không?
Gangnus

5

Chúng ta có thể làm được bao nhiêu nếu không có getters? Có thể loại bỏ hoàn toàn chúng? Những vấn đề này tạo ra? Chúng tôi thậm chí có thể cấm returntừ khóa?

Hóa ra bạn có thể làm rất nhiều nếu bạn sẵn sàng làm việc. Làm thế nào để thông tin bao giờ thoát ra khỏi đối tượng được đóng gói đầy đủ này? Thông qua một cộng tác viên.

Thay vì để mã hỏi bạn những câu hỏi bạn nói để làm mọi thứ. Nếu những thứ đó cũng không trả lại những thứ bạn không phải làm bất cứ điều gì về những gì chúng trả lại. Vì vậy, khi bạn nghĩ đến việc tìm kiếm một returnthay vào đó hãy thử tiếp cận với một số cộng tác viên cổng đầu ra sẽ làm bất cứ điều gì còn lại phải làm.

Làm những việc theo cách này có lợi ích và hậu quả. Bạn phải suy nghĩ về nhiều thứ hơn là những gì bạn sẽ trả lại. Bạn phải suy nghĩ về cách bạn sẽ gửi nó dưới dạng tin nhắn đến một đối tượng không yêu cầu nó. Nó có thể là bạn phát ra cùng một đối tượng bạn sẽ trả lại, hoặc có thể chỉ cần gọi một phương thức là đủ. Làm suy nghĩ đó phải trả giá.

Lợi ích là bây giờ bạn đang nói chuyện với giao diện. Điều này có nghĩa là bạn có được lợi ích đầy đủ của sự trừu tượng.

Nó cũng cung cấp cho bạn công văn đa hình, bởi vì trong khi bạn biết những gì bạn đang nói, bạn không cần phải biết chính xác những gì bạn đang nói.

Bạn có thể nghĩ rằng điều này có nghĩa là bạn chỉ phải đi một chiều qua một lớp các lớp, nhưng hóa ra bạn có thể sử dụng đa hình để đi ngược mà không tạo ra các phụ thuộc tuần hoàn điên rồ.

Nó có thể trông như thế này:

Nhập mô tả hình ảnh ở đây

class Interactor implements InputPort {
    OutputPort out;
    int y = 0;

    Interactor(OutputPort out){
        this.out = out;
    }

    void accumulate(int x) {
        y = y + x;
        out.showAsImage(y);
    }
}

Nếu bạn có thể viết mã như vậy thì sử dụng getters là một lựa chọn. Không phải là một điều ác cần thiết.


Vâng, getters / setters như vậy có ý nghĩa tuyệt vời. Nhưng bạn có hiểu nó là về cái gì khác? :-) +1 nào.
Gangnus

1
@Gangnus Cảm ơn bạn. Có một loạt những điều bạn có thể nói về. Nếu bạn quan tâm đến ít nhất là đặt tên cho chúng, tôi có thể tìm hiểu chúng.
candied_orange

5

Đây là một vấn đề gây tranh cãi (như bạn có thể thấy) bởi vì một loạt các giáo điều và hiểu lầm được trộn lẫn với các mối quan tâm hợp lý liên quan đến vấn đề của getters và setters. Nhưng trong ngắn hạn, không có gì sai @Datavà nó không phá vỡ đóng gói.

Tại sao sử dụng getters và setters chứ không phải là lĩnh vực công cộng?

Bởi vì getters / setters cung cấp đóng gói. Nếu bạn trưng ra một giá trị dưới dạng trường công khai và sau đó thay đổi thành tính toán giá trị một cách nhanh chóng, thì bạn cần sửa đổi tất cả các máy khách truy cập vào trường. Rõ ràng điều này là xấu. Đây là một chi tiết triển khai cho dù một số thuộc tính của một đối tượng được lưu trữ trong một trường, được tạo ra khi đang bay hoặc được tìm nạp từ một nơi khác, do đó, sự khác biệt không nên được đưa ra cho khách hàng. Getter / setters setter giải quyết điều này, vì chúng ẩn việc thực hiện.

Nhưng nếu getter / setter chỉ phản ánh một trường riêng bên dưới thì nó có tệ như vậy không?

Không! Vấn đề là đóng gói cho phép bạn thay đổi việc thực hiện mà không ảnh hưởng đến khách hàng. Một lĩnh vực vẫn có thể là một cách hoàn hảo tốt để lưu trữ giá trị, miễn là khách hàng không phải biết hoặc quan tâm.

Nhưng không phải là tự động tạo ra getters / setters từ các trường phá vỡ đóng gói?

Không có sự đóng gói vẫn còn đó! @Datachú thích chỉ là một cách thuận tiện để viết các cặp getter / setter sử dụng một trường bên dưới. Đối với quan điểm của một khách hàng, đây giống như một cặp getter / setter thông thường. Nếu bạn quyết định viết lại việc thực hiện, bạn vẫn có thể thực hiện nó mà không ảnh hưởng đến máy khách. Vì vậy, bạn có được tốt nhất của cả hai thế giới: cú pháp đóng gói cú pháp súc tích.

Nhưng một số người nói getter / setters luôn xấu!

Có một cuộc tranh cãi riêng, trong đó một số người tin rằng mô hình getter / setter luôn xấu, bất kể việc triển khai cơ bản là gì. Ý tưởng là bạn không nên thiết lập hoặc nhận các giá trị từ các đối tượng, thay vào đó, bất kỳ tương tác nào giữa các đối tượng nên được mô hình hóa thành các thông điệp trong đó một đối tượng yêu cầu một đối tượng khác làm gì đó. Đây chủ yếu là một phần của giáo điều từ những ngày đầu suy nghĩ hướng đối tượng. Suy nghĩ bây giờ là đối với các mẫu nhất định (ví dụ: đối tượng giá trị, đối tượng truyền dữ liệu) getters / setters có thể hoàn toàn phù hợp.


Đóng gói nên cho phép chúng tôi đa hình và an toàn. Gets / bộ cho phép linh sam, nhưng mạnh mẽ so với thứ hai.
Gangnus

Nếu bạn nói tôi sử dụng giáo điều ở đâu đó, làm ơn, hãy nói, văn bản của tôi là giáo điều. Và đừng quên rằng một số suy nghĩ tôi đang sử dụng tôi không thích hoặc đồng ý. Tôi đang sử dụng chúng để chứng minh một mâu thuẫn.
Gangnus

1
"GangNus" :-). Một tuần trước, tôi đã làm việc trong một dự án với đầy đủ các cách gọi getters và setters trên nhiều lớp. Nó hoàn toàn không an toàn, và thậm chí tệ hơn, nó hỗ trợ mọi người trong việc mã hóa không an toàn. Tôi vui vì tôi đã thay đổi công việc đủ nhanh, vì mọi người đã quen với cách đó. Vì vậy, tôi không nghĩ rằng nó, tôi thấy nó .
Gangnus

2
@jrh: Tôi không biết có giải thích chính thức như vậy không, nhưng C # có hỗ trợ tích hợp cho các thuộc tính (getters / setters với cú pháp đẹp hơn) và cho các loại ẩn danh là loại chỉ có thuộc tính và không có hành vi. Vì vậy, các nhà thiết kế của C # đã cố tình chuyển hướng khỏi ẩn dụ nhắn tin và nguyên tắc "nói, đừng hỏi". Sự phát triển của C # kể từ phiên bản 2.0 dường như có nhiều cảm hứng từ các ngôn ngữ chức năng hơn là độ tinh khiết OO truyền thống.
JacquesB

1
@jrh: Bây giờ tôi đoán, nhưng tôi nghĩ C # được truyền cảm hứng khá nhiều bởi các ngôn ngữ chức năng nơi dữ liệu và hoạt động tách biệt hơn. Trong các kiến ​​trúc phân tán, phối cảnh cũng đã chuyển từ hướng thông điệp (RPC, SOAP) sang hướng dữ liệu (REST). Và cơ sở dữ liệu phơi bày và hoạt động trên dữ liệu (không phải đối tượng "hộp đen") đã chiếm ưu thế. Nói tóm lại, tôi nghĩ rằng trọng tâm đã chuyển từ tin nhắn giữa các hộp đen sang các mô hình dữ liệu bị lộ
JacquesB

3

Đó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.


Bạn có thể đặt ở đây một ví dụ về việc lạm dụng đóng gói? Điều đó có thể thú vị. Ví dụ của bạn là chống lại sự thiếu đóng gói.
Gangnus

1
@Gangnus Tôi đã thêm một vài ví dụ. Việc đóng gói vô dụng nói chung là sử dụng sai và cũng từ kinh nghiệm của tôi, các nhà phát triển API cố gắng quá mức để buộc bạn sử dụng API theo một cách nhất định. Tôi đã không đưa ra một ví dụ cho điều đó bởi vì tôi không có một cái dễ dàng để trình bày. Nếu tôi tìm thấy một cái tôi chắc chắn sẽ thêm nó. Tôi nghĩ rằng hầu hết các ý kiến ​​chống lại đóng gói thực sự là chống lại cú pháp đóng gói hơn là đóng gói chính nó.
BananyaDev

Cảm ơn đã chỉnh sửa. Tôi đã tìm thấy một lỗi đánh máy nhỏ: "publice" thay vì "công khai".
jrh

2

Đóng gói cho bạn sự linh hoạt . Bằng cách tách cấu trúc và giao diện, nó cho phép bạn thay đổi cấu trúc mà không thay đổi giao diện.

Ví dụ: nếu bạn thấy rằng bạn cần tính toán một thuộc tính dựa trên các trường khác thay vì khởi tạo trường cơ bản khi xây dựng, bạn chỉ cần thay đổi getter. Nếu bạn đã tiếp xúc trực tiếp với trường, thay vào đó, bạn phải thay đổi giao diện và thực hiện thay đổi tại mỗi trang web sử dụng.


Mặc dù về lý thuyết là một ý tưởng hay, hãy nhớ rằng việc khiến phiên bản 2 của API đưa ra ngoại lệ rằng phiên bản 1 sẽ không phá vỡ người dùng thư viện hoặc lớp của bạn một cách khủng khiếp (và có thể âm thầm!); Tôi hơi nghi ngờ rằng bất kỳ thay đổi lớn nào cũng có thể được thực hiện "đằng sau hậu trường" trên hầu hết các lớp dữ liệu này.
jrh

Xin lỗi, nó không phải là một lời giải thích. Việc sử dụng các trường bị cấm trong các giao diện xuất phát từ ý tưởng đóng gói. Và bây giờ chúng ta đang nói về nó. Bạn đang dựa trên các sự kiện đang được thảo luận. (chú ý, tôi không nói chuyện chuyên nghiệp hay chống lại suy nghĩ của bạn, chỉ nói về chúng không thể được sử dụng làm đối số ở đây)
Gangnus

@Gangnus Từ "giao diện" có nghĩa ngoài từ khóa Java: dictionary.com/browse/interface
glennsl

@jrh Đó là một vấn đề hoàn toàn khác. Bạn có thể sử dụng nó để tranh luận về việc đóng gói trong các bối cảnh nhất định , nhưng nó không làm mất hiệu lực các đối số cho nó.
glennsl

1
Việc đóng gói cũ, không có get / set hàng loạt đã cho chúng tôi không chỉ Đa hình, mà còn cả sự an toàn. Và tập hợp lớn / được dạy chúng ta đối với thực tiễn xấu.
Gangnus

2

Tôi sẽ cố gắng minh họa không gian vấn đề của đóng gói và thiết kế lớp, và trả lời câu hỏi của bạn ở cuối.

Như đã đề cập trong các câu trả lời khác, mục đích của việc đóng gói là để ẩn các chi tiết bên trong của một đối tượng đằng sau một API công khai, phục vụ như một hợp đồng. Đối tượng an toàn để thay đổi nội bộ của nó vì nó biết nó chỉ được gọi thông qua API công khai.

Việc có các trường công khai, hoặc getters / setters, hoặc các phương thức giao dịch hoặc thông điệp cấp cao hơn có ý nghĩa hay không tùy thuộc vào bản chất của miền đang được mô hình hóa. Trong cuốn sách Akka Concurrency (mà tôi có thể giới thiệu ngay cả khi nó đã lỗi thời), bạn tìm thấy một ví dụ minh họa điều này, mà tôi sẽ viết tắt ở đây.

Xem xét một lớp người dùng:

public class User {
  private String first = "";
  private String last = "";

  public String getFirstName() {
    return this.first;
  }
  public void setFirstName(String s) {
    this.first = s;
  }

  public String getLastName() {
    return this.last;
  }
  public void setLastName(String s) {
    this.last = s;
  }
}

Điều này hoạt động tốt trong một bối cảnh đơn luồng. Tên miền đang được mô hình hóa là tên của một người và các cơ chế về cách lưu trữ tên đó có thể được đóng gói hoàn hảo bởi các setters.

Tuy nhiên, hãy tưởng tượng điều này phải được cung cấp trong bối cảnh đa luồng. Giả sử một chủ đề đang định kỳ đọc tên:

System.out.println(user.getFirstName() + " " + user.getLastName());

Và hai chủ đề khác đang chiến đấu giằng xé, lần lượt đặt nó cho Hillary ClintonDonald Trump . Mỗi người cần gọi hai phương thức. Chủ yếu là điều này hoạt động tốt, nhưng thỉnh thoảng bạn sẽ thấy một Hillary Trump hoặc Donald Clinton đi ngang qua.

Bạn không thể giải quyết vấn đề này bằng cách thêm một khóa bên trong setters, vì khóa chỉ được giữ trong thời gian đặt tên đầu tiên hoặc tên cuối cùng. Giải pháp duy nhất thông qua khóa là thêm một khóa xung quanh toàn bộ đối tượng, nhưng phá vỡ đóng gói vì mã cuộc gọi phải quản lý khóa (và có thể gây ra bế tắc).

Hóa ra, không có giải pháp sạch thông qua khóa. Giải pháp sạch sẽ là đóng gói lại các phần bên trong bằng cách làm cho chúng thô hơn:

public class UserName {
   public final String first;
   public final String last;
   public UserName(String first, String last) { ... }
}

public class User
   private UserName name;
   public UserName getName() { return this.name; }
   public setName(UserName n) { this.name = n; }
}

Tên của nó đã trở nên bất biến, và bạn thấy rằng các thành viên của nó có thể được công khai, bởi vì bây giờ nó là một đối tượng dữ liệu thuần túy không có khả năng sửa đổi nó một khi được tạo. Đổi lại, API công khai của lớp Người dùng đã trở nên thô hơn, chỉ còn lại một bộ cài đặt, do đó tên chỉ có thể được thay đổi toàn bộ. Nó gói gọn nhiều trạng thái bên trong của nó đằng sau API.

Ý nghĩa của toàn bộ chu kỳ này là gì? Và có đóng gói thực sự bất kỳ ý nghĩa bây giờ trong lập trình thực tế?

Những gì bạn thấy trong chu trình này là những nỗ lực áp dụng các giải pháp tốt cho một tập hợp hoàn cảnh cụ thể quá rộng. Một mức độ đóng gói phù hợp đòi hỏi phải hiểu miền được mô hình hóa và áp dụng mức độ đóng gói phù hợp. Đôi khi, điều này có nghĩa là tất cả các trường đều công khai, đôi khi (như trong các ứng dụng Akka) có nghĩa là bạn hoàn toàn không có API công khai ngoại trừ một phương thức duy nhất để nhận tin nhắn. Tuy nhiên, khái niệm đóng gói chính nó, có nghĩa là ẩn các phần bên trong đằng sau một API ổn định, là chìa khóa để lập trình phần mềm ở quy mô, đặc biệt là trong các hệ thống đa luồng.


Tuyệt vời. Tôi muốn nói rằng bạn đã nâng mức thảo luận lên một mức cao hơn. Có lẽ hai. Thực sự, tôi nghĩ rằng tôi hiểu đóng gói và đôi khi tốt hơn những cuốn sách tiêu chuẩn đó, và thực sự khá sợ rằng chúng tôi thực sự đang mất nó do một số tùy chỉnh và công nghệ được chấp nhận, và THAT là chủ đề thực sự của câu hỏi của tôi (có thể, không được hình thành trong một cách tốt), nhưng bạn đã cho tôi thấy những khía cạnh thực sự mới của việc đóng gói. Tôi chắc chắn không giỏi trong đa nhiệm và bạn đã cho tôi thấy nó có hại như thế nào khi hiểu các nguyên tắc cơ bản khác.
Gangnus

Nhưng tôi không thể đánh dấu bài đăng của bạn như các câu trả lời, bởi vì nó không phải là về dữ liệu tải khối lượng đến / từ các đối tượng, các vấn đề do đó nền tảng như đậu và Lombok xuất hiện. Có lẽ, bạn có thể giải thích suy nghĩ của bạn theo hướng đó, xin vui lòng, nếu bạn có thể có thời gian? Bản thân tôi vẫn chưa suy nghĩ lại về hậu quả mà suy nghĩ của bạn mang đến cho vấn đề đó. Và tôi không chắc mình có đủ sức khỏe cho nó không (hãy nhớ rằng, nền đa luồng xấu: - [)
Gangnus

Tôi chưa sử dụng lombok, nhưng theo tôi hiểu thì đó là cách thực hiện một mức độ đóng gói cụ thể (getters / setters trên mọi lĩnh vực) với ít lần gõ hơn. Nó không thay đổi hình dạng lý tưởng của API cho miền vấn đề của bạn, nó chỉ là một công cụ để viết nhanh hơn.
Joeri Sebrechts

1

Tôi có thể nghĩ về một trường hợp sử dụng trong đó điều này có ý nghĩa. Bạn có thể có một lớp mà ban đầu bạn truy cập thông qua API getter / setter đơn giản. Sau đó, bạn mở rộng hoặc sửa đổi để nó không còn sử dụng cùng các trường, nhưng vẫn hỗ trợ cùng API .

Một ví dụ hơi khó hiểu: Điểm bắt đầu như một cặp Cartesian với p.x()p.y(). Sau đó, bạn thực hiện một triển khai hoặc lớp con mới sử dụng tọa độ cực, do đó bạn cũng có thể gọi p.r()p.theta(), nhưng mã máy khách của bạn gọi p.x()p.y()vẫn hợp lệ. Bản thân lớp chuyển đổi một cách trong suốt từ dạng cực bên trong, nghĩa là y()bây giờ return r * sin(theta);. (Trong ví dụ này, chỉ cài đặt x()hoặc y()không có ý nghĩa nhiều, nhưng vẫn có thể.)

Trong trường hợp này, bạn có thể thấy mình nói rằng, Vui mừng khi tôi tự động khai báo getters và setters thay vì để các trường công khai, hoặc tôi đã phải phá vỡ API của mình ở đó.


2
Nó không đẹp như bạn thấy. Thay đổi biểu diễn vật lý bên trong thực sự làm cho đối tượng khác nhau. Và thật tệ khi họ nhìn giống nhau, vì họ có những phẩm chất khác nhau. Hệ thống Descartes không có điểm đặc biệt, hệ cực có một.
Gangnus

1
Nếu bạn không thích ví dụ cụ thể đó, chắc chắn bạn có thể nghĩ về những người khác thực sự có nhiều đại diện tương đương hoặc một đại diện mới hơn có thể được chuyển đổi sang và từ một đại diện cũ hơn.
Davislor

Có, bạn rất có thể sử dụng chúng để trình bày. Trong UI nó có thể được sử dụng rộng rãi. Nhưng không phải bạn bắt tôi phải trả lời câu hỏi của chính mình sao? :-)
Gangnus

Theo cách đó hiệu quả hơn, phải không? :)
Davislor

0

Ai đó có thể giải thích cho tôi, ý nghĩa của việc che giấu tất cả các lĩnh vực là riêng tư và sau đó để phơi bày tất cả chúng bằng một số công nghệ bổ sung?

Hoàn toàn không có điểm nào. Tuy nhiên, thực tế bạn hỏi câu hỏi đó chứng tỏ rằng bạn chưa hiểu Lombok làm gì và bạn không hiểu cách viết mã OO bằng cách đóng gói. Hãy tua lại một chút ...

Một số dữ liệu cho một thể hiện của lớp sẽ luôn là nội bộ và không bao giờ được tiết lộ. Một số dữ liệu cho một thể hiện của lớp sẽ cần phải được đặt bên ngoài và một số dữ liệu có thể cần phải được đưa ra khỏi một thể hiện của lớp. Chúng tôi có thể muốn thay đổi cách lớp thực hiện công cụ dưới bề mặt, vì vậy chúng tôi sử dụng các hàm để cho phép chúng tôi lấy và thiết lập dữ liệu.

Một số chương trình muốn lưu trạng thái cho các thể hiện của lớp, vì vậy những chương trình này có thể có một số giao diện tuần tự hóa. Chúng tôi thêm nhiều hàm cho phép cá thể lớp lưu trữ trạng thái của nó để lưu trữ và truy xuất trạng thái của nó từ bộ lưu trữ. Điều này duy trì việc đóng gói vì cá thể lớp vẫn đang kiểm soát dữ liệu của chính nó. Chúng tôi có thể tuần tự hóa dữ liệu riêng tư, nhưng phần còn lại của chương trình không có quyền truy cập vào nó (hay chính xác hơn là chúng tôi duy trì Tường Trung Quốc bằng cách chọn không cố tình làm hỏng dữ liệu riêng tư đó) và cá thể lớp có thể (và nên) tiến hành kiểm tra tính toàn vẹn về khử lưu huỳnh để đảm bảo dữ liệu của nó trở lại OK.

Đôi khi dữ liệu cần kiểm tra phạm vi, kiểm tra tính toàn vẹn hoặc những thứ tương tự. Viết các chức năng này cho phép chúng tôi làm tất cả điều đó. Trong trường hợp này, chúng tôi không muốn hoặc không cần Lombok, vì chúng tôi đang tự mình làm tất cả.

Mặc dù vậy, thường xuyên, bạn thấy rằng một tham số được đặt bên ngoài được lưu trữ trong một biến duy nhất. Trong trường hợp này, bạn sẽ cần bốn hàm để lấy / set / serialise / deserialise nội dung của biến đó. Tự viết bốn chức năng này mỗi khi bạn chậm lại và dễ bị lỗi. Tự động hóa quy trình với Lombok tăng tốc độ phát triển của bạn và loại bỏ khả năng xảy ra lỗi.

Vâng, nó sẽ có thể làm cho biến đó công khai. Trong phiên bản mã đặc biệt này, nó sẽ giống nhau về mặt chức năng. Nhưng hãy quay lại lý do tại sao chúng ta sử dụng các hàm: "Chúng tôi có thể muốn thay đổi cách lớp thực hiện công cụ dưới bề mặt ..." Nếu bạn đặt biến của mình thành công khai, bạn sẽ hạn chế mã của mình ngay bây giờ và mãi mãi để có biến công khai này như giao diện. Nếu bạn sử dụng các chức năng mặc dù hoặc nếu bạn sử dụng Lombok để tự động tạo các chức năng đó cho bạn, bạn có thể tự do thay đổi dữ liệu cơ bản và triển khai cơ bản bất cứ lúc nào trong tương lai.

Điều này làm cho nó rõ ràng hơn?


Bạn đang nói về những câu hỏi của sinh viên SW năm thứ nhất. Chúng tôi đã ở nơi khác rồi. Tôi sẽ không bao giờ nói điều này và thậm chí sẽ cho bạn một điểm cộng cho những câu trả lời thông minh, nếu bạn không quá bất lịch sự dưới dạng câu trả lời của bạn.
Gangnus

0

Tôi thực sự không phải là một nhà phát triển Java; nhưng sau đây là khá nhiều nền tảng bất khả tri.

Tất cả mọi thứ chúng tôi viết đều sử dụng getters và setters công khai truy cập các biến riêng tư. Hầu hết các getters và setters là tầm thường. Nhưng khi chúng ta quyết định rằng setter cần tính toán lại một cái gì đó hoặc setter thực hiện một số xác nhận hoặc chúng ta cần chuyển tiếp thuộc tính tới một thuộc tính của một biến thành viên của lớp này, điều này hoàn toàn không phá vỡ toàn bộ mã và tương thích nhị phân có thể trao đổi một mô-đun ra.

Khi chúng tôi quyết định thuộc tính này thực sự cần được tính toán một cách nhanh chóng, tất cả các mã nhìn vào nó không phải thay đổi và chỉ có mã ghi vào nó cần thay đổi và IDE có thể tìm thấy nó cho chúng tôi. Khi chúng tôi quyết định rằng đó là một trường tính toán có thể ghi được (chỉ phải làm điều đó một vài lần), chúng tôi cũng có thể làm điều đó. Điều tuyệt vời là khá nhiều trong số những thay đổi này tương thích nhị phân (thay đổi sang trường được tính toán chỉ đọc không nằm trong lý thuyết nhưng dù sao cũng có thể trong thực tế).

Chúng tôi đã kết thúc với rất nhiều và rất nhiều getters tầm thường với setters phức tạp. Chúng tôi cũng đã kết thúc với một vài getters bộ nhớ đệm. Kết quả cuối cùng là bạn được phép giả định rằng getters có giá rẻ hợp lý nhưng setters có thể không. Mặt khác, chúng ta đủ khôn ngoan để quyết định rằng setters không tồn tại trên đĩa.

Nhưng tôi đã phải theo dõi anh chàng đã mù quáng thay đổi tất cả các biến thành viên thành tài sản. Anh ta không biết thêm nguyên tử là gì nên anh ta đã thay đổi thứ thực sự cần phải là biến công khai thành tài sản và phá mã theo cách tinh tế.


Chào mừng đến với thế giới Java. (C # cũng vậy). *thở dài*. Nhưng đối với việc sử dụng get / set để ẩn biểu diễn bên trong, hãy cẩn thận. Nó chỉ là về định dạng bên ngoài, nó là OK. Nhưng nếu đó là về toán học, hoặc tệ hơn, vật lý hoặc thực tế khác, đại diện bên trong khác nhau có những phẩm chất khác nhau. viz bình luận của tôi cho phần
mềmengineering.stackexchange.com/a35386666/44104

@Gangnus: Ví dụ điểm đó là không may nhất. Chúng tôi đã có khá nhiều trong số này nhưng tính toán luôn chính xác.
Joshua

-1

Getters và setters là một biện pháp "chỉ trong trường hợp" được thêm vào nhằm tránh tái cấu trúc trong tương lai nếu cấu trúc bên trong hoặc các yêu cầu truy cập thay đổi trong quá trình phát triển.

Giả sử, một vài tháng sau khi phát hành, khách hàng của bạn thông báo cho bạn rằng một trong các trường của một lớp đôi khi được đặt thành giá trị âm, mặc dù nó sẽ kẹp thành 0 nhiều nhất trong trường hợp đó. Sử dụng các trường công khai, bạn sẽ phải tìm kiếm mọi phân công của trường này trong toàn bộ cơ sở mã của mình để áp dụng chức năng kẹp cho giá trị bạn sẽ đặt và lưu ý rằng bạn sẽ luôn phải làm điều đó khi sửa đổi trường này, nhưng mà hút Thay vào đó, nếu bạn tình cờ sử dụng getters và setter, bạn có thể chỉ cần sửa đổi phương thức setField () của mình để đảm bảo kẹp này luôn được áp dụng.

Bây giờ, vấn đề với các ngôn ngữ "cổ xưa" như Java là họ khuyến khích sử dụng các phương thức cho mục đích này, điều này chỉ làm cho mã của bạn dài hơn vô hạn. Thật khó để viết và khó đọc, đó là lý do tại sao chúng tôi đã sử dụng IDE để giảm thiểu vấn đề này bằng cách này hay cách khác. Hầu hết IDE sẽ tự động tạo getters và setters cho bạn, và cũng ẩn chúng trừ khi được nói khác đi. Lombok tiến thêm một bước và chỉ cần tạo chúng theo quy trình trong thời gian biên dịch để giữ cho mã của bạn thêm bóng bẩy. Tuy nhiên, các ngôn ngữ hiện đại khác đã đơn giản giải quyết vấn đề này bằng cách này hay cách khác. Ngôn ngữ Scala hoặc .NET, ví dụ,

Ví dụ: trong VB .NET hoặc C #, bạn có thể chỉ cần làm cho tất cả các trường có nghĩa là không có hiệu ứng phụ và setters chỉ là các trường công khai, và sau đó đặt chúng ở chế độ riêng tư, thay đổi tên của chúng và hiển thị một Thuộc tính với tên trước đó của trường, nơi bạn có thể tinh chỉnh hành vi truy cập của trường, nếu bạn cần. Với Lombok, nếu bạn cần tinh chỉnh hành vi của getter hoặc setter, bạn có thể chỉ cần xóa các thẻ đó khi cần và tự mã hóa các yêu cầu mới, biết chắc chắn bạn sẽ phải cấu trúc lại không có gì trong các tệp khác.

Về cơ bản, cách phương thức của bạn truy cập vào một trường phải minh bạch và thống nhất. Các ngôn ngữ hiện đại cho phép bạn định nghĩa "phương thức" với cùng cú pháp truy cập / cuộc gọi của một trường, do đó những sửa đổi này có thể được thực hiện theo yêu cầu mà không cần suy nghĩ nhiều về nó trong quá trình phát triển ban đầu, nhưng Java buộc bạn phải thực hiện công việc này trước vì nó không có tính năng này. Tất cả những gì Lombok làm là giúp bạn tiết kiệm thời gian, vì ngôn ngữ bạn đang sử dụng không muốn cho phép bạn tiết kiệm thời gian cho phương pháp "chỉ trong trường hợp".


Trong các ngôn ngữ "hiện đại" đó, API trông giống như một truy cập trường foo.barnhưng nó có thể được xử lý bằng một cuộc gọi phương thức. Bạn cho rằng điều này vượt trội so với cách "cổ xưa" khi có API trông giống như các cuộc gọi phương thức foo.getBar(). Chúng tôi dường như đồng ý rằng các lĩnh vực công cộng có vấn đề, nhưng tôi cho rằng sự thay thế "cổ xưa" là vượt trội so với "hiện đại", vì toàn bộ API của chúng tôi là đối xứng (tất cả đều gọi phương thức). Theo cách tiếp cận "hiện đại", chúng ta phải quyết định những thứ nào nên là thuộc tính và đâu là phương thức, điều này làm quá mức mọi thứ (đặc biệt là nếu chúng ta sử dụng sự phản chiếu!).
Warbo

1
1. Giấu vật lý không phải lúc nào cũng tốt. hãy xem nhận xét của tôi về phần mềmengineering.stackexchange.com/a35386666/44104 . 2. Tôi không nói về việc tạo getter / setter khó hay đơn giản như thế nào. C # hoàn toàn có cùng một vấn đề mà chúng ta đang nói đến. Việc thiếu bao bì do giải pháp xấu về tải thông tin đại chúng. 3. Có, giải pháp có thể dựa trên cú pháp, nhưng, xin vui lòng, theo một số cách khác nhau mà bạn đang đề cập. Đó là những điều xấu, chúng tôi có ở đây một trang lớn chứa đầy những lời giải thích. 4. Giải pháp không cần phải dựa trên cú pháp. Nó có thể được thực hiện trong các giới hạn Java.
Gangnus

-1

Ai đó có thể giải thích cho tôi ý nghĩa của việc che giấu tất cả các lĩnh vực là riêng tư và sau đó để phơi bày tất cả chúng bằng một số công nghệ bổ sung? Tại sao chúng ta chỉ đơn giản là không sử dụng các lĩnh vực công cộng sau đó? Tôi cảm thấy chúng tôi đã đi một chặng đường dài và khó khăn chỉ để trở về điểm xuất phát.

Vâng, đó là mâu thuẫn. Lần đầu tiên tôi chạy vào các thuộc tính trong Visual Basic. Cho đến lúc đó, trong các ngôn ngữ khác, kinh nghiệm của tôi, không có trình bao bọc tài sản xung quanh các trường. Chỉ cần các lĩnh vực công cộng, tư nhân và được bảo vệ.

Tài sản là một loại đóng gói. Tôi hiểu các thuộc tính Visual Basic là một cách để kiểm soát và thao tác đầu ra của một hoặc nhiều trường trong khi ẩn trường rõ ràng và thậm chí loại dữ liệu của nó, ví dụ như phát ra một ngày dưới dạng một chuỗi trong một định dạng cụ thể. Nhưng ngay cả khi đó người ta không "che giấu trạng thái và phơi bày chức năng" từ phối cảnh đối tượng lớn hơn.

Nhưng các thuộc tính tự biện minh vì các getters và setters tài sản riêng biệt. Phơi bày một lĩnh vực không có tài sản là tất cả hoặc không có gì - nếu bạn có thể đọc nó, bạn có thể thay đổi nó. Vì vậy, bây giờ người ta có thể biện minh cho thiết kế lớp yếu với setters được bảo vệ.

Vậy tại sao chúng ta không / họ chỉ sử dụng các phương pháp thực tế? Bởi vì đó là Visual Basic (và VBScript ) (oooh! Aaah!), Mã hóa cho số đông (!), Và đó là tất cả cơn thịnh nộ. Và do đó, một sự ngu ngốc cuối cùng đã thống trị.


Có, các thuộc tính cho UI có ý nghĩa đặc biệt, chúng là các biểu diễn công khai và đẹp mắt của các trường. Điểm tốt.
Gangnus

"Vậy tại sao chúng ta không / họ chỉ sử dụng các phương pháp thực tế?" Về mặt kỹ thuật họ đã làm trong phiên bản .Net. Thuộc tính là đường cú pháp cho hàm get và set.
Pharap

"Ngốc" ? Ý bạn là " idiosyncrasy "?
Peter Mortensen

Ý tôi là sự ngu ngốc
radarbob
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.