Có hương vị của OOP trong đó một số hoặc tất cả các nguyên tắc RẮN là phản đối với mã sạch?


26

Gần đây tôi đã có một cuộc thảo luận với một người bạn của tôi về OOP trong phát triển trò chơi video.

Tôi đang giải thích kiến ​​trúc của một trong những trò chơi của tôi, trong sự ngạc nhiên của bạn tôi, có nhiều lớp nhỏ và một vài lớp trừu tượng. Tôi lập luận rằng đây là kết quả của việc tôi tập trung vào việc trao mọi thứ cho một Trách nhiệm duy nhất và cũng để nới lỏng sự ghép nối giữa các thành phần.

Mối quan tâm của anh là số lượng lớn các lớp học sẽ chuyển thành cơn ác mộng bảo trì. Quan điểm của tôi là nó sẽ có tác dụng ngược lại chính xác. Chúng tôi đã tiến hành thảo luận về vấn đề này trong nhiều thế kỷ, cuối cùng đồng ý không đồng ý, nói rằng có lẽ có những trường hợp nguyên tắc RẮN và OOP đúng đắn không thực sự kết hợp tốt.

Ngay cả mục nhập Wikipedia về các nguyên tắc RẮN cũng nói rằng chúng là các hướng dẫn giúp viết mã có thể duy trì và chúng là một phần của chiến lược tổng thể về lập trình thích ứng và nhanh nhẹn.

Vì vậy, câu hỏi của tôi là:

Có trường hợp nào trong OOP khi một số hoặc tất cả các nguyên tắc RẮN không cho vay để làm sạch mã không?

Tôi có thể tưởng tượng ngay rằng Nguyên tắc thay thế Liskov có thể có thể xung đột với một hương vị khác của sự kế thừa an toàn. Điều đó có nghĩa là, nếu ai đó nghĩ ra một mô hình hữu ích khác được thực hiện thông qua thừa kế, thì hoàn toàn có khả năng LSP có thể xung đột trực tiếp với nó.

Có những người khác? Có lẽ một số loại dự án hoặc nền tảng mục tiêu nhất định hoạt động tốt hơn với cách tiếp cận RẮN ít hơn?

Chỉnh sửa:

Tôi chỉ muốn xác định rằng tôi không hỏi cách cải thiện mã của mình;) Lý do duy nhất tôi đề cập đến một dự án trong câu hỏi này là đưa ra một bối cảnh nhỏ. Câu hỏi của tôi là về OOP và các nguyên tắc thiết kế nói chung .

Nếu bạn tò mò về dự án của tôi, hãy xem cái này .

Chỉnh sửa 2:

Tôi tưởng tượng câu hỏi này sẽ được trả lời theo một trong 3 cách:

  1. Có, tồn tại các nguyên tắc thiết kế OOP xung đột một phần với RẮN
  2. Có, tồn tại các nguyên tắc thiết kế OOP hoàn toàn xung đột với RẮN
  3. Không, RẮN là đầu gối của con ong và OOP sẽ mãi mãi tốt hơn với nó. Nhưng, như với tất cả mọi thứ, nó không phải là thuốc chữa bách bệnh. Hay Uông co trach nhiệm.

Tùy chọn 1 và 2 có thể đã tạo ra các câu trả lời dài và thú vị. Mặt khác, lựa chọn 3 sẽ là một câu trả lời ngắn gọn, không thú vị, nhưng rất yên tâm.

Chúng tôi dường như đang hội tụ vào tùy chọn 3.


Làm thế nào để bạn xác định "mã sạch"?
GrandmasterB

Mã sạch, trong phạm vi của câu hỏi này, là cấu trúc mã theo cách đáp ứng các mục tiêu của OOP và một bộ các nguyên tắc thiết kế được chọn. Vì vậy, tôi quen thuộc với mã sạch về các mục tiêu được đặt bởi OOP + RẮN. Tôi tự hỏi liệu có một bộ nguyên tắc thiết kế nào hoạt động với OOP nhưng hoàn toàn không tương thích hoặc một phần với RẮN.
MetaFight

1
Tôi lo lắng nhiều hơn về hiệu suất của trò chơi đó của bạn hơn bất cứ điều gì khác. Trừ khi chúng ta đang nói về trò chơi dựa trên văn bản.
Euphoric

1
Trò chơi này thực chất là một thử nghiệm. Tôi đang thử các cách tiếp cận phát triển khác nhau và tôi cũng đang thử xem liệu mã doanh nghiệp có thể hoạt động khi viết trò chơi hay không. Tôi hy vọng hiệu suất sẽ trở thành một vấn đề, nhưng nó chưa xảy ra. Nếu / khi nó xảy ra, thì điều tiếp theo tôi sẽ thử nghiệm là làm thế nào để điều chỉnh mã của tôi thành mã hiệu suất. Học nhiều quá!
MetaFight

1
Trước nguy cơ của một số downvote, tôi muốn quay lại câu hỏi "tại sao": OOP trong phát triển trò chơi . Do tính chất của phát triển trò chơi, OOP "truyền thống" dường như không còn thịnh hành trong việc ủng hộ các hương vị khác nhau của các hệ thống thực thể / thành phần, một biến thể thú vị ở đây chẳng hạn. Điều này khá khiến tôi nghĩ rằng câu hỏi không nên là "RẮN + OOP" mà là "OOP + Phát triển trò chơi". Khi bạn nhìn vào một biểu đồ tương tác đối tượng cho nhà phát triển trò chơi. so với ứng dụng N-tier, sự khác biệt có thể có nghĩa là một mô hình mới là tốt.
J Trana

Câu trả lời:


20

Có trường hợp nào trong OOP khi một số hoặc tất cả các nguyên tắc RẮN không cho vay để làm sạch mã không?

Nói chung, không. Lịch sử đã chỉ ra rằng tất cả các nguyên tắc RẮN góp phần lớn vào việc tách rời, do đó đã được chứng minh là tăng tính linh hoạt trong mã và do đó khả năng của bạn có thể điều chỉnh thay đổi cũng như làm cho mã dễ dàng hơn để kiểm tra, kiểm tra, tái sử dụng .. Tóm lại, làm cho mã của bạn sạch hơn.

Bây giờ, có thể có trường hợp các nguyên tắc RẮN va chạm với DRY (không lặp lại chính mình), KISS (giữ cho nó đơn giản ngu ngốc) hoặc các nguyên tắc khác của thiết kế OO tốt. Và tất nhiên, họ có thể va chạm với thực tế của các yêu cầu, những hạn chế của con người, những hạn chế của ngôn ngữ lập trình của chúng ta, những trở ngại khác.

Nói tóm lại, các nguyên tắc RẮN sẽ luôn cho vay để làm sạch mã, nhưng trong một số trường hợp, chúng sẽ tự cho vay ít hơn so với các lựa chọn thay thế xung đột. Họ luôn luôn tốt, nhưng đôi khi những thứ khác tốt hơn .


14
Tôi thích nhưng đôi khi những thứ khác tốt hơn . Nó có cảm giác "Trại súc vật". ;)
Thất vọngWithFormsDesigner

12
+1 Khi tôi chỉ nhìn vào các nguyên tắc RẮN, tôi thấy 5 "tốt đẹp để có". Nhưng không ai trong số họ đứng đầu KHÔ, HÔN, và "làm rõ ý định của bạn". Sử dụng RẮN, nhưng cho thấy một số thận trọng và hoài nghi.
user949300

Tôi đồng ý. Tôi nghĩ rằng các nguyên tắc chính xác phải tuân theo rõ ràng phụ thuộc vào tình huống, nhưng ngoài các yêu cầu về hiệu suất cứng, luôn xếp trên mọi thứ khác (vấn đề này đặc biệt là trong phát triển trò chơi), tôi có xu hướng nghĩ rằng DRY và KISS thường quan trọng hơn hơn RẮN. Tất nhiên, bạn càng làm cho mã càng sạch thì càng tốt, vì vậy nếu có thể làm theo tất cả các nguyên tắc mà không có xung đột, thì tất cả sẽ tốt hơn.
Ben Lee

Điều gì về sự tách biệt tích hợp so với thiết kế hiệu quả của cốt liệu? Nếu giao diện "chuỗi" cơ bản [ví dụ IEnumerable] bao gồm các phương thức như CountasImmutablevà các thuộc tính như getAbilities[trả về sẽ cho biết liệu những thứ như Countsẽ "hiệu quả"], thì người ta có thể có một phương thức tĩnh có nhiều chuỗi và tổng hợp chúng để chúng Sẽ hành xử như một chuỗi dài hơn. Nếu các khả năng như vậy có trong loại trình tự cơ bản, ngay cả khi chúng chỉ xâu chuỗi thành các triển khai mặc định, thì một tổng hợp sẽ có thể phơi bày các khả năng đó ...
supercat

... Và thực hiện chúng một cách hiệu quả khi được sử dụng với các lớp cơ sở có thể làm như vậy. Nếu một người tổng hợp danh sách mười triệu mục biết số lượng của nó và danh sách năm mục chỉ có thể truy xuất các mục theo tuần tự, yêu cầu mục 10.000.003 nên đọc số đếm từ danh sách đầu tiên, sau đó đọc ba mục từ mục thứ hai . Giao diện bồn rửa nhà bếp có thể vi phạm ISP, nhưng chúng có thể cải thiện đáng kể hiệu năng của một số tình huống tổng hợp / tổng hợp.
supercat

13

Tôi nghĩ rằng tôi có một quan điểm khác thường ở chỗ tôi đã làm việc trong cả tài chính và trò chơi.

Nhiều lập trình viên tôi gặp trong các trò chơi rất tệ ở Kỹ thuật phần mềm - nhưng họ không cần thực hành như RẮN. Theo truyền thống, họ đặt trò chơi của họ vào một hộp - thế là xong.

Về tài chính, tôi thấy các nhà phát triển có thể rất cẩu thả và vô kỷ luật vì họ không cần hiệu suất mà bạn làm trong các trò chơi.

Cả hai tuyên bố trên tất nhiên là quá khái quát, nhưng thực ra, trong cả hai trường hợp, mã sạch là rất quan trọng. Để tối ưu hóa mã rõ ràng và dễ hiểu là điều cần thiết. Vì lợi ích của bảo trì, bạn muốn điều tương tự.

Điều đó không có nghĩa là RẮN không phải là không có sự chỉ trích. Chúng được gọi là nguyên tắc và chúng được cho là tuân theo nguyên tắc? Vậy chính xác khi nào tôi nên theo dõi họ? Khi nào tôi nên phá vỡ các quy tắc?

Bạn có thể có thể nhìn vào hai đoạn mã và nói cái nào phù hợp nhất với RẮN. Nhưng trong sự cô lập, không có cách khách quan nào để chuyển đổi mã thành RẮN. Nó chắc chắn là để giải thích của các nhà phát triển.

Thật không phải là một trình tự để nói rằng, "một số lượng lớn các lớp học có thể dẫn đến một cơn ác mộng bảo trì". Tuy nhiên, nếu SRP không được giải thích chính xác, bạn có thể dễ dàng gặp phải vấn đề này.

Tôi đã thấy mã với nhiều lớp khá nhiều không có trách nhiệm. Các lớp không làm gì ngoài việc chuyển trạng thái từ lớp này sang lớp khác. Các vấn đề cổ điển của quá nhiều lớp không xác định.

SRP nếu bị lạm dụng có thể kết thúc với sự thiếu gắn kết trong mã của bạn. Bạn có thể nói điều này khi bạn cố gắng thêm một tính năng. Nếu bạn luôn phải thay đổi một vài nơi cùng một lúc thì mã của bạn thiếu sự gắn kết.

Mở-Đóng không phải là không có các nhà phê bình của nó. Ví dụ, xem đánh giá của Jon Skeet về nguyên tắc Đóng mở . Tôi sẽ không tái tạo lập luận của anh ấy ở đây.

Lập luận của bạn về LSP có vẻ khá giả thuyết và tôi không thực sự hiểu ý của bạn về một hình thức thừa kế an toàn khác?

ISP dường như trùng lặp SRP. Chắc chắn chúng phải được giải thích giống như bạn không bao giờ có thể lường trước được tất cả các máy khách của giao diện của bạn sẽ làm gì. Giao diện gắn kết của ngày hôm nay là vi phạm ISP của ngày mai. Kết luận phi logic duy nhất là không có nhiều hơn một thành viên trên mỗi giao diện.

Dip có thể dẫn đến mã không sử dụng được. Tôi đã thấy các lớp có số lượng lớn các tham số trong tên của DIP. Các lớp này thường sẽ "mới" lên các phần tổng hợp của chúng, nhưng tôi là tên của khả năng kiểm tra, chúng ta không bao giờ phải sử dụng từ khóa mới nữa.

RẮN tự nó không đủ để bạn viết mã sạch. Tôi đã xem cuốn sách của Robert C. Martin, " Clean Code: A Handbook of Agile Software Crafts ". Vấn đề lớn nhất với nó là thật dễ dàng để đọc cuốn sách này và giải thích nó như các quy tắc. Nếu bạn làm điều đó, bạn đang thiếu điểm. Nó chứa một danh sách lớn các mùi, heuristic và nguyên tắc - nhiều hơn cả năm từ RẮN. Tất cả những 'nguyên tắc' này không thực sự là nguyên tắc nhưng hướng dẫn. Chú Bob coi họ là "lỗ khối" cho các khái niệm. Chúng là một hành động cân bằng; làm theo bất kỳ hướng dẫn nào một cách mù quáng sẽ gây ra vấn đề.


1
Nếu bạn có một số lượng lớn các tham số hàm tạo, điều đó cho thấy rằng bạn đã vi phạm SRP. Tuy nhiên, một thùng chứa DI sẽ loại bỏ nỗi đau liên quan đến số lượng lớn các tham số của hàm tạo, vì vậy tôi không đồng ý với các tuyên bố của bạn về DIP.
Stephen

Tất cả những 'nguyên tắc' này không thực sự là nguyên tắc nhưng hướng dẫn. Chúng là một hành động cân bằng; như tôi đã nói trong bài viết của mình sau SRP, bản thân nó có thể có vấn đề.
Dave Hillier

Câu trả lời hay, +1. Một điều tôi muốn nói thêm: Tôi đã đọc bài đánh giá của Skeet một bài phê bình về định nghĩa sách giáo khoa tiêu chuẩn về OCP, chứ không phải về những ý tưởng cốt lõi đằng sau OCP. Theo hiểu biết của tôi, ý tưởng biến thể được bảo vệ là những gì OCP nên có từ nơi đầu tiên.
Doc Brown

5

Đây là ý kiến ​​của tôi:

Mặc dù các nguyên tắc RẮN nhắm đến một cơ sở mã không dự phòng và linh hoạt, đây có thể là sự đánh đổi về khả năng đọc và bảo trì nếu có quá nhiều lớp và lớp.

Đặc biệt là về số lượng trừu tượng . Một số lượng lớn các lớp học có thể ổn nếu chúng dựa trên một vài khái niệm trừu tượng. Vì chúng tôi không biết quy mô và chi tiết cụ thể của dự án của bạn, thật khó để nói, nhưng thật đáng lo ngại khi bạn đề cập đến một số lớp cùng với một số lượng lớn các lớp.

Tôi đoán các nguyên tắc RẮN không đồng ý với OOP, nhưng vẫn có thể viết mã không sạch tuân thủ chúng.


3
+1 Theo định nghĩa gần như, một hệ thống rất linh hoạt phải ít bảo trì hơn một hệ thống kém linh hoạt. Tính linh hoạt đi kèm với chi phí do sự phức tạp thêm vào mà nó mang lại.
Andy

@Andy không nhất thiết. trong công việc hiện tại của tôi, rất nhiều phần tồi tệ nhất của mã là lộn xộn bởi vì nó chỉ bị ném vào nhau bởi "làm một cái gì đó đơn giản, chỉ cần hack nó và làm cho nó hoạt động để nó không quá phức tạp". Kết quả là, nhiều phần của hệ thống cứng 100%. Bạn hoàn toàn không thể sửa đổi chúng mà không cần viết lại phần lớn của chúng. Thật sự rất khó để duy trì. Khả năng bảo trì đến từ sự gắn kết cao và khớp nối thấp, không phải từ hệ thống linh hoạt như thế nào.
sara

3

Trong những ngày đầu phát triển, tôi bắt đầu viết một Roguelike bằng C ++. Khi tôi muốn áp dụng phương pháp hướng đối tượng tốt mà tôi đã học được từ giáo dục của mình, tôi ngay lập tức thấy các mục trò chơi là đối tượng C ++. Potion, Swords, Food, Axe, JavelinsVv tất cả bắt nguồn từ một lõi cơ bản Itemmã, xử lý tên, biểu tượng, trọng lượng, mà loại công cụ.

Sau đó, tôi mã hóa túi logic và gặp phải một vấn đề. Tôi có thể bỏ Itemsvào túi, nhưng sau đó, nếu tôi chọn một cái, làm sao tôi biết đó là a Potionhay a Sword? Tôi tìm trên Internet làm thế nào để hạ đồ. Tôi đã hack các tùy chọn trình biên dịch để kích hoạt thông tin thời gian chạy để tôi biết Itemloại thực sự trừu tượng của mình là gì và bắt đầu chuyển đổi logic trên các loại để biết những hoạt động nào tôi sẽ cho phép (ví dụ như tránh uống rượu Swordsvà đặt lên Potionschân)

Về cơ bản, nó biến mã trong một quả bóng lớn được sao chép một nửa. Khi dự án tiếp tục, tôi bắt đầu hiểu thế nào là thất bại trong thiết kế. Điều đã xảy ra là tôi đã cố gắng sử dụng các lớp để thể hiện các giá trị. Hệ thống phân cấp lớp học của tôi không phải là một sự trừu tượng có ý nghĩa. Những gì tôi nên làm là làm cho Itemthực hiện một tập hợp các chức năng, chẳng hạn như Equip (), Apply ()Throw (), và làm cho mỗi cư xử dựa trên các giá trị. Sử dụng enum để đại diện cho các khe trang bị. Sử dụng enum để đại diện cho các loại vũ khí khác nhau. Sử dụng nhiều giá trị hơn và ít phân loại hơn, bởi vì phân lớp phụ của tôi không có mục đích nào khác ngoài việc điền vào các giá trị cuối cùng này.

Vì bạn đang phải đối mặt với sự nhân lên của đối tượng dưới một lớp trừu tượng, tôi nghĩ rằng cái nhìn sâu sắc của tôi có thể có giá trị ở đây. Nếu bạn dường như có quá nhiều loại đối tượng, có thể là bạn đang nhầm lẫn giữa những gì nên là loại với những gì nên là giá trị.


2
Vấn đề là các loại nhầm lẫn với các lớp giá trị (lưu ý: Tôi không sử dụng lớp từ để chỉ khái niệm OOP / C ++ của lớp.) Có nhiều hơn một cách để biểu thị một điểm trong mặt phẳng cartes (tọa độ hình chữ nhật, cực tọa độ) nhưng chúng đều là một phần của cùng một loại. Tuy nhiên, các ngôn ngữ OOP chính thống không hỗ trợ phân loại giá trị một cách tự nhiên. Điều gần nhất đó là sự kết hợp của C / C ++, nhưng bạn cần thêm một trường bổ sung chỉ để biết bạn đang đặt cái quái gì vào đó.
Doval

4
Kinh nghiệm của bạn có thể được sử dụng như là ví dụ chống. Bằng cách không sử dụng RẮN cũng như bất kỳ loại phương pháp mô hình hóa nào, bạn đã tạo ra mã không thể nhầm lẫn và không thể kiểm soát được.
Euphoric

1
@Euphoric Mình đã cố gắng rất nhiều. Tôi đã có phương pháp hướng đối tượng. Tuy nhiên, có một sự khác biệt giữa việc có một phương pháp và thực hiện thành công nó :)
Arthur Havlicek

2
Làm thế nào đây là một ví dụ về các nguyên tắc RẮN chạy ngược lại với mã sạch trong OOP? Nó có vẻ giống như một ví dụ về một thiết kế không chính xác - đây là trực giao với OOP!
Andres F.

1
Lớp ITEM cơ sở của bạn nên có một số phương thức boolean "canXXXX", tất cả đều mặc định là FALSE. Sau đó, bạn có thể đặt "canThrow ()" trả về true trong lớp Javelin của mình và canEat () để trả về true cho lớp Postion của bạn.
James Anderson

3

Mối quan tâm của anh là số lượng lớn các lớp học sẽ chuyển thành cơn ác mộng bảo trì. Quan điểm của tôi là nó sẽ có tác dụng ngược lại chính xác.

Tôi hoàn toàn đứng về phía bạn của bạn, nhưng đó có thể là vấn đề thuộc về lĩnh vực của chúng tôi và các loại vấn đề và thiết kế chúng tôi giải quyết và đặc biệt là những loại điều nào có thể yêu cầu thay đổi trong tương lai. Vấn đề khác nhau, giải pháp khác nhau. Tôi không tin đúng hay sai, chỉ là các lập trình viên cố gắng tìm ra cách tốt nhất họ có thể để giải quyết tốt nhất các vấn đề thiết kế cụ thể của họ. Tôi làm việc trong VFX, không giống với các công cụ trò chơi.

Nhưng vấn đề đối với tôi mà tôi phải vật lộn với cái mà ít nhất có thể được gọi là kiến ​​trúc tuân thủ RẮN hơn (dựa trên COM), có thể bị luộc xuống thành "quá nhiều lớp" hoặc "quá nhiều chức năng" như bạn của bạn có thể mô tả. Tôi đặc biệt nói, "quá nhiều tương tác, quá nhiều nơi có thể xảy ra sai, quá nhiều nơi có thể gây ra tác dụng phụ, quá nhiều nơi có thể cần phải thay đổi và quá nhiều nơi không thể làm những gì chúng ta nghĩ họ làm . "

Chúng tôi đã có một số giao diện trừu tượng (và thuần túy) được triển khai bởi một nhóm các kiểu con, giống như vậy (tạo sơ đồ này trong bối cảnh nói về lợi ích của ECS, bỏ qua nhận xét phía dưới bên trái):

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

Trường hợp giao diện chuyển động hoặc giao diện nút cảnh có thể được thực hiện bởi hàng trăm kiểu con: đèn, máy ảnh, mắt lưới, bộ giải vật lý, đổ bóng, kết cấu, xương, hình dạng nguyên thủy, đường cong, v.v. (và thường có nhiều loại ). Và vấn đề cuối cùng là những thiết kế đó không ổn định lắm. Chúng tôi đã thay đổi các yêu cầu và đôi khi chính các giao diện phải thay đổi và khi bạn muốn thay đổi một giao diện trừu tượng được thực hiện bởi 200 kiểu con, đó là một thay đổi cực kỳ tốn kém. Chúng tôi bắt đầu giảm thiểu rằng bằng cách sử dụng các lớp cơ sở trừu tượng ở giữa giúp giảm chi phí cho những thay đổi thiết kế như vậy, nhưng chúng vẫn còn đắt đỏ.

Vì vậy, thay vào đó tôi bắt đầu khám phá kiến ​​trúc hệ thống thành phần thực thể được sử dụng khá phổ biến trong ngành công nghiệp trò chơi. Điều đó đã thay đổi mọi thứ để được như thế này:

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

Và wow! Đó là một sự khác biệt về khả năng bảo trì. Các phụ thuộc không còn chảy theo trừu tượng , mà hướng tới dữ liệu (các thành phần). Và trong trường hợp của tôi, ít nhất, dữ liệu ổn định hơn và dễ dàng hơn về mặt thiết kế trả trước bất chấp các yêu cầu thay đổi (mặc dù những gì chúng ta có thể làm với cùng một dữ liệu luôn thay đổi theo yêu cầu thay đổi).

Ngoài ra, vì các thực thể trong ECS ​​sử dụng thành phần thay vì kế thừa, chúng thực sự không cần chứa chức năng. Chúng chỉ là "thùng chứa các thành phần" tương tự. Điều đó khiến cho 200 kiểu con tương tự triển khai giao diện chuyển động biến thành 200 trường hợp thực thể (không phải các loại riêng biệt với mã riêng) chỉ lưu trữ một thành phần chuyển động (không có gì ngoài dữ liệu liên quan đến chuyển động). A PointLightkhông còn là một lớp / kiểu con riêng biệt. Nó hoàn toàn không phải là một lớp học. Đây là một thực thể của một thực thể chỉ kết hợp một số thành phần (dữ liệu) liên quan đến vị trí của nó trong không gian (chuyển động) và các thuộc tính cụ thể của đèn điểm. Chức năng duy nhất liên quan đến chúng là bên trong các hệ thống, nhưRenderSystem, tìm kiếm các thành phần ánh sáng trong cảnh để xác định cách hiển thị cảnh.

Với các yêu cầu thay đổi theo phương pháp ECS, thường chỉ cần thay đổi một hoặc hai hệ thống hoạt động trên dữ liệu đó hoặc chỉ giới thiệu một hệ thống mới ở bên cạnh hoặc giới thiệu một thành phần mới nếu cần dữ liệu mới.

Vì vậy, đối với tên miền của tôi ít nhất, và tôi gần như chắc chắn nó không dành cho tất cả mọi người, điều này làm cho mọi thứ trở nên dễ dàng hơn rất nhiều vì sự phụ thuộc đang hướng đến sự ổn định (những thứ không cần phải thay đổi thường xuyên). Đó không phải là trường hợp trong kiến ​​trúc COM khi các phần phụ thuộc được truyền thống nhất theo hướng trừu tượng. Trong trường hợp của tôi, thật dễ dàng hơn để tìm ra dữ liệu nào là cần thiết cho chuyển động trước thay vì tất cả những điều có thể bạn có thể làm với nó, thường thay đổi một chút trong nhiều tháng hoặc nhiều năm khi có yêu cầu mới.

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

Có trường hợp nào trong OOP khi một số hoặc tất cả các nguyên tắc RẮN không cho vay để làm sạch mã không?

Chà, mã sạch Tôi không thể nói vì một số người đánh đồng mã sạch với RẮN, nhưng chắc chắn có một số trường hợp tách dữ liệu khỏi chức năng như ECS, và chuyển hướng phụ thuộc khỏi trừu tượng sang dữ liệu chắc chắn có thể giúp mọi việc dễ dàng hơn rất nhiều thay đổi, vì lý do khớp nối rõ ràng, nếu dữ liệu sẽ ổn định hơn rất nhiều so với trừu tượng. Tất nhiên việc phụ thuộc vào dữ liệu có thể khiến việc duy trì bất biến trở nên khó khăn, nhưng ECS ​​có xu hướng giảm thiểu đến mức tối thiểu với tổ chức hệ thống nhằm giảm thiểu số lượng hệ thống truy cập vào bất kỳ loại thành phần nào.

Không nhất thiết là các phụ thuộc phải chuyển sang trừu tượng như DIP sẽ đề xuất; phụ thuộc nên chảy vào những thứ rất khó có thể cần thay đổi trong tương lai. Điều đó có thể hoặc không thể trừu tượng trong mọi trường hợp (nó chắc chắn không phải là của tôi).

  1. Có, tồn tại các nguyên tắc thiết kế OOP xung đột một phần với RẮN
  2. Có, tồn tại các nguyên tắc thiết kế OOP hoàn toàn mâu thuẫn với RẮN.

Tôi không chắc chắn nếu ECS thực sự là một hương vị của OOP. Một số người định nghĩa nó theo cách đó, nhưng tôi thấy nó rất khác biệt với các đặc điểm ghép nối và tách dữ liệu (các thành phần) khỏi chức năng (hệ thống) và thiếu đóng gói dữ liệu. Nếu nó được coi là một dạng của OOP, tôi sẽ nghĩ rằng nó rất mâu thuẫn với RẮN (ít nhất là những ý tưởng khắt khe nhất về SRP, mở / đóng, thay thế liskov và DIP). Nhưng tôi hy vọng đây là một ví dụ hợp lý về một trường hợp và lĩnh vực trong đó các khía cạnh cơ bản nhất của RẮN, ít nhất là mọi người thường diễn giải chúng trong bối cảnh OOP dễ nhận biết hơn, có thể không được áp dụng như vậy.

Lớp học thiếu niên

Tôi đang giải thích kiến ​​trúc của một trong những trò chơi của tôi, trong sự ngạc nhiên của bạn tôi, có nhiều lớp nhỏ và một vài lớp trừu tượng. Tôi lập luận rằng đây là kết quả của việc tôi tập trung vào việc trao mọi thứ cho một Trách nhiệm duy nhất và cũng để nới lỏng sự ghép nối giữa các thành phần.

ECS đã thách thức và thay đổi quan điểm của tôi rất nhiều. Giống như bạn, tôi từng nghĩ rằng chính ý tưởng về khả năng duy trì là thực hiện đơn giản nhất cho những điều có thể, trong đó bao hàm nhiều điều, và hơn nữa, nhiều điều phụ thuộc lẫn nhau (ngay cả khi sự phụ thuộc lẫn nhau nằm giữa trừu tượng). Sẽ có ý nghĩa nhất nếu bạn phóng to chỉ một lớp hoặc hàm để muốn xem cách triển khai đơn giản, đơn giản nhất và nếu chúng ta không nhìn thấy một, hãy cấu trúc lại nó và thậm chí có thể phân tách nó hơn nữa. Nhưng kết quả có thể dễ dàng bỏ lỡ những gì đang xảy ra với thế giới bên ngoài, bởi vì bất cứ khi nào bạn chia bất cứ thứ gì tương đối phức tạp thành 2 hoặc nhiều thứ, thì 2 hoặc nhiều thứ đó chắc chắn phải tương tác * (xem bên dưới) với nhau trong một số cách, hoặc một cái gì đó bên ngoài phải tương tác với tất cả chúng.

Ngày nay tôi thấy có một hành động cân bằng giữa sự đơn giản của một cái gì đó và có bao nhiêu thứ và có bao nhiêu tương tác được yêu cầu. Các hệ thống trong ECS ​​có xu hướng khá nặng nề với các triển khai không tầm thường để vận hành trên dữ liệu, như PhysicsSystemhoặc RenderSystemhoặc GuiLayoutSystem. Tuy nhiên, thực tế là một sản phẩm phức tạp cần rất ít trong số chúng có xu hướng giúp dễ dàng lùi lại và suy luận về hành vi chung của toàn bộ cơ sở mã. Có một cái gì đó ở đó có thể gợi ý rằng nó có thể không phải là một ý tưởng tồi khi nghiêng về phía các lớp ít hơn, cồng kềnh hơn (vẫn thực hiện một trách nhiệm duy nhất có thể tranh cãi), nếu điều đó có nghĩa là ít lớp hơn để duy trì và lý do, và ít tương tác hơn trong suốt hệ thống.

Tương tác

Tôi nói "tương tác" chứ không phải "khớp nối" (mặc dù giảm tương tác ngụ ý giảm cả hai), vì bạn có thể sử dụng trừu tượng để tách rời hai đối tượng cụ thể, nhưng chúng vẫn nói chuyện với nhau. Họ vẫn có thể gây ra tác dụng phụ trong quá trình giao tiếp gián tiếp này. Và thường thì tôi thấy khả năng suy luận về tính đúng đắn của một hệ thống có liên quan đến những "tương tác" này hơn là "khớp nối". Giảm thiểu các tương tác có xu hướng làm cho mọi thứ dễ dàng hơn đối với tôi để suy luận về mọi thứ từ góc nhìn của một con chim. Điều đó có nghĩa là mọi thứ hoàn toàn không nói chuyện với nhau, và từ ý nghĩa đó, ECS cũng có xu hướng thực sự giảm thiểu "tương tác", và không chỉ khớp nối, đến mức tối thiểu (ít nhất là tôi trốn tránh '

Điều đó nói rằng, điều này có thể ít nhất một phần là tôi và điểm yếu cá nhân của tôi. Tôi đã tìm thấy trở ngại lớn nhất đối với tôi khi tạo ra các hệ thống có quy mô khổng lồ, và vẫn tự tin lý luận về chúng, điều hướng qua chúng và cảm thấy như tôi có thể thực hiện bất kỳ thay đổi mong muốn tiềm năng nào ở bất cứ đâu theo cách có thể dự đoán được, đó là quản lý tài nguyên và nhà nước cùng với tác dụng phụ. Đó là trở ngại lớn nhất bắt đầu nảy sinh khi tôi đi từ hàng chục ngàn LỘC đến hàng trăm ngàn LỘC đến hàng triệu LỘC, ngay cả đối với mã mà tôi tự tạo hoàn toàn. Nếu một cái gì đó sẽ làm tôi chậm lại để bò lên trên tất cả những thứ khác, thì có nghĩa là tôi không còn có thể hiểu những gì đang xảy ra về trạng thái ứng dụng, dữ liệu, tác dụng phụ. Nó ' Đây không phải là thời gian robot cần thiết để tạo ra sự thay đổi khiến tôi chậm lại nhiều đến mức không thể hiểu được toàn bộ tác động của thay đổi nếu hệ thống phát triển vượt quá khả năng suy nghĩ của tôi về nó. Và đối với tôi, việc giảm các tương tác là cách hiệu quả nhất để cho phép sản phẩm phát triển lớn hơn nhiều với nhiều tính năng hơn mà cá nhân tôi không bị choáng ngợp bởi những điều này, vì giảm tương tác xuống mức tối thiểu cũng làm giảm số lượng địa điểm có thể thậm chí có thể thay đổi trạng thái ứng dụng và gây ra tác dụng phụ đáng kể.

Nó có thể biến một thứ như thế này (trong đó mọi thứ trong sơ đồ đều có chức năng, và rõ ràng một kịch bản trong thế giới thực sẽ có số lượng đối tượng nhiều, gấp nhiều lần và đây là sơ đồ "tương tác", chứ không phải khớp nối, như một khớp nối người ta sẽ có sự trừu tượng ở giữa):

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

... đến đây, nơi chỉ có các hệ thống có chức năng (các thành phần màu xanh bây giờ chỉ là dữ liệu và bây giờ đây là sơ đồ khớp nối):

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

Và có những suy nghĩ xuất hiện trên tất cả những điều này và có thể là một cách để đóng khung một số lợi ích này trong bối cảnh OOP phù hợp hơn, tương thích hơn với RẮN, nhưng tôi vẫn chưa tìm thấy các thiết kế và từ ngữ, và tôi thấy nó khó khăn vì thuật ngữ này tôi đã quen với tất cả những gì liên quan trực tiếp đến OOP. Tôi tiếp tục cố gắng tìm ra cách đọc qua câu trả lời của mọi người ở đây và cũng cố gắng hết sức để tự mình xây dựng, nhưng có một số điều rất thú vị về bản chất của một ECS mà tôi không thể đặt ngón tay của mình lên đó có thể được áp dụng rộng rãi hơn ngay cả đối với các kiến ​​trúc không sử dụng nó. Tôi cũng hy vọng câu trả lời này không xuất hiện dưới dạng khuyến mãi của ECS! Tôi chỉ thấy nó rất thú vị vì thiết kế một ECS đã thực sự thay đổi suy nghĩ của tôi,


Tôi thích sự phân biệt giữa tương tác và khớp nối. Mặc dù sơ đồ tương tác đầu tiên của bạn bị xáo trộn. Đó là một đồ thị phẳng, vì vậy không cần phải vượt qua mũi tên.
D Drmmr

2

Nguyên tắc RẮN là tốt, nhưng KISS và YAGNI ưu tiên trong trường hợp có xung đột. Mục đích của RẮN là quản lý sự phức tạp, nhưng nếu áp dụng chính RẮN làm cho mã phức tạp hơn, nó sẽ đánh bại mục đích. Lấy một ví dụ, nếu bạn có chương trình nhỏ chỉ có vài lớp, áp dụng DI, phân tách giao diện, v.v ... rất có thể làm tăng độ phức tạp chung của chương trình.

Nguyên tắc mở / đóng đặc biệt gây tranh cãi. Về cơ bản, nó nói rằng bạn nên thêm các tính năng cho một lớp bằng cách tạo một lớp con hoặc triển khai riêng biệt của cùng một giao diện, nhưng không để lại lớp gốc. Nếu bạn đang duy trì một thư viện hoặc dịch vụ được sử dụng bởi các khách hàng không phối hợp, điều này có ý nghĩa, vì bạn không muốn phá vỡ hành vi mà khách hàng thoát ra có thể dựa vào. Tuy nhiên, nếu bạn đang sửa đổi một lớp chỉ được sử dụng trong ứng dụng của riêng bạn, thì việc sửa đổi lớp sẽ đơn giản và đơn giản hơn nhiều.


Thảo luận về OCP của bạn bỏ qua khả năng mở rộng hành vi của một lớp thông qua thành phần (ví dụ: sử dụng đối tượng chiến lược để cho phép thay đổi thuật toán được sử dụng bởi lớp), thường được coi là cách tốt hơn để tuân thủ hiệu trưởng so với phân lớp .
Jules

1
Nếu KISS và YAGNI luôn được ưu tiên, bạn vẫn nên mã hóa trong 1 và 0. Nhà lắp ráp chỉ là một cái gì đó có thể đi sai. Không có KISS và YAGNI chỉ ra hai trong số nhiều chi phí cho công việc thiết kế. Chi phí đó phải luôn được cân đối với lợi ích của bất kỳ công việc thiết kế nào. RẮN có lợi ích mà trong một số trường hợp bù cho chi phí. Không có cách nào đơn giản để biết khi bạn đã vượt qua ranh giới.
candied_orange

@CandiedOrange: Tôi không đồng ý rằng mã máy đơn giản hơn mã trong ngôn ngữ cấp cao. Mã máy kết thúc chứa rất nhiều sự phức tạp ngẫu nhiên do thiếu tính trừu tượng, do đó, chắc chắn không phải là KISS để quyết định viết một ứng dụng điển hình trong mã máy.
JacquesB

@Jules: Có thành phần thích hợp hơn, nhưng nó vẫn yêu cầu bạn thiết kế lớp gốc theo cách mà thành phần có thể được sử dụng để tùy chỉnh nó, do đó bạn phải đưa ra các giả định về những khía cạnh nào cần được tùy chỉnh trong tương lai và những khía cạnh nào không thể tùy chỉnh. Trong một số trường hợp, điều này là cần thiết (ví dụ: bạn đang viết thư viện để phân phối) nhưng nó có chi phí, do đó, việc tuân theo RẮN không phải lúc nào cũng tối ưu.
JacquesB

1
@JacquesB - đúng. Và OCP là cốt lõi của nó đối với YAGNI; tuy nhiên, về việc đạt được nó, nó đòi hỏi bạn phải thiết kế các điểm mở rộng để cho phép cải tiến sau này. Tìm một điểm cân bằng phù hợp giữa hai lý tưởng là ... khó khăn.
Jules

1

Theo kinh nghiệm của tôi:-

Các lớp lớn khó theo cấp số nhân hơn khi chúng lớn hơn.

Các lớp nhỏ dễ bảo trì hơn và khó khăn trong việc duy trì một tập hợp các lớp nhỏ tăng lên một cách hợp lý theo số lượng các lớp.

Đôi khi các lớp lớn không thể tránh khỏi một vấn đề lớn đôi khi cần một lớp lớn, nhưng, chúng khó đọc và khó hiểu hơn nhiều. Đối với các lớp nhỏ hơn được đặt tên tốt, chỉ cần tên có thể đủ để hiểu bạn thậm chí không cần xem mã trừ khi có một vấn đề rất cụ thể với lớp.


0

Tôi luôn cảm thấy khó khăn khi vẽ ranh giới giữa 'S' của vật rắn và (có thể là lỗi thời) để cho một đối tượng có tất cả kiến ​​thức về chính nó.

15 năm trước tôi đã làm việc với các nhà phát triển Deplhi, người có chén thánh của họ để có các lớp chứa mọi thứ. Vì vậy, đối với một khoản thế chấp, bạn có thể thêm bảo hiểm và thanh toán và thu nhập cũng như các khoản vay và thông tin về thuế và bạn có thể tính thuế để trả, thuế để khấu trừ và nó có thể tự tuần tự hóa, tồn tại vào đĩa, v.v.

Và bây giờ tôi có cùng một đối tượng được chia thành rất nhiều lớp nhỏ hơn, có lợi thế là chúng có thể được kiểm tra đơn vị dễ dàng và tốt hơn nhiều nhưng không đưa ra một cái nhìn tổng quan tốt về toàn bộ mô hình kinh doanh.

Và vâng tôi biết bạn không muốn ràng buộc các đối tượng của mình vào cơ sở dữ liệu nhưng bạn có thể tiêm một kho lưu trữ trong lớp thế chấp lớn để giải quyết điều đó.

Bên cạnh đó, không phải lúc nào cũng dễ dàng tìm thấy tên hay cho các lớp chỉ làm một việc nhỏ.

Vì vậy, tôi không chắc chắn về việc 'S' luôn là một ý tưởng hay.

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.