Không có đối tượng hành vi trong OOP - tiến thoái lưỡng nan thiết kế của tôi


94

Ý tưởng cơ bản đằng sau OOP là dữ liệu và hành vi (dựa trên dữ liệu đó) không thể tách rời và chúng được kết hợp bởi ý tưởng về một đối tượng của một lớp. Đối tượng có dữ liệu và phương thức hoạt động với điều đó (và dữ liệu khác). Rõ ràng theo các nguyên tắc của OOP, các đối tượng chỉ là dữ liệu (như cấu trúc C) được coi là một mô hình chống.

Càng xa càng tốt.

Vấn đề là tôi đã nhận thấy rằng mã của tôi dường như ngày càng đi theo hướng chống mẫu này gần đây. Dường như với tôi rằng tôi càng cố gắng đạt được thông tin ẩn giữa các lớp và các thiết kế được ghép lỏng lẻo, các lớp của tôi càng trở nên hỗn hợp giữa dữ liệu thuần túy không có lớp hành vi và tất cả hành vi không có lớp dữ liệu.

Tôi thường thiết kế các lớp theo cách giảm thiểu nhận thức của họ về sự tồn tại của các lớp khác và giảm thiểu kiến ​​thức của họ về các giao diện của các lớp khác. Tôi đặc biệt thực thi điều này theo kiểu từ trên xuống, các lớp cấp thấp hơn không biết về các lớp cấp cao hơn. Ví dụ:

Giả sử bạn có API trò chơi thẻ chung. Bạn có một lớp học Card. Bây giờ Cardlớp này cần xác định tầm nhìn cho người chơi.

Một cách là có boolean isVisible(Player p)trên Cardlớp.

Một cách khác là có boolean isVisible(Card c)trên Playerlớp.

Tôi không thích cách tiếp cận đầu tiên cụ thể vì nó cấp kiến ​​thức về Playerlớp cấp cao hơn cho lớp cấp thấp hơn Card.

Thay vào đó, tôi đã chọn tùy chọn thứ ba nơi chúng tôi có một Viewportlớp, trong đó có Playermột danh sách các thẻ xác định thẻ nào có thể nhìn thấy.

Tuy nhiên, cách tiếp cận này cướp đi cả hai CardPlayercác lớp của một chức năng thành viên có thể. Khi bạn làm điều này cho những thứ khác hơn là tầm nhìn của thẻ, bạn là trái với CardPlayerlớp học có chứa hoàn toàn dữ liệu như tất cả các chức năng được thực hiện trong các lớp khác, mà chủ yếu là các lớp học không có dữ liệu, chỉ cần phương pháp, giống như Viewportở trên.

Điều này rõ ràng chống lại ý tưởng chính của OOP.

Đó là cách chính xác? Làm thế nào tôi nên thực hiện nhiệm vụ giảm thiểu sự phụ thuộc của lớp và giảm thiểu kiến ​​thức và khớp nối giả định, nhưng không kết thúc với thiết kế kỳ lạ trong đó tất cả các lớp cấp thấp chỉ chứa dữ liệu và các lớp cấp cao chứa tất cả các phương thức? Có ai có bất kỳ giải pháp hoặc quan điểm thứ ba về thiết kế lớp mà tránh được toàn bộ vấn đề không?

PS Đây là một ví dụ khác:

Giả sử bạn có lớp DocumentIdkhông thay đổi, chỉ có một BigDecimal idthành viên duy nhất và một getter cho thành viên này. Bây giờ bạn cần phải có một phương thức ở đâu đó, DocumentIdtrả về Documentid này từ cơ sở dữ liệu.

Bạn có

  • Thêm Document getDocument(SqlSession)phương thức vào DocumentIdlớp, đột nhiên giới thiệu kiến ​​thức về sự kiên trì của bạn ( "we're using a database and this query is used to retrieve document by id"), API được sử dụng để truy cập DB và tương tự. Ngoài ra lớp này bây giờ yêu cầu tệp JAR kiên trì chỉ để biên dịch.
  • Thêm một số lớp khác với phương thức Document getDocument(DocumentId id), để lại DocumentIdlớp là chết, không có hành vi, lớp giống như cấu trúc.

21
Một số cơ sở của bạn ở đây là hoàn toàn sai, điều này sẽ làm cho việc trả lời câu hỏi cơ bản trở nên rất khó khăn. Giữ câu hỏi của bạn ngắn gọn và không có ý kiến ​​như bạn có thể và bạn sẽ nhận được câu trả lời tốt hơn.
pdr

31
"Điều này rõ ràng chống lại ý tưởng chính của OOP" - không, không phải, nhưng đó là một ngụy biện phổ biến.
Doc Brown

5
Tôi đoán vấn đề nằm ở chỗ trước đây đã có nhiều trường khác nhau cho "Định hướng đối tượng" - cách mà ban đầu nó có nghĩa là bởi những người như Alan Kay (xem geekswithbloss.net/theArchitectsNapkin/archive/2013/09/08/ Mạnh ), và cách được dạy bởi ngữ cảnh của OOA / OOD bởi những người từ Rational ( en.wikipedia.org/wiki/Object-oriented_analysis_and_design ).
Doc Brown

21
Đây là một câu hỏi rất hay và được đăng tốt - trái với một số ý kiến ​​khác, tôi nói. Nó cho thấy rõ ràng sự ngây thơ hoặc không đầy đủ của hầu hết các lời khuyên về cách cấu trúc chương trình là gì - và nó khó thực hiện như thế nào, và thiết kế phù hợp như thế nào trong nhiều tình huống cho dù người ta có cố gắng làm đúng đến mức nào. Và mặc dù một câu trả lời rõ ràng cho câu hỏi cụ thể là đa phương pháp, vấn đề cơ bản của việc thiết kế vẫn tồn tại.
Thiago Silva

5
Ai nói rằng các lớp không hành vi là một mô hình chống?
James Anderson

Câu trả lời:


42

Những gì bạn mô tả được gọi là một mô hình miền thiếu máu . Như với nhiều nguyên tắc thiết kế OOP (như Law of Demeter, v.v.), không đáng để cúi xuống chỉ để thỏa mãn một quy tắc.

Không có gì sai khi có các túi giá trị, miễn là chúng không làm lộn xộn toàn bộ cảnh quan và không dựa vào các vật thể khác để làm công việc dọn phòng mà chúng có thể tự làm .

Nó chắc chắn sẽ là một mùi mã nếu bạn có một lớp riêng chỉ để sửa đổi các thuộc tính của Card- nếu nó có thể được mong đợi một cách hợp lý để tự chăm sóc chúng.

Nhưng nó có thực sự là một công việc Cardđể biết Playernó có thể nhìn thấy không?

Và tại sao thực hiện Card.isVisibleTo(Player p), nhưng không Player.isVisibleTo(Card c)? Hoặc ngược lại?

Vâng, bạn có thể cố gắng đưa ra một số quy tắc cho điều đó như bạn đã làm - như Playerlà cấp cao hơn Card(?) - nhưng nó không đơn giản để đoán và tôi sẽ phải tìm ở nhiều nơi để tìm phương pháp.

Theo thời gian, nó có thể dẫn đến một sự thỏa hiệp thiết kế thối rữa khi thực hiện isVisibleTotrên cả hai lớp CardPlayerđiều mà tôi tin là không. Tại sao vậy? Bởi vì tôi đã tưởng tượng ngày đáng xấu hổ khi player1.isVisibleTo(card1)sẽ trả lại một giá trị khác so với card1.isVisibleTo(player1).tôi nghĩ - đó là chủ quan - điều này nên được thiết kế không thể thực hiện được .

Khả năng hiển thị lẫn nhau của thẻ và người chơi tốt hơn nên được chi phối bởi một loại đối tượng bối cảnh - có thể Viewport, Dealhoặc Game.

Nó không bằng có chức năng toàn cầu. Rốt cuộc, có thể có nhiều trò chơi đồng thời. Lưu ý rằng cùng một thẻ có thể được sử dụng đồng thời trên nhiều bảng. Chúng ta sẽ tạo ra nhiều Cardtrường hợp cho mỗi ace của spade?

Tôi vẫn có thể thực hiện isVisibleTotrên Card, nhưng chuyển một đối tượng bối cảnh cho nó và Cardủy quyền truy vấn. Chương trình để giao diện để tránh khớp nối cao.

Đối với ví dụ thứ hai của bạn - nếu ID tài liệu chỉ bao gồm một BigDecimal, tại sao lại tạo một lớp bao bọc cho nó?

Tôi muốn nói tất cả những gì bạn cần là một DocumentRepository.getDocument(BigDecimal documentID);

Nhân tiện, trong khi vắng mặt Java, có structs trong C #.

Xem

để tham khảo. Đó là một ngôn ngữ hướng đối tượng cao, nhưng không ai làm được gì lớn từ nó.


1
Chỉ cần lưu ý về các cấu trúc trong C #: Chúng không phải là cấu trúc điển hình, như bạn biết chúng trong C. Trên thực tế, chúng cũng hỗ trợ OOP với sự kế thừa, đóng gói và đa hình. Bên cạnh một số đặc thù, sự khác biệt chính là cách thời gian chạy xử lý các trường hợp khi chúng được truyền cho các đối tượng khác: cấu trúc là các loại giá trị và các lớp là các kiểu tham chiếu!
Aschratt

3
@Aschratt: Cấu trúc không hỗ trợ kế thừa. Các cấu trúc có thể thực hiện các giao diện, nhưng các cấu trúc thực hiện các giao diện hoạt động khác với các đối tượng lớp cũng làm như vậy. Mặc dù có thể tạo ra các cấu trúc hoạt động hơi giống các đối tượng, trường hợp sử dụng tốt nhất cho các cấu trúc khi người ta muốn một thứ gì đó hoạt động như cấu trúc C và những thứ mà người ta gói gọn là nguyên thủy hoặc các loại lớp bất biến khác.
supercat

1
+1 cho lý do "không bằng chức năng toàn cầu." Điều này đã không được giải quyết nhiều bởi những người khác. (Mặc dù nếu bạn có một số sàn, một hàm toàn cục vẫn sẽ trả về các giá trị khác nhau cho các trường hợp riêng biệt của cùng một thẻ).
alexis

@supercat Đây xứng đáng là một câu hỏi riêng biệt hoặc một phiên trò chuyện, nhưng hiện tại tôi không quan tâm đến :-( Bạn nói (trong C #) "các cấu trúc thực hiện giao diện hoạt động khác với các đối tượng lớp cũng tương tự". Tôi đồng ý rằng Có những khác biệt về hành vi để xem xét, nhưng AFAIK trong mã theo sau Interface iObj = (Interface)obj;, hành vi của iObjkhông bị ảnh hưởng bởi structhoặc classtrạng thái của obj(ngoại trừ đó sẽ là một bản sao được đóng hộp tại nhiệm vụ đó nếu là một struct).
Mark Hurd

150

Ý tưởng cơ bản đằng sau OOP là dữ liệu và hành vi (dựa trên dữ liệu đó) không thể tách rời và chúng được kết hợp bởi ý tưởng về một đối tượng của một lớp.

Bạn đang mắc lỗi phổ biến khi cho rằng các lớp là một khái niệm cơ bản trong OOP. Các lớp học chỉ là một cách đặc biệt phổ biến để đạt được đóng gói. Nhưng chúng ta có thể cho phép điều đó trượt.

Giả sử bạn có API trò chơi thẻ chung. Bạn có một thẻ lớp. Bây giờ lớp Thẻ này cần xác định mức độ hiển thị cho người chơi.

HEAVENS TỐT Khi bạn đang chơi Bridge, bạn có hỏi bảy trái tim khi nào là lúc thay đổi hình nộm từ một bí mật chỉ được biết đến với hình nộm để được mọi người biết đến? Dĩ nhiên là không. Đó không phải là một mối quan tâm của thẻ.

Một cách là có boolean isVisible (Người chơi p) trên lớp Thẻ. Một cách khác là có boolean isVisible (Thẻ c) trên lớp Người chơi.

Cả hai đều kinh khủng; đừng làm một trong những điều đó Cả người chơithẻ đều không chịu trách nhiệm thực hiện các quy tắc của Bridge!

Thay vào đó, tôi đã chọn tùy chọn thứ ba nơi chúng tôi có lớp Viewport, được cung cấp Trình phát và danh sách các thẻ xác định thẻ nào hiển thị.

Tôi chưa bao giờ chơi bài với "khung nhìn" trước đây, vì vậy tôi không biết lớp này có nghĩa vụ gì để gói gọn. Tôi đã chơi bài với một vài bộ bài, một số người chơi, một cái bàn và một bản sao của Hoyle. Viewport đại diện cho một trong những điều đó?

Tuy nhiên, cách tiếp cận này cướp đi cả hai lớp Thẻ và Người chơi của một chức năng thành viên có thể.

Tốt

Khi bạn thực hiện việc này cho các mục đích khác ngoài khả năng hiển thị của thẻ, bạn sẽ chỉ còn lại các lớp Thẻ và Trình phát chứa dữ liệu hoàn toàn vì tất cả chức năng được triển khai trong các lớp khác, hầu hết là các lớp không có dữ liệu, chỉ là các phương thức, như Viewport ở trên. Điều này rõ ràng chống lại ý tưởng chính của OOP.

Không; ý tưởng cơ bản của OOP là các đối tượng gói gọn mối quan tâm của họ . Trong hệ thống của bạn, một thẻ không quan tâm nhiều. Không phải là một người chơi. Điều này là do bạn đang mô hình chính xác thế giới . Trong thế giới thực, các thuộc tính là các thẻ có liên quan đến một trò chơi cực kỳ đơn giản. Chúng tôi có thể thay thế các hình ảnh trên thẻ bằng các số từ 1 đến 52 mà không làm thay đổi nhiều cách chơi của trò chơi. Chúng tôi có thể thay thế bốn người bằng ma-nơ-canh dán nhãn Bắc, Nam, Đông và Tây mà không làm thay đổi nhiều cách chơi của trò chơi. Người chơi và thẻ là những thứ đơn giản nhất trong thế giới trò chơi bài. Các quy tắc là những gì phức tạp, vì vậy lớp đại diện cho các quy tắc là nơi phức tạp nên được.

Bây giờ, nếu một trong những người chơi của bạn là AI, thì trạng thái bên trong của nó có thể cực kỳ phức tạp. Nhưng AI không xác định liệu nó có thể nhìn thấy thẻ hay không. Các quy tắc xác định rằng .

Đây là cách tôi thiết kế hệ thống của bạn.

Trước hết, thẻ phức tạp đáng ngạc nhiên nếu có các trò chơi có nhiều hơn một bộ bài. Bạn phải xem xét câu hỏi: người chơi có thể phân biệt giữa hai thẻ có cùng cấp bậc không? Nếu người chơi một người chơi một trong bảy trái tim, và sau đó một số thứ xảy ra, và sau đó người chơi hai chơi một trong bảy trái tim, liệu người chơi ba có thể xác định rằng đó là bảy trái tim giống nhau không? Hãy xem xét điều này một cách cẩn thận. Nhưng ngoài mối quan tâm đó, thẻ nên rất đơn giản; chúng chỉ là dữ liệu.

Tiếp theo, bản chất của một người chơi là gì? Một người chơi tiêu thụ một chuỗi các hành động có thể nhìn thấytạo ra một hành động .

Các đối tượng quy tắc là những gì phối hợp tất cả những điều này. Các quy tắc tạo ra một chuỗi các hành động có thể nhìn thấy và thông báo cho người chơi:

  • Người chơi một, mười trái tim đã được trao cho bạn bởi người chơi ba.
  • Người chơi thứ hai, một lá bài đã được người chơi ba trao cho người chơi thứ ba.

Và sau đó yêu cầu người chơi cho một hành động.

  • Người chơi một, bạn muốn làm gì?
  • Người chơi nói: treble the fromp.
  • Người chơi một, đó là một hành động bất hợp pháp bởi vì một cú ăn ba được tạo ra một gambit không thể bảo vệ.
  • Người chơi một, bạn muốn làm gì?
  • Người chơi nói: loại bỏ nữ hoàng thuổng.
  • Người chơi thứ hai, người chơi một đã loại bỏ nữ hoàng thuổng.

Và như vậy.

Tách cơ chế của bạn khỏi chính sách của bạn . Các chính sách của trò chơi nên được gói gọn trong một đối tượng chính sách , không phải trong các thẻ . Các thẻ chỉ là một cơ chế.


41
@gnat: Một ý kiến ​​trái ngược từ Alan Kay là "Thật ra tôi đã tạo ra thuật ngữ" hướng đối tượng "và tôi có thể nói với bạn rằng tôi không có C ++ trong đầu." Có những ngôn ngữ OO không có lớp; JavaScript đến với tâm trí.
Eric Lippert

19
@gnat: Tôi đồng ý rằng JS hiện tại không phải là một ví dụ tuyệt vời về ngôn ngữ OOP, nhưng nó cho thấy rằng người ta có thể dễ dàng xây dựng một ngôn ngữ OO mà không cần các lớp. Tôi đồng ý rằng cả đơn vị cơ bản của OO-ness trong Eiffel và C ++ đều là lớp; trong đó tôi không đồng ý với ý kiến ​​cho rằng các lớp là điều kiện thiết yếu của OO. Các thông số không phải của OO là các đối tượng gói gọn hành vi và giao tiếp với nhau thông qua giao diện chung được xác định rõ.
Eric Lippert

16
Tôi đồng ý với w / @EricLippert, các lớp không phải là nền tảng cho OO, cũng không phải là sự kế thừa, bất kể dòng chính có thể nói gì. Đóng gói dữ liệu, hành vi và trách nhiệm là, tuy nhiên đó là hoàn thành. Có những ngôn ngữ dựa trên nguyên mẫu ngoài Javascript là OO nhưng không có lớp. Điều đặc biệt sai lầm khi tập trung vào sự kế thừa đối với các khái niệm này. Điều đó nói rằng, các lớp học là phương tiện rất hữu ích để tổ chức đóng gói hành vi. Rằng bạn có thể coi các lớp là các đối tượng (và trong các ngôn ngữ nguyên mẫu, ngược lại) làm cho dòng bị mờ.
Schwern

6
Hãy xem xét nó theo cách này: hành vi nào mà một thẻ thực tế, trong thế giới thực, tự thể hiện? Tôi nghĩ rằng câu trả lời là "không có." Những thứ khác hành động trên thẻ. Bản thân thẻ, trong thế giới thực, theo nghĩa đen chỉ thông tin (4 câu lạc bộ), không có hành vi nội tại dưới bất kỳ hình thức nào. Làm thế nào thông tin đó (còn gọi là "thẻ") được sử dụng 100% cho một cái gì đó / ai đó, hay còn gọi là "quy tắc" và "người chơi". Các thẻ giống nhau có thể được sử dụng cho vô số trò chơi khác nhau (tốt, có thể không hoàn toàn), bởi bất kỳ số lượng người chơi khác nhau. Thẻ chỉ là một thẻ, và tất cả những gì nó có là tài sản.
Craig

5
@Montagist: Hãy để tôi làm rõ mọi thứ một chút sau đó. Hãy xem xét C. Tôi nghĩ bạn sẽ đồng ý rằng C không có các lớp. Tuy nhiên, bạn có thể nói rằng các cấu trúc là "các lớp", bạn có thể tạo các trường kiểu con trỏ hàm, bạn có thể xây dựng vtables, bạn có thể tạo các phương thức gọi là "constructor" để thiết lập vtables để một số cấu trúc "được kế thừa" từ nhau, vân vân Bạn có thể mô phỏng kế thừa dựa trên lớp trong C. Và bạn có thể mô phỏng nó trong JS. Nhưng làm như vậy có nghĩa là xây dựng một cái gì đó trên đầu ngôn ngữ chưa có ở đó.
Eric Lippert

29

Bạn đúng rằng việc ghép dữ liệu và hành vi là ý tưởng trung tâm của OOP, nhưng có nhiều hơn thế. Ví dụ, đóng gói : OOP / lập trình mô-đun cho phép chúng tôi tách giao diện công cộng khỏi chi tiết triển khai. Trong OOP, điều này có nghĩa là dữ liệu không bao giờ có thể truy cập công khai và chỉ được sử dụng thông qua các bộ truy cập. Theo định nghĩa này, một đối tượng không có bất kỳ phương thức nào thực sự là vô dụng.

Một lớp không cung cấp các phương thức nào ngoài các hàm truy cập về cơ bản là một cấu trúc quá phức tạp. Nhưng điều này không tệ, bởi vì OOP cho phép bạn linh hoạt thay đổi các chi tiết bên trong, điều mà một cấu trúc không có. Ví dụ: thay vì lưu trữ một giá trị trong trường thành viên, nó có thể được tính toán lại mỗi lần. Hoặc một thuật toán sao lưu được thay đổi, và với nó trạng thái phải được theo dõi.

Mặc dù OOP có một số lợi thế rõ ràng (đặc biệt là lập trình thủ tục đơn giản), nhưng thật ngây thơ khi phấn đấu cho dòng thuần OOP. Một số vấn đề không phù hợp với cách tiếp cận hướng đối tượng và được giải quyết dễ dàng hơn bằng các mô hình khác. Khi gặp phải một vấn đề như vậy, đừng nhấn mạnh vào một cách tiếp cận thấp kém.

  • Xem xét tính toán chuỗi Fibonacci theo cách hướng đối tượng . Tôi không thể nghĩ ra một cách lành mạnh để làm điều đó; lập trình có cấu trúc đơn giản cung cấp giải pháp tốt nhất cho vấn đề này.

  • isVisibleMối quan hệ của bạn thuộc về cả hai lớp, hoặc không, hoặc thực tế: với bối cảnh . Hồ sơ không có hành vi là điển hình của phương pháp lập trình chức năng hoặc thủ tục, có vẻ phù hợp nhất với vấn đề của bạn. Không có gì sai với

    static boolean isVisible(Card c, Player p);
    

    và không có gì sai Cardkhi không có phương pháp nào ngoài rankvà người truy cập suit.


11
@UMad vâng, đó chính xác là quan điểm của tôi, và không có gì sai với điều đó. Sử dụng mô hình <del> ngôn ngữ </ del> chính xác cho công việc hiện tại. (Nhân tiện, hầu hết các ngôn ngữ ngoài Smalltalk đều không hướng đối tượng thuần túy. Ví dụ: Java, C # và C ++ hỗ trợ lập trình bắt buộc, có cấu trúc, thủ tục, mô đun, chức năng và hướng đối tượng. Tất cả các mô hình không OO này đều có sẵn vì một lý do. : để bạn có thể sử dụng chúng)
amon

1
Có một cách OO hợp lý để thực hiện Fibonacci, gọi fibonacciphương thức trên một thể hiện của integer. Tôi muốn nhấn mạnh quan điểm của bạn rằng OO là về đóng gói, ngay cả ở những nơi dường như nhỏ. Hãy để số nguyên tìm ra cách thực hiện công việc. Sau này bạn có thể cải thiện việc thực hiện, thêm bộ nhớ đệm để cải thiện hiệu suất. Không giống như các hàm, các phương thức tuân theo dữ liệu để tất cả người gọi nhận được lợi ích của việc triển khai được cải thiện. Có thể các số nguyên chính xác tùy ý được thêm vào sau, chúng có thể được xử lý trong suốt như các số nguyên thông thường và có thể có fibonacciphương thức điều chỉnh hiệu suất của riêng chúng .
Schwern

2
@Schwern nếu bất cứ thứ gì Fibonaccilà một lớp con của lớp trừu tượng Sequence, chuỗi được sử dụng bởi bất kỳ bộ số nào và chịu trách nhiệm lưu trữ các hạt giống, trạng thái, bộ đệm và bộ lặp.
George Reith

2
Tôi không mong đợi, chỉ số OOP Fibonacci tinh khiết có hiệu quả trong việc bắn tỉa . Hãy dừng bất kỳ cuộc thảo luận vòng tròn nào về điều đó trong các bình luận này, mặc dù nó có một giá trị giải trí nhất định. Bây giờ chúng ta hãy làm một cái gì đó mang tính xây dựng để thay đổi!
amon

3
Sẽ thật ngớ ngẩn khi biến MySpace thành một phương pháp số nguyên, để bạn có thể nói đó là OOP. Đó là một chức năng và nên được coi như một chức năng.
Immibis

19

Ý tưởng cơ bản đằng sau OOP là dữ liệu và hành vi (dựa trên dữ liệu đó) không thể tách rời và chúng được kết hợp bởi ý tưởng về một đối tượng của một lớp. Đối tượng có dữ liệu và phương thức hoạt động với điều đó (và dữ liệu khác). Rõ ràng theo các nguyên tắc của OOP, các đối tượng chỉ là dữ liệu (như cấu trúc C) được coi là một mô hình chống. (...) Điều này rõ ràng chống lại ý tưởng chính của OOP.

Đây là một câu hỏi khó vì nó dựa trên khá nhiều tiền đề bị lỗi:

  1. Ý tưởng rằng OOP là cách duy nhất để viết mã.
  2. Ý tưởng rằng OOP là một khái niệm được xác định rõ. Nó trở thành một từ thông dụng đến nỗi thật khó để tìm thấy hai người có thể đồng ý về những gì OOP nói về.
  3. Ý tưởng rằng OOP là về gói dữ liệu và hành vi.
  4. Ý tưởng rằng mọi thứ là / nên là một sự trừu tượng.

Tôi sẽ không động đến # 1-3 nhiều, vì mỗi người có thể sinh ra câu trả lời của riêng mình và nó mời rất nhiều cuộc thảo luận dựa trên ý kiến. Nhưng tôi thấy ý tưởng "OOP là về việc kết hợp dữ liệu và hành vi" đặc biệt rắc rối. Nó không chỉ dẫn đến # 4, mà còn dẫn đến ý tưởng rằng mọi thứ nên là một phương pháp.

Có một sự khác biệt giữa các hoạt động xác định một loại và các cách bạn có thể sử dụng loại đó. Có thể truy xuất iphần tử thứ là điều cần thiết cho khái niệm của một mảng, nhưng sắp xếp chỉ là một trong nhiều điều tôi có thể chọn để làm với một mảng. Sắp xếp không cần phải là một phương thức nhiều hơn "tạo một mảng mới chỉ chứa các phần tử chẵn".

OOP là về việc sử dụng các đối tượng. Đối tượng chỉ là một cách để đạt được sự trừu tượng . Trừu tượng là một phương tiện để tránh sự ghép đôi không cần thiết trong mã của bạn, không phải là kết thúc cho chính nó. Nếu khái niệm về thẻ của bạn chỉ được xác định bởi giá trị của bộ và thứ hạng của nó, thì tốt nhất bạn nên thực hiện dưới dạng bộ hoặc đơn giản. Không có chi tiết không quan trọng nào mà bất kỳ phần nào khác của mã có thể tạo thành sự phụ thuộc vào. Đôi khi bạn không có gì để che giấu.

Bạn sẽ không tạo ra isVisiblemột phương pháp Cardloại vì có thể nhìn thấy có vẻ không cần thiết đối với khái niệm thẻ của bạn (trừ khi bạn có những thẻ rất đặc biệt có thể chuyển sang mờ hoặc mờ ...). Nó có nên là một phương pháp của các Playerloại? Chà, có lẽ đó cũng không phải là chất lượng xác định của người chơi. Nó có nên là một phần của một số Viewportloại? Một lần nữa, điều đó phụ thuộc vào việc bạn xác định chế độ xem là gì và liệu khái niệm kiểm tra mức độ hiển thị của thẻ có tách rời với việc xác định chế độ xem hay không.

Nó rất có thể isVisiblechỉ là một chức năng miễn phí.


1
+1 cho ý nghĩa thông thường thay vì mất trí nhớ.
đúng

Từ những dòng tôi đã đọc, bài luận bạn đã liên kết trông giống như một bài đọc vững chắc mà tôi chưa từng có.
Arthur Havlicek

@ArthurHavlicek Thật khó để theo dõi nếu bạn không hiểu các ngôn ngữ được sử dụng trong mẫu mã, nhưng tôi thấy nó khá sáng.
Doval

9

Rõ ràng theo các nguyên tắc của OOP, các đối tượng chỉ là dữ liệu (như cấu trúc C) được coi là một mô hình chống.

Không, họ không. Các đối tượng Plain-Old-Data là một mẫu hoàn toàn hợp lệ và tôi sẽ mong đợi chúng trong bất kỳ chương trình nào liên quan đến dữ liệu cần được duy trì hoặc liên lạc giữa các khu vực riêng biệt của chương trình của bạn.

Mặc dù lớp dữ liệu của bạn có thể tạo ra một Playerlớp đầy đủ khi nó đọc từ Playersbảng, nhưng nó có thể chỉ là một thư viện dữ liệu chung trả về POD với các trường từ bảng, nó chuyển đến một khu vực khác của chương trình mà bạn chuyển đổi một người chơi POD đến Playerlớp cụ thể của bạn .

Việc sử dụng các đối tượng dữ liệu, được gõ hoặc không được gõ, có thể không có ý nghĩa trong chương trình của bạn, nhưng điều đó không làm cho chúng trở thành một mô hình chống. Nếu chúng có ý nghĩa, hãy sử dụng chúng, và nếu chúng không, thì không.


5
Đừng không đồng ý với bất cứ điều gì bạn đã nói, nhưng điều này hoàn toàn không trả lời câu hỏi. Điều đó nói rằng, tôi đổ lỗi cho câu hỏi nhiều hơn câu trả lời.
pdr

2
Chính xác, thẻ và tài liệu chỉ là nơi chứa thông tin ngay cả trong thế giới thực và bất kỳ "mẫu" nào không thể xử lý nó cần phải được bỏ qua.
JeffO

1
Plain-Old-Data objects are a perfectly valid pattern Tôi không nói họ không, tôi nói sai khi họ điền vào toàn bộ nửa dưới của ứng dụng.
RokL

8

Cá nhân tôi nghĩ rằng Thiết kế hướng miền giúp mang lại sự rõ ràng cho vấn đề này. Câu hỏi tôi đặt ra là, làm thế nào để tôi mô tả trò chơi bài cho con người? Nói cách khác, tôi đang làm người mẫu là gì? Nếu thứ mà tôi đang mô hình hóa thực sự bao gồm từ "khung nhìn" và một khái niệm phù hợp với hành vi của nó, thì tôi sẽ tạo đối tượng khung nhìn và để nó làm những gì nó cần.

Tuy nhiên nếu tôi không có khái niệm về khung nhìn trong trò chơi của mình và đó là thứ tôi nghĩ tôi cần vì nếu không thì mã "cảm thấy sai". Tôi nghĩ hai lần về việc thêm nó vào mô hình miền của tôi.

Mô hình từ có nghĩa là bạn đang xây dựng một đại diện của một cái gì đó. Tôi thận trọng chống lại việc đưa vào một lớp đại diện cho một cái gì đó trừu tượng ngoài những thứ mà bạn đang đại diện.

Tôi sẽ chỉnh sửa để thêm rằng có thể bạn cần khái niệm về Viewport trong một phần khác của mã, nếu bạn cần giao diện với màn hình. Nhưng theo thuật ngữ DDD, đây sẽ là một mối quan tâm về cơ sở hạ tầng và sẽ tồn tại bên ngoài mô hình miền.


Câu trả lời của Lippert ở trên là một ví dụ tốt hơn về khái niệm này.
RibaldEddie

5

Tôi thường không tự quảng cáo, nhưng thực tế là tôi đã viết rất nhiều về các vấn đề thiết kế OOP trên blog của mình . Để tổng hợp nhiều trang: bạn không nên bắt đầu thiết kế với các lớp. Bắt đầu với các giao diện hoặc API và mã hình dạng từ đó có cơ hội cao hơn để cung cấp các tóm tắt có ý nghĩa, phù hợp với các thông số kỹ thuật và tránh các lớp cụ thể đầy hơi với mã không thể tái sử dụng.

Làm thế nào điều này áp dụng cho Card- Playervấn đề: Tạo ra một sự ViewPorttrừu tượng có ý nghĩa nếu bạn nghĩ CardPlayerlà hai thư viện độc lập (có nghĩa Playerlà đôi khi được sử dụng mà không có Card). Tuy nhiên, tôi có xu hướng nghĩ rằng Playergiữ Cardsvà nên cung cấp một người Collection<Card> getVisibleCards ()truy cập cho họ. Cả hai giải pháp này ( ViewPortvà của tôi) đều tốt hơn là cung cấp isVisiblenhư một phương pháp Cardhoặc Player, về mặt tạo mối quan hệ mã dễ hiểu.

Một giải pháp bên ngoài lớp học là nhiều, tốt hơn nhiều cho DocumentId. Có rất ít động lực để thực hiện (về cơ bản, một số nguyên) phụ thuộc vào một thư viện cơ sở dữ liệu phức tạp.


Tôi thích blog của bạn.
RokL

3

Tôi không chắc chắn câu hỏi trong tầm tay đang được trả lời đúng cấp độ. Tôi đã thúc giục những người khôn ngoan trong diễn đàn tích cực suy nghĩ về cốt lõi của câu hỏi ở đây.

U Mad đang đưa ra một tình huống mà anh ta tin rằng lập trình theo sự hiểu biết của anh ta về OOP nói chung sẽ dẫn đến rất nhiều nút lá là chủ sở hữu dữ liệu trong khi API cấp trên của anh ta bao gồm hầu hết các hành vi.

Tôi nghĩ rằng chủ đề hơi đi sâu vào việc liệu IsVisible sẽ được xác định trên Thẻ vs Người chơi; đó chỉ là một ví dụ minh họa, mặc dù ngây thơ.

Tôi đã đẩy những người có kinh nghiệm ở đây để xem xét vấn đề trong tay. Tôi nghĩ rằng có một câu hỏi hay mà U Mad đã đưa ra. Tôi hiểu rằng bạn sẽ đẩy các quy tắc và logic liên quan đến một đối tượng của riêng nó; nhưng theo tôi hiểu thì câu hỏi là

  1. Có ổn không khi có các cấu trúc giữ dữ liệu đơn giản (các lớp / cấu trúc; tôi không quan tâm đến những gì chúng được mô hình hóa cho câu hỏi này) mà không thực sự cung cấp nhiều chức năng?
  2. Nếu có, cách tốt nhất hoặc ưa thích để mô hình hóa chúng là gì?
  3. Nếu không, làm thế nào để chúng tôi kết hợp các bộ phận truy cập dữ liệu này vào các lớp API cao hơn (bao gồm cả hành vi)

Quan điểm của tôi:

Tôi nghĩ rằng bạn đang hỏi một câu hỏi về độ chi tiết rất khó để có được ngay trong lập trình hướng đối tượng. Theo kinh nghiệm nhỏ bé của tôi, tôi sẽ không bao gồm một thực thể trong mô hình của mình mà không bao gồm bất kỳ hành vi nào. Nếu tôi phải, có lẽ tôi đã sử dụng một cấu trúc được thiết kế để giữ sự trừu tượng như vậy không giống như một lớp có ý tưởng đóng gói dữ liệu và hành vi.


3
Vấn đề là câu hỏi (và câu trả lời của bạn) là về cách thực hiện mọi thứ "nói chung". Thực tế là, chúng tôi không bao giờ làm những việc "nói chung". Chúng tôi luôn làm những việc cụ thể. Cần phải kiểm tra những thứ cụ thể của chúng tôi và đo lường chúng theo yêu cầu của chúng tôi để xác định xem những thứ cụ thể của chúng tôi có phải là những thứ chính xác cho tình huống hay không.
John Saunders

@ JohnSaunders Tôi nhận thấy sự khôn ngoan của bạn ở đây và đồng ý ở một mức độ nào đó nhưng cũng chỉ có cách tiếp cận khái niệm cần thiết trước khi giải quyết một vấn đề. Rốt cuộc, câu hỏi ở đây không phải là kết thúc mở như nó có vẻ. Tôi nghĩ rằng đây là một câu hỏi hợp lệ của bất kỳ nhà thiết kế OO nào trong các ứng dụng ban đầu của OOP. Bạn lấy gì Nếu một sự trợ giúp có ích, chúng tôi có thể thảo luận về việc xây dựng một ví dụ về sự lựa chọn của bạn.
Harsha

Tôi đã ra khỏi trường hơn 35 năm rồi. Trong thế giới thực, tôi tìm thấy rất ít giá trị trong "cách tiếp cận khái niệm". Tôi tìm thấy kinh nghiệm để trở thành một giáo viên giỏi hơn Meyers trong trường hợp này.
John Saunders

Tôi không thực sự hiểu lớp về dữ liệu so với lớp để phân biệt hành vi. Nếu bạn trừu tượng các đối tượng của bạn đúng cách, không có sự phân biệt. Hãy tưởng tượng một Pointvới getX()chức năng. Bạn có thể tưởng tượng nó nhận được một trong những thuộc tính của nó, nhưng nó cũng có thể đọc nó từ đĩa hoặc internet. Nhận và thiết lập là hành vi và có các lớp thực hiện điều đó là hoàn toàn tốt. Cơ sở dữ liệu chỉ nhận và thiết lập dữ liệu fwiw
Arthur Havlicek

@ArthurHavlicek: Biết những gì một lớp học sẽ không thường hữu ích như biết những gì nó sẽ làm. Có một cái gì đó xác định trong hợp đồng của mình rằng nó sẽ hoạt động không khác gì một người giữ dữ liệu bất biến có thể chia sẻ, hoặc không có gì hơn một người giữ dữ liệu có thể thay đổi không thể chia sẻ, là hữu ích.
supercat

2

Một nguồn gây nhầm lẫn phổ biến trong OOP bắt nguồn từ việc nhiều đối tượng gói gọn hai khía cạnh của trạng thái: những điều họ biết và những điều biết về họ. Các cuộc thảo luận về trạng thái của các đối tượng thường bỏ qua khía cạnh thứ hai, vì trong các khung mà các tham chiếu đối tượng là lăng nhăng, không có cách nào chung để xác định những điều có thể biết về bất kỳ đối tượng nào có tham chiếu ra thế giới bên ngoài.

Tôi sẽ đề nghị rằng có lẽ sẽ hữu ích khi có một CardEntityđối tượng gói gọn các khía cạnh của thẻ trong các thành phần riêng biệt. Một thành phần sẽ liên quan đến các dấu hiệu trên thẻ (Ví dụ: "Diamond King" hoặc "Lava Blast; người chơi có cơ hội AC-3 để né tránh, nếu không sẽ gây sát thương 2D6"). Người ta có thể liên quan đến một khía cạnh duy nhất của trạng thái, chẳng hạn như vị trí (ví dụ như trong boong tàu, hoặc trong tay Joe, hoặc trên bàn trước mặt Larry). Một phần ba có thể liên quan để có thể nhìn thấy nó (có lẽ không ai, có thể một người chơi, hoặc có thể nhiều người chơi). Để đảm bảo rằng mọi thứ được giữ đồng bộ, những nơi mà một thẻ có thể sẽ không được gói gọn như các trường đơn giản, mà là CardSpacecác đối tượng; để di chuyển một thẻ vào một không gian, người ta sẽ cung cấp cho nó một tham chiếu đến đúngCardSpacevật; sau đó nó sẽ tự loại bỏ nó khỏi không gian cũ và đưa nó vào không gian mới).

Hoàn toàn gói gọn "những người biết về X" tách biệt với "những gì X biết" sẽ giúp tránh được nhiều sự nhầm lẫn. Đôi khi cần phải cẩn thận để tránh rò rỉ bộ nhớ, đặc biệt là với nhiều hiệp hội (ví dụ: nếu thẻ mới có thể tồn tại và thẻ cũ biến mất, người ta phải đảm bảo rằng thẻ nên bị bỏ không được gắn vĩnh viễn vào bất kỳ vật thể nào tồn tại lâu ) nhưng nếu sự tồn tại của các tham chiếu đến một đối tượng sẽ tạo thành một phần có liên quan của trạng thái của nó, thì chính đối tượng đó sẽ đóng gói một cách rõ ràng thông tin đó (ngay cả khi nó ủy thác cho một số lớp khác công việc thực sự quản lý nó).


0

Tuy nhiên, cách tiếp cận này cướp đi cả hai lớp Thẻ và Người chơi của một chức năng thành viên có thể.

Và làm thế nào là xấu / khuyên?

Để sử dụng một sự tương tự tương tự như ví dụ về thẻ của bạn, hãy xem xét a Car, a Drivervà bạn cần xác định xem liệu Drivercó thể điều khiển được không Car.

OK, vì vậy bạn đã quyết định rằng bạn không muốn Carbiết mình Drivercó chìa khóa xe phù hợp hay không, và vì một số lý do không rõ, bạn cũng quyết định rằng bạn không muốn Driverbiết về Carlớp học (bạn không hoàn toàn xác thịt Điều này trong câu hỏi ban đầu của bạn là tốt). Do đó, bạn có một lớp trung gian, một cái gì đó dọc theo dòng của một Utilslớp, chứa phương thức với các quy tắc kinh doanh để trả về một booleangiá trị cho câu hỏi trên.

Tôi nghĩ rằng điều này là tốt. Lớp trung gian có thể chỉ cần kiểm tra chìa khóa xe ngay bây giờ, nhưng có thể được tái cấu trúc để xem xét liệu người lái xe có bằng lái xe hợp lệ, dưới ảnh hưởng của rượu hoặc trong tương lai của bác sĩ da liễu, kiểm tra sinh trắc học DNA. Bằng cách đóng gói, thực sự không có vấn đề lớn nào khi ba lớp này cùng tồn tại.

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.