Sử dụng chung kết công khai chứ không phải cá nhân


51

Tôi thấy hầu hết các POJO bất biến được viết như thế này:

public class MyObject {
    private final String foo;
    private final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public String getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }
}

Tuy nhiên, tôi có xu hướng viết chúng như thế này:

public class MyObject {
    public final String foo;
    public final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }
}

Lưu ý các tài liệu tham khảo là cuối cùng, vì vậy Đối tượng vẫn không thay đổi. Nó cho phép tôi viết ít mã hơn và cho phép truy cập ngắn hơn (bằng 5 ký tự: get()).

Nhược điểm duy nhất tôi có thể thấy là nếu bạn muốn thay đổi việc thực hiện getFoo()xuống đường để làm điều gì đó điên rồ, bạn không thể. Nhưng thực tế, điều này không bao giờ xảy ra vì Đối tượng là bất biến; bạn có thể xác minh trong instantiation, tạo bản sao phòng thủ không thể thay đổi trong instantiation (xem ổi của ImmutableListví dụ), và nhận được foohoặc barcác đối tượng sẵn sàng cho các getcuộc gọi.

Có bất kỳ nhược điểm nào tôi đang thiếu?

BIÊN TẬP

Tôi cho rằng một nhược điểm khác mà tôi thiếu là các thư viện tuần tự hóa sử dụng sự phản chiếu qua các phương thức bắt đầu bằng gethoặc is, nhưng đó là một thực tế khá khủng khiếp ...


Cái quái gì thế? finalkhông biến một đối tượng thành bất biến. Tôi thường sử dụng một thiết kế trong đó tôi xác định finalcác trường trước khi tạo cuộc gọi lại để gọi lại có thể truy cập vào các trường đó. Tất nhiên nó có thể gọi tất cả các phương thức bao gồm mọi setXphương thức.
Tomáš Zato

@ TomášZato Không có phương pháp setX trên String, inthoặc MyObject. Trong phiên bản đầu tiên, finalchỉ để đảm bảo rằng các phương thức khác ngoài hàm tạo trong lớp không cố gắng bar = 7;, chẳng hạn. Trong phiên bản thứ hai, finalcần thiết để ngăn người tiêu dùng thực hiện : MyObject x = new MyObject("hi", 5); x.bar = 7;.
Cory Kendall

1
Chà, " Lưu ý các tài liệu tham khảo là cuối cùng, vì vậy Objectvẫn không thay đổi. " Là sai lệch - theo cách mà nó xuất hiện mà bạn nghĩ rằng bất kỳ final Objectlà bất biến, điều đó không phải là bất biến. Xin lỗi vì sự hiểu lầm.
Tomáš Zato

3
Nếu các giá trị cơ bản là có thể thay đổi, đường riêng + người truy cập sẽ không ngăn chặn nó - myObj.getFoo().setFrob(...).
Beni Cherniavsky-Paskin

Câu trả lời:


38

Bốn nhược điểm mà tôi có thể nghĩ đến:

  1. Nếu bạn muốn có một dạng chỉ đọc và có thể thay đổi của cùng một thực thể, một mẫu phổ biến là có một Thực thể lớp bất biến chỉ hiển thị các hàm truy cập với các biến thành viên được bảo vệ, sau đó tạo MutableEntity mở rộng nó và thêm setters. Phiên bản của bạn ngăn chặn nó.
  2. Việc sử dụng getters và setters tuân thủ quy ước JavaBeans. Nếu bạn muốn sử dụng lớp của mình như một hạt đậu trong các công nghệ dựa trên thuộc tính, như JSTL hoặc EL, bạn cần phải hiển thị các công cụ thu thập công khai.
  3. Nếu bạn muốn thay đổi việc triển khai để lấy các giá trị hoặc tìm kiếm chúng trong cơ sở dữ liệu, bạn phải cấu trúc lại mã máy khách. Một cách tiếp cận accessor / mutator cho phép bạn chỉ thay đổi việc thực hiện.
  4. Ít ngạc nhiên nhất - khi tôi thấy các biến thể hiện công khai, tôi ngay lập tức tìm kiếm ai có thể đang biến đổi nó và lo lắng rằng tôi đang mở hộp pandora vì đóng gói bị mất. http://en.wikipedia.org/wiki/Principl_of_least_astonemony

Điều đó nói rằng, phiên bản của bạn chắc chắn ngắn gọn hơn. Nếu đây là một lớp chuyên biệt chỉ được sử dụng trong một gói cụ thể (có thể phạm vi gói là một ý tưởng tốt ở đây), thì tôi có thể xem xét điều này cho một lần. Nhưng tôi sẽ không tiết lộ các API chính như thế này.


3
Câu trả lời tuyệt vời; Tôi có bất đồng với một số người trong số họ, nhưng tôi không chắc trang này là diễn đàn tốt nhất để thảo luận. Tóm lại, 1) Tôi có thể phá vỡ những người khác bằng cách sử dụng dạng biến đổi trong đó họ cho rằng nó không thay đổi (có lẽ tôi nên thêm cuối cùng vào lớp trong các ví dụ của mình), 2) Tôi đồng ý, 3) Tôi sẽ cho rằng nó không còn là POJO nữa trường hợp, 4) Tôi đồng ý ngay bây giờ, nhưng hy vọng các cuộc thảo luận như thế này có thể thay đổi những gì ít gây ngạc nhiên nhất :).
Cory Kendall

2
5) Bạn không thể phơi bày một cách an toàn các lớp / loại vốn có thể thay đổi - như mảng. Cách an toàn là trả lại một bản sao từ một getter mỗi lần.
Đồng hồ-Muse

@ Clockwork-Muse bạn có thể, nếu bạn cho rằng mọi người không ngốc. Khi truy cập someObj.thisList.add () rõ ràng là bạn đang sửa đổi một số trạng thái đối tượng khác. Với someObj.get ThisList (). Add () không rõ. Bạn cần hỏi tài liệu hoặc tra cứu nguồn, nhưng chỉ riêng việc khai báo phương thức thì không thể biết được.
kritzikratzi

2
@kritzikratzi - Vấn đề là bạn không thể đảm bảo rằng mã của bạn sẽ không chạy vào những kẻ ngốc. Tại một số điểm, nó sẽ (mà thậm chí có thể là chính mình!), Và điều ngạc nhiên có thể là một vấn đề lớn.
Clockwork-Muse

1
Vâng, để cho bất kỳ loại đột biến thừa kế từ một loại bất biến là vốn đã sai. Hãy nhớ rằng một loại bất biến mang đến sự đảm bảo "Tôi không thay đổi. Đã từng."
Ded repeatator

26

Loại bỏ các getters / setters quá, và bạn ổn!

Đây là một chủ đề gây tranh cãi giữa các lập trình viên Java.

Dù sao, có hai tình huống trong đó tôi sử dụng các biến công khai thay vì (!) Của getters / setters:

  1. Chung kết với tôi điều này báo hiệu "Tôi bất biến" tốt hơn nhiều so với chỉ là một người đi đường. Hầu hết các IDE sẽ chỉ ra công cụ sửa đổi cuối cùng với 'F' trong khi hoàn thành tự động. Không giống như với getters / setters, nơi bạn phải tìm kiếm sự vắng mặt của một setXXX.
  2. công khai không chung kết Tôi thích điều này cho các lớp dữ liệu. Tôi chỉ phơi bày tất cả các lĩnh vực công khai. Không có getters, setters, constructor. Không có gì. Ít hơn một pojo. Đối với tôi điều này ngay lập tức báo hiệu "nhìn này, tôi thật ngốc. Tôi giữ dữ liệu, đó là tất cả. Đó là công việc CỦA BẠN để đưa dữ liệu phù hợp vào trong tôi". Gson / JAXB / v.v. xử lý các lớp này tốt Họ là một niềm hạnh phúc để viết. Không có nghi ngờ gì về mục đích hoặc khả năng của họ. Và quan trọng nhất: Bạn biết không có tác dụng phụ khi bạn thay đổi một biến. IMHO điều này dẫn đến các mô hình dữ liệu rất súc tích với một vài sự mơ hồ, trong khi các getters và setters có vấn đề lớn này, nơi đôi khi phép thuật xảy ra bên trong chúng.

5
Rất vui khi thấy sự tỉnh táo một lần trong một lúc :)
Navin

2
Cho đến ngày mà mô hình của bạn phát triển và một trong những lĩnh vực cũ bây giờ có thể được bắt nguồn từ một mô hình khác. Vì bạn muốn duy trì khả năng tương thích ngược, trường cũ phải duy trì nhưng thay vì chỉ thay đổi mã getter và setter, bạn sẽ phải thay đổi ở mọi nơi khác, nơi trường cũ này được sử dụng để đảm bảo rằng nó tuân theo biểu diễn mô hình mới. Dễ viết .. vâng chắc chắn ... cơn ác mộng cần duy trì trong thời gian dài ... Đó là lý do tại sao chúng ta có getter / setter hoặc tốt hơn vẫn là trong các bộ truy cập thuộc tính C #.
Newtopian

9

Theo cách nói của giáo dân:

  • Bạn vi phạm đóng gói để lưu một vài dòng mã. Điều đó đánh bại mục đích của OOD.
  • Mã khách hàng sẽ khó kết hợp với tên của các thành viên trong lớp của bạn. Khớp nối là xấu. Toàn bộ mục đích của OOD là ngăn chặn khớp nối.
  • Bạn cũng rất chắc chắn rằng lớp học của bạn sẽ không bao giờ cần phải có thể thay đổi. Nhiều thứ thay đổi. Thay đổi là điều duy nhất không đổi.

6
Làm thế nào đây là một vi phạm đóng gói? Cả hai phiên bản đều được tiếp xúc với thế giới bên ngoài như nhau (với quyền truy cập chỉ đọc). Tuy nhiên, bạn đã đúng về sự khớp nối cứng và tính không linh hoạt của sự thay đổi, tôi sẽ không tranh luận điều đó.
KChaloux

4
@KChaloux Bạn nhầm lẫn "vi phạm đóng gói" với "mã không biên dịch và phải lãng phí thời gian để sửa nó", điều này xảy ra trước tiên. Điều thứ hai xảy ra sau đó. Để làm rõ: đóng gói đã bị vi phạm trong hiện tại. Các bộ phận trong lớp của bạn không còn được gói gọn trong thời điểm hiện tại. Nhưng mã máy khách sẽ bị hỏng trong tương lai nếu lớp thay đổi trong tương lai, vì đóng gói đã bị phá vỡ trong quá khứ. Có hai lần "nghỉ" khác nhau, đóng gói hôm nay, mã khách hàng vào ngày mai, vì thiếu đóng gói.
Tulains Córdova

8
Về mặt kỹ thuật, khi bạn sử dụng getters / vv. thay vào đó, mã máy khách sẽ được kết hợp với tên phương thức. Hãy xem có bao nhiêu thư viện phải bao gồm các phiên bản cũ của phương thức với thẻ / chú thích "không dùng nữa" vì mã cũ vẫn phụ thuộc vào chúng (và một số người vẫn có thể sử dụng chúng vì chúng thấy chúng dễ làm việc hơn, nhưng bạn không phải làm điều đó).
JAB

5
Thực dụng: Làm thế nào thường xuyên lập trình viên thực sự cấu trúc lại tên trường riêng mà không đổi tên các getters / setters công cộng? Từ những gì tôi có thể nói, có một quy ước toàn cộng đồng được đặt tên giống nhau, ngay cả khi quy ước đó chỉ có thể là kết quả của các công cụ IDE tạo ra getters và setters từ tên trường. Lý thuyết: Tuy nhiên, từ góc độ mô hình hướng đối tượng, mọi thứ công khai đều là một phần của hợp đồng công cộng, và không phải là một chi tiết thực hiện nội bộ. Vì vậy, nếu ai đó quyết định công khai trường cuối cùng, không phải họ nói đây là hợp đồng mà họ hứa sẽ thực hiện sao?
Thomas Jung

3
@ user949300 Hãy cho tôi biết những cái nào để tôi có thể học.
Tulains Córdova

6

Một nhược điểm có thể xảy ra mà tôi có thể thấy được là bạn bị ràng buộc với biểu diễn bên trong của dữ liệu trong lớp. Đây có thể không phải là một vấn đề lớn, nhưng nếu bạn sử dụng setters và bạn quyết định rằng foo và thanh sẽ được trả về từ một số lớp khác được định nghĩa ở một nơi khác, các lớp tiêu thụ MyObject sẽ không bắt buộc phải thay đổi. Bạn sẽ chỉ cần chạm vào MyObject. Tuy nhiên, nếu bạn sử dụng các giá trị trần trụi, thì bạn sẽ phải chạm vào mọi nơi mà MyObject tôi đã sử dụng.

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.