Làm thế nào cụ thể nên mẫu Trách nhiệm duy nhất cho các lớp?


14

Ví dụ: giả sử bạn có một chương trình trò chơi điều khiển, có tất cả các loại phương thức nhập / xuất đến và từ bảng điều khiển. Nó sẽ được thông minh để giữ cho chúng tất cả trong một đơn inputOutputlớp hoặc phá vỡ chúng xuống đến các lớp học cụ thể hơn như startMenuIO, inGameIO, playerIO, gameBoardIOvv như vậy mà mỗi lớp có khoảng 1-5 phương pháp?

Và trên cùng một lưu ý, nếu tốt hơn là phá vỡ chúng, sẽ tốt hơn nếu đặt chúng trong một IOkhông gian tên, do đó, gọi chúng dài hơn một chút, ví dụ: IO.inGamevv?



Liên quan: Tôi chưa bao giờ thấy một lý do tốt để kết hợp đầu vào và đầu ra. Và khi tôi cố tình tách chúng ra, mã của tôi trở nên sạch hơn rất nhiều.
Vịt Mooing

1
Trong ngôn ngữ nào bạn đặt tên các lớp trong lowCamelCase, dù sao?
dùng253751

Câu trả lời:


7

Cập nhật (tóm tắt)

Vì tôi đã viết một câu trả lời khá dài dòng, đây là tất cả những gì nó rút ra:

  • Không gian tên là tốt, sử dụng chúng bất cứ khi nào nó có ý nghĩa
  • Việc sử dụng inGameIOplayerIOcác lớp có thể sẽ cấu thành vi phạm SRP. Điều đó có nghĩa là bạn đang ghép nối cách bạn xử lý IO với logic ứng dụng.
  • Có một vài lớp IO chung, được sử dụng (hoặc đôi khi được chia sẻ) bởi các lớp xử lý. Các lớp xử lý này sau đó sẽ dịch đầu vào thô thành định dạng mà logic ứng dụng của bạn có thể hiểu được.
  • Tương tự với đầu ra: điều này có thể được thực hiện bởi các lớp khá chung chung, nhưng chuyển trạng thái trò chơi thông qua một đối tượng xử lý / ánh xạ dịch trạng thái trò chơi nội bộ thành thứ mà các lớp IO chung có thể xử lý.

Tôi nghĩ rằng bạn đang nhìn điều này sai cách. Bạn đang tách IO theo chức năng của các thành phần của ứng dụng, trong khi đó - với tôi - sẽ có ý nghĩa hơn khi có các lớp IO riêng biệt dựa trên nguồn và "loại" IO.

Có một số KeyboardIOlớp cơ sở / chung chung MouseIOđể bắt đầu, và sau đó dựa trên thời điểm và nơi bạn cần chúng, có các lớp con xử lý IO khác nhau.
Ví dụ: nhập văn bản là thứ bạn có thể muốn xử lý khác với điều khiển trong trò chơi. Bạn sẽ thấy mình muốn ánh xạ các khóa nhất định khác nhau tùy theo từng trường hợp sử dụng, nhưng ánh xạ đó không phải là một phần của chính IO, đó là cách bạn xử lý IO.

Bám sát SRP, tôi có một vài lớp mà tôi có thể sử dụng cho IO bàn phím. Tùy thuộc vào tình huống, có lẽ tôi sẽ muốn tương tác với các lớp này một cách khác nhau, nhưng công việc duy nhất của họ là cho tôi biết người dùng đang làm gì.

Sau đó, tôi sẽ đưa các đối tượng này vào một đối tượng xử lý để ánh xạ IO thô vào thứ gì đó mà logic ứng dụng của tôi có thể hoạt động (ví dụ: người dùng nhấn "w" , trình xử lý ánh xạ lên MOVE_FORWARD).

Những trình xử lý này lần lượt được sử dụng để làm cho các nhân vật di chuyển và vẽ màn hình tương ứng. Một sự đơn giản hóa quá mức, nhưng ý chính của nó là loại cấu trúc này:

[ IO.Keyboard.InGame ] // generic, if SoC and SRP are strongly adhered to, changing this component should be fairly easy to do
   ||
   ==> [ Controls.Keyboard.InGameMapper ]

[ Game.Engine ] <- Controls.Keyboard.InGameMapper
                <- IO.Screen
                <- ... all sorts of stuff here
    InGameMapper.move() //returns MOVE_FORWARD or something
      ||
      ==> 1. Game.updateStuff();//do all the things you need to do to move the character in the given direction
          2. Game.Screen.SetState(GameState); //translate the game state (inverse handler)
          3. IO.Screen.draw();//generate actual output

Những gì chúng ta có bây giờ là một lớp chịu trách nhiệm cho IO bàn phím ở dạng thô. Một lớp khác dịch dữ liệu này thành thứ gì đó mà công cụ trò chơi thực sự có thể hiểu được, dữ liệu này sau đó được sử dụng để cập nhật trạng thái của tất cả các thành phần liên quan, và cuối cùng, một lớp riêng sẽ đảm nhiệm việc xuất ra màn hình.

Mỗi một lớp có một công việc duy nhất: xử lý đầu vào bàn phím được thực hiện bởi một lớp không biết / quan tâm / phải biết đầu vào mà nó xử lý nghĩa là gì. Tất cả những gì nó làm là biết làm thế nào để có được đầu vào (được đệm, không có bộ đệm, ...).

Trình xử lý dịch điều này thành một đại diện nội bộ cho phần còn lại của ứng dụng để hiểu ý nghĩa của thông tin này.

Công cụ trò chơi lấy dữ liệu đã được dịch và sử dụng nó để thông báo cho tất cả các thành phần có liên quan rằng có gì đó đang diễn ra. Mỗi thành phần này chỉ làm một việc, cho dù đó là kiểm tra va chạm hay thay đổi hoạt hình nhân vật, điều đó không thành vấn đề, điều đó tùy thuộc vào từng đối tượng riêng lẻ.

Các đối tượng này sau đó chuyển tiếp trạng thái của chúng trở lại và dữ liệu này được truyền tới Game.Screen, về bản chất là một trình xử lý IO nghịch đảo. Nó ánh xạ biểu diễn bên trong vào một cái gì đó mà IO.Screenthành phần có thể sử dụng để tạo đầu ra thực tế.


Vì đây là một ứng dụng giao diện điều khiển không có chuột và việc in các tin nhắn hoặc bảng được liên kết chặt chẽ với đầu vào. Trong ví dụ của bạn, các không gian IOgametên hoặc các lớp có các lớp con?
shinzou

@kuhaku: Chúng là không gian tên. Ý chính của những gì tôi đang nói là, nếu bạn chọn tạo các lớp con dựa trên phần nào của ứng dụng, bạn kết hợp chặt chẽ chức năng IO cơ bản với logic ứng dụng của bạn. Bạn sẽ kết thúc với các lớp chịu trách nhiệm về IO trong chức năng của ứng dụng . Điều đó, với tôi, nghe có vẻ như vi phạm SRP. Đối với các tên so với các lớp được đặt tên: Tôi có xu hướng thích các không gian tên 95% thời gian
Elias Van Ootegem

Tôi đã cập nhật câu trả lời của mình, để tóm tắt câu trả lời của tôi
Elias Van Ootegem

Vâng, đó thực sự là một vấn đề khác mà tôi gặp phải (ghép IO với logic), vì vậy bạn thực sự khuyên bạn nên tách đầu vào khỏi đầu ra?
shinzou

@kuhaku: Điều đó thực sự phụ thuộc vào những gì bạn đang làm và mức độ phức tạp của công cụ đầu vào / đầu ra. Nếu các trình xử lý (dịch trạng thái trò chơi so với đầu vào / đầu ra thô) khác nhau quá nhiều, thì có lẽ bạn sẽ muốn tách các lớp đầu vào và đầu ra, nếu không, một lớp IO duy nhất vẫn ổn
Elias Van Ootegem

23

Nguyên tắc trách nhiệm duy nhất có thể là khó khăn để hiểu. Những gì tôi thấy hữu ích là nghĩ về nó như cách bạn viết câu. Bạn đừng cố nhồi nhét nhiều ý tưởng vào một câu. Mỗi câu nên nêu một ý tưởng rõ ràng và trì hoãn các chi tiết. Ví dụ: nếu bạn muốn xác định một chiếc xe hơi, bạn sẽ nói:

Một phương tiện giao thông đường bộ, thường có bốn bánh, chạy bằng động cơ đốt trong.

Sau đó, bạn sẽ định nghĩa những thứ như "phương tiện", "đường", "bánh xe", v.v. Bạn sẽ không cố nói:

Một phương tiện để vận chuyển người trên một con đường, tuyến đường hoặc đường bộ trên đất liền giữa hai nơi đã được lát hoặc cải thiện để cho phép đi lại có bốn vật thể tròn xoay trên một trục cố định bên dưới xe và được cung cấp bởi một động cơ tạo ra động lực bằng cách đốt xăng, dầu hoặc nhiên liệu khác bằng không khí.

Tương tự như vậy, bạn nên cố gắng tạo các lớp, phương thức, v.v., nêu khái niệm trung tâm càng đơn giản càng tốt và trì hoãn các chi tiết cho các phương thức và lớp khác. Cũng giống như với việc viết câu, không có quy tắc cứng nào về việc chúng nên lớn như thế nào.


Vì vậy, giống như với việc xác định chiếc xe với một vài từ thay vì nhiều, sau đó xác định các lớp nhỏ cụ thể nhưng tương tự là tốt?
shinzou

2
@kuhaku: Nhỏ là tốt. Cụ thể là tốt. Tương tự là tốt miễn là bạn giữ nó KHÔ. Bạn đang cố gắng để làm cho thiết kế của bạn dễ dàng thay đổi. Giữ mọi thứ nhỏ và cụ thể giúp bạn biết nơi để thực hiện thay đổi. Giữ nó DRY giúp tránh phải thay đổi nhiều nơi.
Vaughn Cato

6
Câu thứ hai của bạn trông giống như "Chết tiệt, giáo viên nói đây cần 4 trang ... 'tạo ra động lực' đó là !!"
corsiKa

2

Tôi muốn nói rằng cách tốt nhất để đi là giữ chúng trong các lớp riêng biệt. Các lớp học nhỏ không phải là xấu, trên thực tế hầu hết thời gian chúng là một ý tưởng tốt.

Về trường hợp cụ thể của bạn, tôi nghĩ rằng việc tách riêng có thể giúp bạn thay đổi logic của bất kỳ trình xử lý cụ thể nào mà không ảnh hưởng đến các trình xử lý khác và nếu cần, bạn sẽ dễ dàng thêm phương thức nhập / xuất mới nếu đến với nó.


0

Hiệu trưởng trách nhiệm duy nhất tuyên bố rằng một lớp chỉ nên có một lý do để thay đổi.Nếu lớp của bạn có nhiều lý do để thay đổi, thì bạn có thể chia nó thành các lớp khác và sử dụng thành phần để loại bỏ vấn đề này.

Để trả lời câu hỏi của bạn, tôi phải hỏi bạn một câu hỏi: Lớp học của bạn chỉ có một lý do để thay đổi? Nếu không, thì đừng ngại tiếp tục thêm các lớp chuyên biệt cho đến khi mỗi lớp chỉ có một lý do để thay đổ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.