Những gì nên được cho phép bên trong getters và setters?


45

Tôi đã tham gia một cuộc tranh luận thú vị trên internet về các phương thức và đóng gói getter và setter. Ai đó nói rằng tất cả những gì họ nên làm là một bài tập (setters) hoặc một truy cập biến (getters) để giữ cho chúng "thuần túy" và đảm bảo đóng gói.

  • Tôi có đúng rằng điều này sẽ hoàn toàn đánh bại mục đích có getters và setters ở vị trí đầu tiên và xác nhận và logic khác (tất nhiên không có tác dụng phụ lạ) nên được cho phép?
  • Khi nào nên xác nhận xảy ra?
    • Khi đặt giá trị, bên trong bộ cài đặt (để bảo vệ đối tượng không bao giờ rơi vào trạng thái không hợp lệ - theo ý kiến ​​của tôi)
    • Trước khi đặt giá trị, bên ngoài bộ cài đặt
    • Bên trong đối tượng, trước mỗi lần sử dụng giá trị
  • Là một setter được phép thay đổi giá trị (có thể chuyển đổi một giá trị hợp lệ thành một số biểu diễn bên trong chính tắc)?

18
Điều tốt nhất về getters và setters là khả năng không có chúng , tức là rời khỏi setter và bạn có một thuộc tính chỉ đọc, rời khỏi getter và bạn có tùy chọn cấu hình có giá trị hiện tại không phải là doanh nghiệp của ai.
Michael Borgwardt

7
@MichaelBorgwardt Rời khỏi cả hai để có một câu nói rõ ràng, đừng hỏi giao diện của J. Thuộc tính = mùi mã tiềm năng.
Konrad Rudolph


2
Setters cũng có thể được sử dụng để nâng cao một sự kiện .
John Isaiah Carmona

@KonradRudolph Tôi đồng ý với tuyên bố của bạn, mặc dù tôi muốn thực sự nhấn mạnh từ "tiềm năng".
Phil

Câu trả lời:


37

Tôi nhớ có một cuộc tranh luận tương tự với giảng viên của tôi khi học C ++ ở trường đại học. Tôi chỉ không thể thấy quan điểm của việc sử dụng getters và setters khi tôi có thể công khai biến. Bây giờ tôi hiểu rõ hơn với nhiều năm kinh nghiệm và tôi đã học được một lý do tốt hơn là chỉ đơn giản nói "để duy trì đóng gói".

Bằng cách xác định các getters và setters, bạn sẽ cung cấp một giao diện nhất quán để nếu bạn muốn thay đổi triển khai của mình, bạn sẽ ít có khả năng phá vỡ mã phụ thuộc. Điều này đặc biệt quan trọng khi các lớp của bạn được hiển thị thông qua API và được sử dụng trong các ứng dụng khác hoặc bởi các bên thứ 3. Vì vậy, những gì về những thứ đi vào getter hoặc setter?

Getters thường được thực hiện tốt hơn như là một sự thông qua đơn giản để truy cập vào một giá trị bởi vì điều này làm cho hành vi của họ có thể dự đoán được. Tôi nói chung, bởi vì tôi đã thấy các trường hợp trong đó getters đã được sử dụng để truy cập các giá trị được thao tác bằng phép tính hoặc thậm chí bằng mã có điều kiện. Nói chung là không tốt nếu bạn đang tạo các thành phần trực quan để sử dụng tại thời điểm thiết kế, nhưng có vẻ tiện dụng trong thời gian chạy. Tuy nhiên, không có sự khác biệt thực sự giữa điều này và sử dụng một phương thức đơn giản, ngoại trừ khi bạn sử dụng một phương thức, bạn thường có khả năng đặt tên một phương thức phù hợp hơn để chức năng của "getter" rõ ràng hơn khi đọc mã.

So sánh như sau:

int aValue = MyClass.Value;

int aValue = MyClass.CalculateValue();

Tùy chọn thứ hai cho thấy rõ rằng giá trị đang được tính toán, trong khi ví dụ đầu tiên cho bạn biết rằng bạn chỉ đang trả về một giá trị mà không biết gì về chính giá trị đó.

Bạn có thể tranh luận rằng những điều sau đây sẽ rõ ràng hơn:

int aValue = MyClass.CalculatedValue;

Tuy nhiên, vấn đề là bạn cho rằng giá trị đã bị thao túng ở nơi khác. Vì vậy, trong trường hợp của một getter, trong khi bạn có thể muốn giả định rằng điều gì đó khác có thể xảy ra khi bạn trả lại một giá trị, thật khó để làm cho những điều đó rõ ràng trong ngữ cảnh của một tài sản và tên thuộc tính không bao giờ nên chứa động từ mặt khác, nó làm cho khó hiểu trong nháy mắt liệu tên được sử dụng có nên được trang trí bằng dấu ngoặc đơn khi truy cập hay không.

Setters là một trường hợp hơi khác nhau tuy nhiên. Điều hoàn toàn phù hợp là một setter cung cấp một số xử lý bổ sung để xác thực dữ liệu được gửi đến một thuộc tính, đưa ra một ngoại lệ nếu đặt giá trị sẽ vi phạm các ranh giới đã xác định của thuộc tính. Tuy nhiên, vấn đề mà một số nhà phát triển gặp phải khi thêm xử lý vào setters là luôn có sự cám dỗ để người thiết lập thực hiện thêm một chút, chẳng hạn như thực hiện tính toán hoặc thao tác dữ liệu theo một cách nào đó. Đây là nơi bạn có thể nhận được các tác dụng phụ mà trong một số trường hợp có thể là không thể đoán trước hoặc không mong muốn.

Trong trường hợp setters tôi luôn áp dụng một quy tắc đơn giản, đó là làm ít nhất có thể với dữ liệu. Ví dụ, tôi thường sẽ cho phép thử nghiệm ranh giới và làm tròn để tôi có thể đưa ra các ngoại lệ nếu thích hợp hoặc tránh các ngoại lệ không cần thiết nơi chúng có thể tránh được một cách hợp lý. Thuộc tính dấu phẩy động là một ví dụ điển hình trong đó bạn có thể làm tròn các vị trí thập phân quá mức để tránh gây ra ngoại lệ, trong khi vẫn cho phép nhập các giá trị phạm vi với một vài vị trí thập phân bổ sung.

Nếu bạn áp dụng một số thao tác của đầu vào setter, bạn sẽ gặp vấn đề tương tự như với getter, rằng thật khó để cho phép người khác biết những gì setter đang làm bằng cách chỉ cần đặt tên cho nó. Ví dụ:

MyClass.Value = 12345;

Điều này có cho bạn biết bất cứ điều gì về những gì sẽ xảy ra với giá trị khi nó được trao cho setter không?

Làm thế nào về:

MyClass.RoundValueToNearestThousand(12345);

Ví dụ thứ hai cho bạn biết chính xác điều gì sẽ xảy ra với dữ liệu của bạn, trong khi ví dụ đầu tiên sẽ không cho bạn biết nếu giá trị của bạn sẽ được sửa đổi tùy ý. Khi đọc mã, ví dụ thứ hai sẽ rõ ràng hơn nhiều về mục đích và chức năng của nó.

Tôi có đúng rằng điều này sẽ hoàn toàn đánh bại mục đích có getters và setters ở vị trí đầu tiên và xác nhận và logic khác (tất nhiên không có tác dụng phụ lạ) nên được cho phép?

Có getters và setters không phải là đóng gói vì mục đích "tinh khiết", mà là đóng gói để cho phép mã dễ dàng được tái cấu trúc mà không mạo hiểm thay đổi giao diện của lớp, điều này sẽ phá vỡ tính tương thích của lớp với mã gọi. Xác thực là hoàn toàn phù hợp trong một setter, tuy nhiên có một rủi ro nhỏ là việc thay đổi xác thực có thể phá vỡ tính tương thích với mã gọi nếu mã gọi phụ thuộc vào xác thực xảy ra theo một cách cụ thể. Đây là một tình huống thường hiếm gặp và có rủi ro tương đối thấp, nhưng nó cần được lưu ý vì sự hoàn chỉnh.

Khi nào nên xác nhận xảy ra?

Xác thực sẽ xảy ra trong bối cảnh của trình thiết lập trước khi thực sự thiết lập giá trị. Điều này đảm bảo rằng nếu một ngoại lệ được đưa ra, trạng thái của đối tượng của bạn sẽ không thay đổi và có khả năng làm mất hiệu lực dữ liệu của nó. Tôi thường thấy tốt hơn khi ủy quyền xác thực cho một phương thức riêng biệt, đó sẽ là điều đầu tiên được gọi trong setter, để giữ cho mã setter tương đối không bị xáo trộn.

Là một setter được phép thay đổi giá trị (có thể chuyển đổi một giá trị hợp lệ thành một số biểu diễn bên trong chính tắc)?

Trong trường hợp rất hiếm, có thể. Nói chung, có lẽ tốt hơn là không. Đây là loại điều tốt nhất còn lại cho phương pháp khác.


re: thay đổi giá trị, có thể hợp lý khi đặt nó thành -1 hoặc một số mã thông báo cờ NULL nếu bộ cài đặt được chuyển một giá trị bất hợp pháp
Martin Beckett

1
Có một vài vấn đề với điều này. Tự ý thiết lập một giá trị tạo ra hiệu ứng phụ có chủ ý và không rõ ràng. Ngoài ra, nó không cho phép mã cuộc gọi nhận phản hồi có thể được sử dụng để xử lý tốt hơn với dữ liệu bất hợp pháp. Điều này đặc biệt quan trọng với các giá trị ở cấp độ UI. Mặc dù công bằng mà nói, một ngoại lệ tôi chỉ nghĩ có thể là nếu cho phép nhiều định dạng ngày làm đầu vào, trong khi lưu trữ ngày ở định dạng ngày / giờ tiêu chuẩn. Người ta có thể tranh luận rằng "tác dụng phụ" đặc biệt này là sự bình thường hóa dữ liệu khi xác nhận, với điều kiện dữ liệu đầu vào là hợp pháp.
S.Robins

Có, một trong những biện minh của setter là nó sẽ trả về true / false nếu giá trị có thể được đặt.
Martin Beckett

1
"Thuộc tính dấu phẩy động là một ví dụ điển hình trong đó bạn có thể làm tròn các số thập phân quá mức để tránh tăng ngoại lệ" Trong trường hợp nào có quá nhiều vị trí thập phân sẽ tạo ra ngoại lệ?
Mark Byers

2
Tôi thấy việc thay đổi giá trị thành một biểu diễn chính tắc như một hình thức xác nhận. Mặc định các giá trị bị thiếu (ví dụ: ngày hiện tại nếu dấu thời gian chỉ chứa thời gian) hoặc chia tỷ lệ giá trị (.93275 -> 93,28%) để đảm bảo tính nhất quán bên trong sẽ ổn, nhưng mọi thao tác như vậy nên được gọi rõ ràng trong tài liệu API , đặc biệt nếu chúng là một thành ngữ không phổ biến trong API.
TMN

20

Nếu getter / setter chỉ phản ánh giá trị thì không có điểm nào trong việc có chúng, hoặc làm cho giá trị riêng tư. Không có gì sai khi công khai một số biến thành viên nếu bạn có lý do chính đáng. Nếu bạn đang viết một lớp điểm 3d thì có .x, .y, .z công khai có ý nghĩa hoàn hảo.

Như Ralph Waldo Emerson đã nói, "Một sự nhất quán ngu ngốc là hobgoblin của những bộ óc nhỏ bé, được yêu thích bởi các chính khách và nhà triết học nhỏ và các nhà thiết kế của Java."

Getters / setters là hữu ích khi có thể có tác dụng phụ, nơi bạn cần cập nhật các biến nội bộ khác, tính toán lại các giá trị được lưu trong bộ nhớ cache và bảo vệ lớp khỏi các đầu vào không hợp lệ.

Sự biện minh thông thường cho họ, rằng họ che giấu cấu trúc bên trong, nói chung là ít hữu ích nhất. ví dụ. Tôi có các điểm này được lưu dưới dạng 3 số float, tôi có thể quyết định lưu trữ chúng dưới dạng chuỗi trong cơ sở dữ liệu từ xa để tôi tạo getters / setters để ẩn chúng, như thể bạn có thể làm điều đó mà không có bất kỳ ảnh hưởng nào khác đến mã của người gọi.


1
Wow, các nhà thiết kế của Java là thần thánh? </ jk>

@delnan - rõ ràng là không - hoặc họ sẽ gieo vần tốt hơn ;-)
Martin Beckett

Nó có hợp lệ để tạo một điểm 3d trong đó x, y, z đều là Float.NAN không?
Andrew T Finnell

4
Tôi không đồng ý với quan điểm của bạn rằng "sự biện minh thông thường" (ẩn cấu trúc bên trong) là thuộc tính ít hữu ích nhất của getters và setters. Trong C #, nó chắc chắn là hữu ích để làm cho tài sản một giao diện có cấu trúc cơ bản có thể được thay đổi - ví dụ, nếu bạn chỉ muốn có một tài sản có sẵn đếm, nó tốt hơn nhiều điều để nói IEnumerable<T>vì buộc nó vào một cái gì đó giống như List<T>. Ví dụ của bạn về quyền truy cập cơ sở dữ liệu mà tôi muốn nói là vi phạm trách nhiệm đơn lẻ - trộn sự biểu diễn của mô hình với cách nó được duy trì.
Brandon Linton

@AndrewFinnell - đó có thể là một cách tốt để gắn cờ các điểm là không thể / không hợp lệ / bị xóa, v.v.
Martin Beckett

8

Nguyên tắc truy cập thống nhất của Meyer: "Tất cả các dịch vụ được cung cấp bởi một mô-đun nên có sẵn thông qua ký hiệu thống nhất, không phản bội cho dù chúng được thực hiện thông qua lưu trữ hoặc thông qua tính toán." là lý do chính đằng sau getters / setters, còn gọi là Thuộc tính.

Nếu bạn quyết định lưu trữ bộ đệm hoặc tính toán một cách lười biếng một trường của một lớp thì bạn có thể thay đổi điều này bất cứ lúc nào nếu bạn chỉ tiếp xúc với các bộ truy cập thuộc tính và không phải là dữ liệu cụ thể.

Theo tôi, các đối tượng giá trị, các cấu trúc đơn giản không cần sự trừu tượng hóa này, nhưng theo tôi thì một lớp hoàn chỉnh.


2

Một chiến lược chung để thiết kế các lớp, được giới thiệu thông qua ngôn ngữ Eiffel, là Phân tách truy vấn lệnh . Ý tưởng là một phương pháp nên hoặc là cho bạn biết điều gì đó về đối tượng hoặc nói với đối tượng để làm điều gì đó, nhưng không phải để làm cả hai.

Điều này chỉ liên quan đến giao diện chung của lớp, không liên quan đến đại diện bên trong. Hãy xem xét một đối tượng mô hình dữ liệu được hỗ trợ bởi một hàng trong cơ sở dữ liệu. Bạn có thể tạo đối tượng mà không cần tải dữ liệu vào, sau đó lần đầu tiên bạn gọi một getter, nó thực sự làm SELECT. Điều đó tốt, bạn có thể thay đổi một số chi tiết nội bộ về cách thể hiện của đối tượng nhưng bạn không thay đổi cách nó xuất hiện với khách hàng của đối tượng đó. Bạn sẽ có thể gọi getters nhiều lần mà vẫn nhận được kết quả như nhau, ngay cả khi họ làm những công việc khác nhau để trả về những kết quả đó.

Tương tự, một setter trông, theo hợp đồng, giống như nó chỉ thay đổi trạng thái của một đối tượng. Nó có thể làm điều đó trong một số thời trang phức tạp, viết UPDATEmột cơ sở dữ liệu hoặc truyền tham số cho một số đối tượng bên trong. Điều đó tốt, nhưng làm một cái gì đó không liên quan đến việc thiết lập trạng thái sẽ là đáng ngạc nhiên.

Meyer (người tạo ra Eiffel) cũng có những điều để nói về việc xác nhận. Về cơ bản, bất cứ khi nào một đối tượng không hoạt động, nó sẽ ở trạng thái hợp lệ. Vì vậy, ngay sau khi hàm tạo kết thúc, trước và sau (nhưng không nhất thiết trong suốt) mỗi lệnh gọi phương thức bên ngoài, trạng thái của đối tượng phải nhất quán.

Thật thú vị khi lưu ý rằng trong ngôn ngữ đó, cú pháp để gọi một thủ tục và để đọc một thuộc tính được phơi bày đều trông giống nhau. Nói cách khác, một người gọi không thể biết họ đang sử dụng một số phương thức hay làm việc trực tiếp với một biến thể hiện. Chỉ bằng các ngôn ngữ không che giấu chi tiết triển khai này, nơi câu hỏi thậm chí xuất hiện trên mạng nếu người gọi không thể nói, bạn có thể chuyển đổi giữa ngà công khai và người truy cập mà không rò rỉ thay đổi thành mã máy khách.


1

Một điều chấp nhận được để làm là nhân bản. Trong một số trường hợp, bạn cần chắc chắn rằng, sau khi ai đó đưa ra lớp của bạn, vd. một danh sách một cái gì đó, anh ta không thể thay đổi nó trong lớp của bạn (cũng không thay đổi đối tượng trong danh sách đó). Do đó, bạn tạo bản sao sâu của tham số trong setter và trả về bản sao sâu trong getter. (Sử dụng các loại không thay đổi làm tham số là tùy chọn ann ann, nhưng ở trên giả định là không thể) Nhưng đừng sao chép trong các bộ truy cập nếu không cần thiết. Thật dễ dàng để suy nghĩ (nhưng không thích hợp) về sự phù hợp / getters và setters như hoạt động chi phí không đổi, vì vậy đây là mìn hiệu suất đang chờ người sử dụng api.


1

Không phải lúc nào cũng có ánh xạ 1-1 giữa các bộ truy cập thuộc tính và các ngà lưu trữ dữ liệu.

Ví dụ: một lớp khung nhìn có thể cung cấp một thuộc centertính mặc dù không có ngà nào lưu trữ trung tâm của khung nhìn; cài đặt centergây ra các thay đổi đối với các ngà khác, như originhoặc transformhoặc bất cứ điều gì, nhưng các khách hàng của lớp không biết hoặc quan tâm đến cách center lưu trữ miễn là nó hoạt động chính xác. Tuy nhiên, điều không nên xảy ra là cài đặt centerkhiến mọi thứ xảy ra vượt quá mọi thứ cần thiết để lưu giá trị mới, tuy nhiên điều đó đã được thực hiện.


0

Phần tốt nhất về setters và getters là chúng giúp dễ dàng thay đổi các quy tắc của API mà không thay đổi API. Nếu bạn phát hiện ra một lỗi, nhiều khả năng bạn có thể sửa lỗi trong thư viện và không phải mọi người tiêu dùng đều cập nhật cơ sở mã của nó.


-4

Tôi có xu hướng tin rằng setters và getters là xấu và chỉ nên được sử dụng trong các lớp được quản lý bởi một khung / container. Một thiết kế lớp thích hợp không nên mang lại getters và setters.

Chỉnh sửa: một bài viết tốt về chủ đề này .

Edit2: các trường công khai là vô nghĩa trong cách tiếp cận OOP; bằng cách nói rằng getters và setters là xấu xa tôi không có nghĩa là chúng nên được thay thế bởi các lĩnh vực công cộng.


1
Tôi nghĩ rằng bạn đang thiếu điểm chính của bài viết, điều này gợi ý sử dụng getters / setters một cách tiết kiệm và để tránh lộ dữ liệu lớp trừ khi cần thiết. IMHO này là một nguyên tắc thiết kế hợp lý nên được nhà phát triển áp dụng có chủ ý. Về mặt tạo các thuộc tính trên một lớp, bạn có thể chỉ cần hiển thị một biến, nhưng điều này khiến việc cung cấp xác thực khi biến được đặt hoặc rút giá trị từ một nguồn thay thế khi "có", về cơ bản là vi phạm đóng gói khóa bạn vào một thiết kế giao diện khó khăn.
S.Robins

@ S.Robins Theo tôi, getters và setters công cộng là sai; lĩnh vực công cộng là sai hơn (nếu đó là so sánh).
m3th0dman

@methodman Có, tôi đồng ý rằng một lĩnh vực công cộng là sai, tuy nhiên một tài sản công cộng có thể hữu ích. Thuộc tính đó có thể được sử dụng để cung cấp một nơi để xác thực hoặc các sự kiện liên quan đến cài đặt hoặc trả lại dữ liệu, tùy thuộc vào yêu cầu cụ thể tại thời điểm đó. Bản thân Getters và setters không sai mỗi lần. Mặt khác, làm thế nào và khi chúng được sử dụng hoặc lạm dụng có thể được coi là kém về thiết kế và khả năng bảo trì tùy thuộc vào hoàn cảnh. :)
S.Robins

@ S.Robins Hãy nghĩ về những điều cơ bản của OOP và mô hình hóa; các đối tượng được cho là sao chép các thực thể trong cuộc sống thực. Có thực thể thực tế nào có loại hoạt động / thuộc tính này như getters / setters không?
m3th0dman

Để tránh có quá nhiều bình luận ở đây, tôi sẽ đưa cuộc trò chuyện này vào cuộc trò chuyện này và giải quyết bình luận của bạn ở đó.
S.Robins
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.