Một vấn đề chính xác với việc cho phép getters là gì?


15

Tôi không tìm kiếm một ý kiến ​​về ngữ nghĩa mà chỉ đơn giản là trong trường hợp có các getters được sử dụng hợp lý là một trở ngại thực tế. Có lẽ nó ném tôi vào một vòng xoáy không bao giờ kết thúc dựa vào chúng, có thể thay thế là sạch hơn và xử lý getters tự động, vv Một cái gì đó cụ thể.

Tôi đã nghe tất cả các cuộc tranh luận, tôi đã nghe nói rằng họ xấu vì họ buộc bạn coi các đối tượng là nguồn dữ liệu, rằng họ vi phạm "trạng thái tinh khiết" của đối tượng "không đưa ra quá nhiều nhưng hãy chuẩn bị chấp nhận nhiều ".

Nhưng hoàn toàn không có lý do hợp lý cho lý do tại sao a getDatalà một điều xấu, trên thực tế, một số người tranh luận rằng đó là rất nhiều về ngữ nghĩa, getters là tốt per-se, nhưng chỉ cần không đặt tên cho họ getX, với tôi, điều này ít nhất là buồn cười .

Một điều, nếu không có ý kiến, nó sẽ bị phá vỡ nếu tôi sử dụng getters một cách hợp lý và cho dữ liệu rõ ràng tính toàn vẹn của đối tượng không bị phá vỡ nếu nó đưa ra?

Tất nhiên, việc cho phép một getter cho một chuỗi được sử dụng để mã hóa thứ gì đó vượt quá sự ngu ngốc, nhưng tôi đang nói về dữ liệu mà hệ thống của bạn cần để hoạt động. Có thể dữ liệu của bạn được kéo qua a Providertừ đối tượng, nhưng, tuy nhiên, đối tượng vẫn cần cho phép Providerthực hiện a $provider[$object]->getData, không có cách nào xung quanh nó.


Tại sao tôi hỏi: Đối với tôi, getters, khi được sử dụng hợp lý và trên dữ liệu được coi là "an toàn" được gửi đi, 99% getters của tôi được sử dụng để xác định đối tượng, như trong, tôi hỏi, thông qua mã Object, what is your name? Object, what is your identifier?, Bất cứ ai làm việc với một đối tượng nên biết những điều này về một đối tượng, bởi vì gần như mọi thứ về lập trình là danh tính và ai khác biết rõ hơn về đối tượng đó là gì? Vì vậy, tôi không thấy bất kỳ vấn đề thực sự trừ khi bạn là một người theo chủ nghĩa thuần túy.

Tôi đã xem tất cả các câu hỏi của StackOverflow về "tại sao getters / setters" là xấu và mặc dù tôi đồng ý rằng setters thực sự xấu trong 99% các trường hợp, getters không phải được đối xử giống nhau chỉ vì chúng có vần.

Một setter sẽ thỏa hiệp danh tính đối tượng của bạn và làm cho việc gỡ lỗi thay đổi dữ liệu của bạn rất khó khăn, nhưng một getter không làm gì cả.


2
Lời khuyên của tôi là quên hầu hết lý thuyết OO. Thực hành. Viết rất nhiều mã, và sau đó duy trì nó. Bạn sẽ học được rất nhiều, rất xa, nhiều hơn về những gì hoạt động tốt và những gì không thực sự làm một cái gì đó và sau đó trở lại với nó một vài tháng sau đó.
jpmc26

1
@ jpmc26 Điều gì ở mẹ của .... Tôi chưa bao giờ thực sự nhận ra rằng không ai trong chúng ta thực sự làm OOP một cách chính xác, tôi luôn có khái niệm đóng gói liên quan chặt chẽ đến các lĩnh vực của đối tượng, nhưng bản thân đối tượng là một trạng thái và đang Tự do chia sẻ. Thực sự tất cả mọi người đang làm sai OOP, hoặc, đúng hơn, OOP là không thể nếu mô hình là các đối tượng. Tôi sử dụng OOP một cách nghiêm ngặt để diễn tả cách thức một hệ thống hoạt động và không bao giờ coi nó là chén thánh, cũng không phải RẮN. Chúng chỉ là những công cụ tôi sử dụng chủ yếu để xác định một cách hợp lý (thông qua triển khai) khái niệm của tôi. Bất kỳ đọc trên nếu điều này là đúng?
coolpasta

2
--cont, Càng viết, tôi càng nhận ra rằng lập trình là tất cả về khả năng trừu tượng hóa theo yêu cầu để có thể suy luận, cho người khác, cũng như cho bạn về cơ sở mã của bạn. Tất nhiên, khía cạnh kỹ thuật của việc này là đảm bảo bạn thực hiện kiểm tra đúng / đặt đúng quy tắc. Trong thực tế, bất cứ ai quản lý để làm cho người khác hiểu mã của họ tốt hơn sẽ thắng. Hãy chắc chắn rằng nó có ý nghĩa với bất kỳ ai đọc nó, sau đó đảm bảo rằng nó không thất bại bằng cách phân chia đúng các đối tượng với giao diện, kiểm tra và chuyển sang các mô hình khác khi dễ dàng lý luận với chúng: linh hoạt.
coolpasta

2
Điều này có vẻ như một người rơm đối với tôi. Tôi không tin bất kỳ nhà phát triển nghiêm túc nào thực sự viết mã có thể sử dụng sẽ lập luận rằng việc sử dụng getters hợp lý là một vấn đề. Câu hỏi ngụ ý getters có thể được sử dụng hợp lý mà tiếp tục ngụ ý getters có thể hợp lý. Bạn đang tìm kiếm ai đó để nói rằng không có sử dụng hợp lý của getters? Bạn sẽ chờ đợi một thời gian.
JimmyJames

2
@coolpasta Bạn nói đúng. Tôi không thực sự quan tâm nếu đó là OOP thuần túy. Chỉ cần tốt hơn. Hãy cho tôi lựa chọn giữa mã bị hỏng có thể được hiểu bởi con người so với mã yêu thích CPU hoàn hảo không thể giải mã được và Ill luôn lấy mã thân thiện với con người. OOP chỉ hữu ích với tôi khi nó làm giảm số lượng những điều tôi phải suy nghĩ bất cứ lúc nào. Đó là lý do duy nhất tôi thích nó hơn thủ tục đơn giản. Không có điều đó, bạn chỉ khiến tôi nhảy qua nhiều tệp mã nguồn mà không có lý do.
candied_orange

Câu trả lời:


22

Bạn không thể viết mã tốt mà không có getters.

Lý do tại sao không phải vì getters không phá vỡ đóng gói, họ làm. Không phải vì getters không cám dỗ mọi người không bận tâm theo OOP mà họ sẽ đưa phương thức vào dữ liệu họ hành động. Họ làm. Không, bạn cần getters vì ranh giới.

Các ý tưởng đóng gói và giữ các phương thức cùng với dữ liệu mà chúng hành động chỉ đơn giản là không hoạt động khi bạn chạy vào một ranh giới ngăn bạn di chuyển một phương thức và do đó buộc bạn phải di chuyển dữ liệu.

Nó thực sự đơn giản. Nếu bạn sử dụng getters khi không có ranh giới, cuối cùng bạn không có đối tượng thực sự. Mọi thứ bắt đầu có xu hướng thủ tục. Mà hoạt động tốt như nó từng làm.

OOP thực sự không phải là thứ bạn có thể lan truyền khắp nơi. Nó chỉ hoạt động trong những ranh giới đó.

Những ranh giới đó không mỏng như dao cạo. Họ có mã trong đó. Mã đó không thể là OOP. Nó cũng không thể hoạt động được. Không có mã này có lý tưởng của chúng tôi bị tước khỏi nó để nó có thể đối phó với thực tế khắc nghiệt.

Michael Fetters gọi mã này là fascia sau mô liên kết màu trắng đó giữ các phần của một quả cam lại với nhau.

Đây là một cách tuyệt vời để suy nghĩ về nó. Nó giải thích tại sao bạn có thể có cả hai loại mã trong cùng một cơ sở mã. Không có viễn cảnh này, nhiều lập trình viên mới bám chặt vào lý tưởng của họ, rồi trái tim họ tan vỡ và từ bỏ những lý tưởng này khi họ chạm đến ranh giới đầu tiên.

Các lý tưởng chỉ làm việc ở vị trí thích hợp của họ. Đừng từ bỏ chúng chỉ vì chúng không hoạt động ở mọi nơi. Sử dụng chúng ở nơi họ làm việc. Nơi đó là phần ngon ngọt mà fascia bảo vệ.

Một ví dụ đơn giản về một ranh giới là một bộ sưu tập. Cái này giữ một cái gì đó và không biết nó là gì. Làm thế nào một nhà thiết kế bộ sưu tập có thể chuyển chức năng hành vi của đối tượng bị giữ vào bộ sưu tập khi họ không biết nó sẽ được giữ gì? Bạn không thể. Bạn đang chống lại một ranh giới. Đó là lý do tại sao các bộ sưu tập có getters.

Bây giờ nếu bạn đã biết, bạn có thể di chuyển hành vi đó và tránh chuyển trạng thái. Khi bạn biết, bạn nên. Bạn không phải lúc nào cũng biết.

Một số người chỉ gọi điều này là thực dụng. Và nó là. Nhưng thật tuyệt khi biết tại sao chúng ta phải thực dụng.


Bạn đã bày tỏ rằng bạn không muốn nghe những tranh luận ngữ nghĩa và dường như đang ủng hộ việc đưa "những người thu hút hợp lý" đi khắp mọi nơi. Bạn đang yêu cầu ý tưởng này được thử thách. Tôi nghĩ rằng tôi có thể cho thấy ý tưởng có vấn đề với cách bạn đóng khung nó. Nhưng nó cũng nghĩ tôi biết bạn đến từ đâu vì tôi đã ở đó.

Nếu bạn muốn getters ở khắp mọi nơi hãy nhìn vào Python. Không có từ khóa riêng. Tuy nhiên Python vẫn OOP tốt. Làm sao? Họ sử dụng một mánh khóe ngữ nghĩa. Họ đặt tên cho bất cứ điều gì có nghĩa là riêng tư với một dấu gạch dưới hàng đầu. Bạn thậm chí được phép đọc từ nó với điều kiện bạn phải chịu trách nhiệm cho việc đó. "Chúng ta đều là người lớn ở đây", họ thường nói.

Vì vậy, sự khác biệt giữa điều đó và chỉ đưa getters vào mọi thứ trong Java hoặc C #? Xin lỗi nhưng đó là ngữ nghĩa. Hội nghị gạch dưới của Pythons báo hiệu rõ ràng cho bạn rằng bạn đang chọc vào đằng sau cánh cửa chỉ của nhân viên. Tát getters trên tất cả mọi thứ và bạn mất tín hiệu đó. Với sự phản chiếu, dù sao bạn cũng có thể loại bỏ sự riêng tư và vẫn không bị mất tín hiệu ngữ nghĩa. Đơn giản là không có một lập luận cấu trúc nào được đưa ra ở đây.

Vì vậy, những gì chúng tôi còn lại là công việc quyết định nơi treo biển hiệu "chỉ nhân viên". Những gì nên được coi là riêng tư? Bạn gọi đó là "getters hợp lý". Như tôi đã nói, sự biện minh tốt nhất cho một người đi bộ là một ranh giới buộc chúng ta tránh xa lý tưởng của mình. Điều đó không nên dẫn đến getters trên tất cả mọi thứ. Khi nó không dẫn đến một getter, bạn nên xem xét chuyển hành vi hơn nữa vào bit juicy nơi bạn có thể bảo vệ nó.

Sự tách biệt này đã dẫn đến một vài điều khoản. Đối tượng truyền dữ liệu hoặc DTO, không có hành vi. Các phương thức duy nhất là getters và đôi khi setters, đôi khi là một constructor. Tên này thật đáng tiếc vì nó hoàn toàn không phải là một đối tượng. Các getters và setters thực sự chỉ là mã gỡ lỗi cung cấp cho bạn một nơi để thiết lập một điểm dừng. Nếu không có nhu cầu đó, họ sẽ chỉ là một đống các lĩnh vực công cộng. Trong C ++, chúng tôi thường gọi chúng là structs. Sự khác biệt duy nhất họ có từ một lớp C ++ là họ được mặc định công khai.

DTO rất tuyệt vì bạn có thể ném chúng qua một bức tường ranh giới và giữ các phương thức khác của bạn an toàn trong một đối tượng hành vi ngon ngọt. Một đối tượng thực sự. Không có getters để vi phạm đóng gói của nó. Các đối tượng hành vi của tôi có thể ăn DTO bằng cách sử dụng chúng làm Đối tượng tham số . Đôi khi tôi phải tạo một bản sao phòng thủ của nó để ngăn chặn trạng thái đột biến được chia sẻ . Tôi không lan truyền các DTO đột biến xung quanh bên trong phần ngon ngọt trong ranh giới. Tôi gói gọn chúng. Tôi giấu chúng. Và cuối cùng khi tôi chạy vào một ranh giới mới, tôi quay lên một DTO mới và ném nó lên tường, do đó biến nó thành vấn đề của người khác.

Nhưng bạn muốn cung cấp getters thể hiện bản sắc. Xin chúc mừng bạn đã tìm thấy một biên giới. Các thực thể có một bản sắc vượt ra ngoài tham chiếu của họ. Đó là, ngoài địa chỉ bộ nhớ của họ. Vì vậy, nó phải được lưu trữ ở đâu đó. Và một cái gì đó phải có thể đề cập đến điều này bằng bản sắc của nó. Một getter thể hiện bản sắc là hoàn toàn hợp lý. Một đống mã sử dụng getter đó để đưa ra quyết định mà Thực thể có thể tự đưa ra là không.

Cuối cùng, đó không phải là sự tồn tại của getters mà là sai. Họ là tốt hơn nhiều so với các lĩnh vực công cộng. Điều tồi tệ là khi chúng được sử dụng để giả vờ rằng bạn đang hướng đối tượng khi bạn không. Getters là tốt. Được hướng đối tượng là tốt. Getters không phải là hướng đối tượng. Sử dụng getters để khắc ra một nơi an toàn để được hướng đối tượng.


Đây chính xác là lý do tại sao tôi hỏi và tôi không chắc cuộc trò chuyện của mình có thể nhìn thấy được với Robert hay không, tôi cũng đang cố gắng tìm hiểu một trường hợp cụ thể trong đó rõ ràng làm tổn thương tôi khi sử dụng "getters nhạy cảm". Bản thân tôi không đồng ý với setters vì ít nhất là đối với bản thân tôi, rất khó để quy kết quyền sở hữu đối với một hành động như thế này, tôi đã từng sử dụng setters trong mọi thứ và đến lúc tôi nhận ra rằng ngay cả 1 setter, khi bị ảnh hưởng bởi quá nhiều đối tượng sẽ giết chết tôi ... tôi phải viết lại mọi thứ Việc sử dụng hợp lý các getters, sau khi thử nghiệm toàn diện quá lâu, nghiêm túc không có nhược điểm nếu bạn không phải là người theo chủ nghĩa thuần túy.
coolpasta

1
@coolpasta bạn có thể tạo đối tượng chủ sở hữu dữ liệu. Các đối tượng chủ sở hữu dữ liệu là các đối tượng dữ liệu, nhưng chúng được thiết kế và đặt tên sao cho chúng (rõ ràng) sở hữu dữ liệu. Tất nhiên những đối tượng này sẽ có getters.
rwong

1
@rwong Ý bạn là clearlygì? Nếu dữ liệu được truyền cho tôi từ một thứ gì đó, theo giá trị , rõ ràng, đối tượng của tôi hiện đang sở hữu dữ liệu, nhưng theo ngữ nghĩa, thì tại thời điểm này, nó không nhận được dữ liệu từ người khác. Sau đó, nó phải thực hiện các thao tác để chuyển đổi dữ liệu đó, biến nó thành dữ liệu của riêng nó, thời điểm dữ liệu được chạm vào, bạn phải thực hiện các thay đổi ngữ nghĩa: Bạn có dữ liệu được đặt tên $data_from_requestvà bây giờ bạn đã vận hành trên dữ liệu đó? Đặt tên cho nó $changed_data. Bạn muốn tiết lộ dữ liệu này cho người khác? Tạo một getter getChangedRequestData, sau đó, quyền sở hữu được thiết lập rõ ràng. Hoặc là nó?
coolpasta

1
"Bạn không thể viết mã tốt mà không có getters." Điều này là hoàn toàn sai, như là khái niệm rằng ranh giới vốn đã cần getters. Đây không phải là chủ nghĩa thực dụng, chỉ là sự lười biếng. Sẽ khá dễ dàng hơn một chút khi chỉ cần ném dữ liệu qua hàng rào và được thực hiện với nó, suy nghĩ về các trường hợp sử dụng và thiết kế một api hành vi tốt khó.
Robert Bräutigam

2
Câu trả lời tuyệt vời!
cmaster - phục hồi monica

10

Getters vi phạm Nguyên tắc Hollywood ("Đừng gọi cho chúng tôi, chúng tôi sẽ gọi cho bạn")

Nguyên tắc Hollywood (còn gọi là Inversion of Control) nói rằng bạn không gọi mã thư viện để hoàn thành công việc; thay vào đó, khung gọi mã của bạn. Bởi vì khung kiểm soát mọi thứ, việc truyền phát trạng thái nội bộ của nó tới các máy khách của nó là không cần thiết. Bạn không cần phải biết.

Ở dạng quỷ quyệt nhất, vi phạm Nguyên tắc Hollywood có nghĩa là bạn đang sử dụng một getter để có được thông tin về trạng thái của một lớp, sau đó đưa ra quyết định về cách gọi phương thức nào dựa trên giá trị mà bạn có được. đó là vi phạm đóng gói tốt nhất của nó.

Sử dụng một getter ngụ ý rằng bạn cần giá trị đó, khi bạn thực sự không.

Bạn thực sự có thể cần cải thiện hiệu suất

Trong các trường hợp cực đoan của các vật thể nhẹ phải có hiệu suất tối đa có thể, có thể (mặc dù rất khó xảy ra) rằng bạn không thể trả tiền phạt hiệu suất rất nhỏ mà người nhận được áp dụng. Điều này sẽ không xảy ra 99,9 phần trăm thời gian.


1
Tôi hiểu và tôi sẽ bay với sự thật của bạn, mà tôi không cần phải biết. . Nhưng tôi đã đạt đến một nơi mà tôi cần. Tôi có một Generatorđối tượng lặp qua tất cả Itemscác đối tượng của mình , sau đó gọi getNametừ mỗi đối tượng Itemđể làm gì đó hơn nữa. Vấn đề với điều này là gì? Sau đó, đổi lại, các Generatorchuỗi phun ra định dạng. Đây là trong khuôn khổ của tôi, sau đó tôi có API trên đó mọi người có thể sử dụng để chạy bất cứ thứ gì người dùng cung cấp nhưng không cần chạm vào khung.
coolpasta

3
What is the issue with this?Không ai mà tôi có thể nhìn thấy. Đó thực chất là những gì một mapchức năng làm. Nhưng đó không phải là câu hỏi bạn đã hỏi. Về cơ bản, bạn đã hỏi "Có bất kỳ điều kiện nào theo đó một getter có thể không được xem xét." Tôi đã trả lời với hai người, nhưng điều đó không có nghĩa là bạn từ bỏ setters hoàn toàn.
Robert Harvey

1
Bạn đang hỏi nhầm anh chàng câu hỏi đó. Tôi là người thực dụng; Tôi làm bất cứ điều gì phù hợp nhất với các chương trình cụ thể của mình và không đặt nhiều "nguyên tắc" trừ khi chúng phục vụ mục đích của tôi.
Robert Harvey

2
@ jpmc26: Khung. Không phải thư viện.
Robert Harvey

2
Một khung là một nỗ lực để biến một ngôn ngữ có mục đích chung thành một ngôn ngữ cụ thể theo miền. Một thư viện là một tập hợp các thuật toán có thể sử dụng lại. Một trong hai sẽ yêu cầu bạn phụ thuộc vào nó. Tùy thuộc vào bạn nếu bạn muốn mã cứng hoặc phụ thuộc hoặc làm cho phụ thuộc dễ dàng thay thế.
candied_orange

2

Chi tiết thực hiện rò rỉ Getters và phá vỡ trừu tượng

Xem xét public int millisecondsSince1970()

Khách hàng của bạn sẽ nghĩ "oh, đây là một int" và sẽ có một cuộc gọi gazillion giả định rằng nó là một int , làm toán số nguyên so sánh ngày tháng, vv ... Khi bạn nhận ra rằng bạn cần một dài , sẽ có rất nhiều mã di sản có vấn đề. Trong một thế giới Java, bạn thêm @deprecatedvào API, mọi người sẽ bỏ qua nó và bạn bị mắc kẹt trong việc duy trì mã lỗi, lỗi thời. :-( (Tôi giả sử các ngôn ngữ khác tương tự!)

Trong trường hợp cụ thể này, tôi không chắc lựa chọn nào tốt hơn và câu trả lời @candiedorange hoàn toàn chính xác, có nhiều lúc bạn cần getters, nhưng ví dụ này minh họa cho nhược điểm. Mỗi getter công khai có xu hướng "khóa" bạn vào một triển khai nhất định. Sử dụng chúng nếu bạn thực sự cần "vượt qua ranh giới", nhưng sử dụng ít nhất có thể và cẩn thận và biết trước.


Điều này đúng, nhưng giải pháp mẫu nào để thực thi kiểu / cấu trúc dữ liệu đối với biến của đối tượng, cũng như đối với bất kỳ thứ gì sử dụng getter cho biến đó?
coolpasta

1
Ví dụ này minh họa một hiện tượng khác, nỗi ám ảnh nguyên thủy. Ngày nay hầu hết các thư viện và khung công tác đều có các lớp đại diện timepointtimespantương ứng. ( epochlà một giá trị cụ thể của timepoint.) Trên các lớp này, chúng cung cấp các biểu đồ như getInthoặc getLonghoặc getDouble. Nếu các lớp thao túng thời gian được viết sao cho chúng (1) ủy thác các phép đo liên quan đến thời gian cho các đối tượng và phương thức này và (2) cung cấp quyền truy cập vào các đối tượng thời gian này thông qua getters, thì vấn đề được giải quyết, mà không cần phải loại bỏ getters .
rwong

1
Tóm lại, getters là một phần của API, và do đó, getters cần phải được thiết kế với sự cân nhắc đầy đủ cho tất cả các hậu quả, bao gồm cả quá khứ, hiện tại và tương lai. Nhưng điều đó không dẫn đến một kết luận về việc tránh hoặc giảm thiểu số lượng getters. Trong thực tế, nếu có nhu cầu trong tương lai để truy cập vào một phần thông tin cụ thể mà trình getter bị bỏ qua, thì nó sẽ yêu cầu thay đổi API phía thư viện và thay đổi mã. Do đó, quyết định về việc một getter cụ thể nên được thực hiện hay tránh nên dựa trên tên miền và mục đích, chứ không phải dựa trên lý do phòng ngừa.
rwong

1
"millisecondsSince1970" đã ngừng hoạt động trong 32 bit int trước cuối tháng 1 năm 1970, vì vậy tôi nghi ngờ sẽ có nhiều mã sử dụng nó :-)
gnasher729

1
@rwong Hơi đúng, nhưng bạn cho rằng các thư viện ưa thích đại diện cho các mốc thời gian là ổn định. Mà họ không. Ví dụ: khoảnh khắc.js
user949300

1

Tôi nghĩ rằng chìa khóa đầu tiên là phải nhớ rằng tuyệt đối luôn luôn sai.

Hãy xem xét một chương trình sổ địa chỉ, đơn giản là lưu trữ thông tin liên lạc của mọi người. Nhưng, hãy làm điều đó đúng và chia nó thành các lớp trình điều khiển / dịch vụ / kho lưu trữ.

Sẽ có một Personlớp chứa dữ liệu thực tế: tên, địa chỉ, số điện thoại, v.v. AddressPhonecũng là các lớp, vì vậy chúng ta có thể sử dụng đa hình sau này để hỗ trợ các chương trình ngoài Hoa Kỳ. Cả ba lớp này đều là Đối tượng truyền dữ liệu , vì vậy chúng sẽ không là gì ngoài getters và setters. Và, điều đó có ý nghĩa: họ sẽ không có bất kỳ logic nào trong họ ngoài việc ghi đè equalsgetHashcode; yêu cầu họ cung cấp cho bạn bản trình bày thông tin của một người đầy đủ không có ý nghĩa: đó có phải là bản trình bày HTML, ứng dụng GUI tùy chỉnh, ứng dụng bảng điều khiển, điểm cuối HTTP, v.v.?

Đó là nơi mà bộ điều khiển, dịch vụ và kho lưu trữ đến. Tất cả các lớp đó sẽ trở nên nặng về logic và nhẹ hơn, nhờ vào việc tiêm phụ thuộc.

Bộ điều khiển sẽ nhận được một dịch vụ tiêm, mà sẽ phơi bày các phương pháp như getsave, cả hai đều sẽ mất một lập luận hợp lý (ví dụ như,. getCó thể có ghi đè để tìm kiếm theo tên, tìm theo mã zip, hoặc chỉ nhận được tất cả mọi thứ; savecó thể sẽ mất một đơn Personvà lưu nó). Bộ điều khiển sẽ có những gì? Có thể lấy tên của lớp cụ thể có thể hữu ích cho việc đăng nhập, nhưng trạng thái nào sẽ giữ ngoài trạng thái được đưa vào?

Tương tự, lớp dịch vụ sẽ được lưu trữ một kho lưu trữ, vì vậy nó thực sự có thể nhận và tồn tại Personvà nó có thể có một số "logic nghiệp vụ" (ví dụ: có thể chúng ta sẽ yêu cầu tất cả Personcác đối tượng có ít nhất một địa chỉ hoặc điện thoại con số). Nhưng, một lần nữa: đối tượng đó có trạng thái nào khác ngoài những gì được tiêm? Một lần nữa, không có.

Lớp kho lưu trữ là nơi mọi thứ trở nên thú vị hơn một chút: nó sẽ thiết lập kết nối đến một vị trí lưu trữ, ví dụ. một tệp, cơ sở dữ liệu SQL hoặc lưu trữ trong bộ nhớ. Ở đây, thật hấp dẫn khi thêm một getter để lấy tên tệp hoặc chuỗi kết nối SQL, nhưng trạng thái đó đã được đưa vào lớp. Kho lưu trữ có nên có một getter để cho biết liệu nó có được kết nối thành công với kho dữ liệu của nó hay không? Vâng, có thể, nhưng thông tin đó sử dụng bên ngoài lớp kho lưu trữ là gì? Bản thân lớp kho lưu trữ sẽ có thể cố gắng kết nối lại với kho dữ liệu của nó nếu cần, điều này cho thấy rằng thuộc isConnectedtính đã có giá trị đáng ngờ. Hơn nữa, một isConnectedtài sản có thể sẽ sai ngay khi nó cần nhất là đúng: kiểm traisConnectedtrước khi thử lấy / lưu trữ dữ liệu không đảm bảo rằng kho lưu trữ vẫn sẽ được kết nối khi cuộc gọi "thực" được thực hiện, do đó, nó không loại bỏ nhu cầu xử lý ngoại lệ ở đâu đó (có những đối số sẽ đi đến đâu, ngoài ra phạm vi của câu hỏi này).

Cũng xem xét các bài kiểm tra đơn vị (bạn đang viết bài kiểm tra đơn vị, phải không?): Dịch vụ sẽ không mong đợi một lớp cụ thể được tiêm, thay vào đó sẽ hy vọng rằng việc triển khai giao diện cụ thể sẽ được đưa vào. Điều đó cho phép ứng dụng thay đổi toàn bộ nơi dữ liệu được lưu trữ mà không phải làm gì ngoài việc trao đổi kho lưu trữ được đưa vào dịch vụ. Nó cũng cho phép dịch vụ được kiểm tra đơn vị bằng cách tiêm một kho lưu trữ giả. Điều này có nghĩa là suy nghĩ về các giao diện hơn là các lớp. Giao diện kho lưu trữ sẽ tiết lộ bất kỳ getters? Đó là, có bất kỳ trạng thái nội bộ nào sẽ là: chung cho tất cả các kho lưu trữ, bên ngoài kho lưu trữ và không được đưa vào kho lưu trữ không? Tôi rất khó để nghĩ về bản thân mình.

TL; DR

Tóm lại: ngoài các lớp có mục đích duy nhất là mang dữ liệu đi khắp nơi, không có gì khác trong ngăn xếp có một nơi để đặt một getter: trạng thái hoặc được đưa vào hoặc không liên quan bên ngoài lớp làm việc.


Tôi thấy dòng suy nghĩ này rất giống với cuộc tranh luận về việc sử dụng đúng các thuộc tính C #. Nói tóm lại, các thuộc tính (có thể chỉ là getter hoặc getter-setter-cặp) được dự kiến ​​sẽ duy trì một hợp đồng nhất định, chẳng hạn như "những gì bạn đặt là những gì bạn nhận được", "getter nên chủ yếu là không có tác dụng phụ", "getter nên tránh ném lỗi", v.v ... Trái với kết luận của bạn, có nhiều trạng thái đối tượng có lợi khi được hiển thị dưới dạng các thuộc tính (hoặc getters). Ví dụ là quá nhiều để trích dẫn ở đây.
rwong

2
@rwong Tôi rất muốn biết một số ví dụ, giả sử ứng dụng "doanh nghiệp" "bình thường" được thực hiện bởi một nhóm. Chúng ta cũng giả sử vì đơn giản vì không có bên thứ ba tham gia. Bạn biết đấy, một hệ thống khép kín, như Lịch, Cửa hàng thú cưng, bất cứ điều gì bạn muốn.
Robert Bräutigam

1

Một điều, nếu không có ý kiến, nó sẽ bị phá vỡ nếu tôi sử dụng getters một cách hợp lý và cho dữ liệu rõ ràng tính toàn vẹn của đối tượng không bị phá vỡ nếu nó đưa ra?

Thành viên đột biến.

Nếu bạn có một bộ sưu tập các thứ trong một đối tượng mà bạn phơi bày thông qua một getter, bạn có khả năng phơi bày ra các lỗi liên quan đến những thứ sai được thêm vào bộ sưu tập.

Nếu bạn có một đối tượng có thể thay đổi mà bạn đang phơi bày thông qua một getter, bạn có khả năng mở ra trạng thái bên trong của đối tượng của bạn được thay đổi theo cách bạn không mong đợi.

Một ví dụ thực sự tồi tệ sẽ là một đối tượng đang đếm từ 1 đến 100 phơi bày giá trị Hiện tại của nó dưới dạng tham chiếu có thể thay đổi. Điều này cho phép một đối tượng bên thứ ba thay đổi giá trị của Hiện tại theo cách mà giá trị có thể nằm ngoài giới hạn dự kiến.

Đây chủ yếu là một vấn đề với việc phơi bày các đối tượng có thể thay đổi. Phơi bày cấu trúc hoặc các đối tượng không thay đổi hoàn toàn không phải là vấn đề, vì mọi thay đổi đối với những thay đổi đó sẽ thay đổi một bản sao (thường là bằng một bản sao ẩn trong trường hợp của một cấu trúc hoặc bởi một bản sao rõ ràng trong trường hợp của một đối tượng bất biến).

Sử dụng một phép ẩn dụ có thể giúp dễ dàng nhận thấy sự khác biệt.

Một bông hoa có một số điều độc đáo về nó. Nó có một Colorvà nó có một số cánh hoa (NumPetals). Nếu tôi đang quan sát bông hoa đó, trong thế giới thực, tôi có thể thấy rõ màu sắc của nó. Sau đó tôi có thể đưa ra quyết định dựa trên màu đó. Ví dụ: nếu màu là màu đen, đừng tặng cho bạn gái nhưng nếu màu là màu đỏ, hãy tặng cho bạn gái. Trong mô hình đối tượng của chúng tôi, màu sắc của hoa sẽ được phơi bày dưới dạng getter trên đối tượng hoa. Quan sát của tôi về màu sắc đó rất quan trọng đối với các hành động mà tôi sẽ thực hiện, nhưng nó hoàn toàn không ảnh hưởng đến đối tượng hoa. Tôi không thể thay đổi màu sắc của bông hoa đó. Tương tự, nó không có ý nghĩa để ẩn thuộc tính màu sắc. Một bông hoa nói chung không thể ngăn cản mọi người quan sát màu sắc của nó.

Nếu tôi nhuộm hoa, tôi nên gọi phương thức ReactToDye (Color dyColor) trên bông hoa, nó sẽ thay đổi bông hoa theo quy tắc nội bộ của nó. Sau đó tôi có thể truy vấn thuộc Colortính một lần nữa và phản ứng với bất kỳ thay đổi nào trong đó sau khi phương thức ReactToDye được gọi. Sẽ là sai lầm khi tôi trực tiếp sửa đổi Colorbông hoa và nếu tôi có thể thì sự trừu tượng đã bị phá vỡ.

Đôi khi (ít thường xuyên hơn, nhưng vẫn thường xuyên đủ để đề cập đến) setters là thiết kế OO khá hợp lệ. Nếu tôi có một đối tượng Khách hàng, việc hiển thị một setter cho địa chỉ của họ là hoàn toàn hợp lệ. Cho dù bạn gọi điều đó setAddress(string address)hay ChangeMyAddressBecauseIMoved(string newAddress)đơn giản string Address { get; set; }là vấn đề ngữ nghĩa. Trạng thái bên trong của đối tượng đó cần thay đổi và cách thích hợp để làm điều đó là đặt trạng thái bên trong của đối tượng đó. Ngay cả khi tôi yêu cầu một hồ sơ lịch sử về các địa chỉ mà người đó Customerđã sống, tôi có thể sử dụng trình thiết lập để thay đổi trạng thái nội bộ của mình một cách thích hợp để làm như vậy. Trong trường hợp này, nó không có ý nghĩa đối với sự Customerbất biến và không có cách nào tốt hơn để thay đổi địa chỉ hơn là cung cấp một setter để làm như vậy.

Tôi không chắc ai là người gợi ý rằng Getters và setters là xấu hoặc phá vỡ các mô hình hướng đối tượng, nhưng nếu có, họ có thể làm như vậy để đáp ứng với một tính năng ngôn ngữ cụ thể của ngôn ngữ mà họ tình cờ sử dụng. Tôi có cảm giác rằng điều này phát triển từ văn hóa "mọi thứ đều là đối tượng" của Java (và có thể mở rộng sang một số ngôn ngữ khác). Thế giới .NET hoàn toàn không có cuộc thảo luận này. Getters và Setters là một tính năng ngôn ngữ hạng nhất được sử dụng không chỉ bởi những người viết các ứng dụng bằng ngôn ngữ, mà còn bởi chính API ngôn ngữ.

Bạn nên cẩn thận khi sử dụng getters. Bạn không muốn phơi bày những mẩu dữ liệu sai và cũng không muốn phơi bày dữ liệu sai cách (tức là các đối tượng có thể thay đổi, tham chiếu đến các cấu trúc có thể cho phép người tiêu dùng sửa đổi trạng thái bên trong). Nhưng bạn muốn mô hình hóa các đối tượng của mình để dữ liệu được gói gọn theo cách mà các đối tượng của bạn có thể được sử dụng bởi người tiêu dùng bên ngoài và được bảo vệ khỏi sự lạm dụng bởi chính những người tiêu dùng đó. Thường thì điều đó sẽ đòi hỏi getters.

Để tóm tắt: Getters và Setters là các công cụ thiết kế hướng đối tượng hợp lệ. Chúng không được sử dụng một cách tự do đến mức mọi chi tiết triển khai đều được phơi bày nhưng bạn cũng không muốn sử dụng chúng một cách tiết kiệm đến mức người tiêu dùng của một đối tượng không thể sử dụng đối tượng một cách hiệu quả.


-1

Một điều, nếu không có ý kiến, nó sẽ bị phá vỡ nếu tôi sử dụng getters ...

Bảo trì sẽ phá vỡ.

Bất cứ lúc nào (tôi nên nói gần như luôn luôn) bạn cung cấp một getter mà về cơ bản bạn cho đi nhiều hơn so với yêu cầu của bạn. Điều này cũng có nghĩa là bạn phải duy trì nhiều hơn yêu cầu, cho phép bạn hạ thấp khả năng bảo trì mà không có lý do thực sự.

Hãy đi với Collectionví dụ của @ candied_orange . Trái ngược với những gì anh ấy viết Collection không nên có một getter. Bộ sưu tập tồn tại vì những lý do rất cụ thể, đáng chú ý nhất là lặp đi lặp lại trên tất cả các mục. Chức năng này không gây ngạc nhiên cho bất kỳ ai, vì vậy nó nên được triển khai trong Collection, thay vì đẩy trường hợp sử dụng rõ ràng này lên người dùng, sử dụng chu kỳ và getters hoặc không có gì.

Để công bằng, một số ranh giới làm getters cần. Điều này xảy ra nếu bạn thực sự không biết chúng sẽ được sử dụng cho mục đích gì. Ví dụ Exception, ngày nay bạn có thể lấy stacktrace từ lớp Java , bởi vì mọi người muốn sử dụng nó cho tất cả các lý do tò mò mà các nhà văn không hoặc không thể hình dung được.


1
Ví dụ. Hãy xem xét một hàm cần lặp lại hai Collections cùng một lúc, như trong (A1, B1), (A2, B2), .... Điều này đòi hỏi phải thực hiện "zip". Làm thế nào để bạn thực hiện "zip" nếu chức năng lặp lại được thực hiện trên một trong hai Collection- làm thế nào để bạn truy cập vào mục tương ứng trong cái kia?
rwong

@rwong Việc Collectionphải tự thực hiện zip, tùy thuộc vào cách thư viện được viết. Nếu không, bạn triển khai nó và gửi yêu cầu kéo cho người tạo thư viện. Lưu ý rằng tôi đã đồng ý rằng đôi khi một số ranh giới cần getters, vấn đề thực sự của tôi là getters hiện nay ở khắp mọi nơi.
Robert Bräutigam

Trong trường hợp đó, điều đó ngụ ý rằng thư viện phải có getters trên Collectionđối tượng, ít nhất là để thực hiện riêng của nó zip. (Nghĩa là, getter ít nhất phải có khả năng hiển thị gói.) Và bây giờ bạn phụ thuộc vào người tạo thư viện chấp nhận yêu cầu kéo của bạn ...
rwong

Tôi không hiểu bạn lắm. Các Collectionrõ ràng là có thể truy cập vào trạng thái nội bộ của riêng mình, hay nói chính xác hơn bất kỳ Collectionví dụ có thể truy cập vào trạng thái nội bộ của bất kỳ khác. Đó là cùng một loại, không cần getters.
Robert Bräutigam

Tôi cũng nghe thấy bạn, nhưng sau đó một lần nữa, chính xác giải pháp tốt hơn cho getters là gì? Tôi biết nó nằm ngoài phạm vi của câu hỏi.
coolpasta
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.