Duy trì sự tách biệt các mối quan tâm


8

Tôi đang tạo ứng dụng C # đầu tiên của mình và tôi gặp một chút khó khăn khi tách các mối quan tâm. Tôi hiểu khái niệm này, nhưng tôi không biết liệu tôi có làm đúng hay không. Tôi có một ví dụ nhanh để minh họa cho câu hỏi của tôi. Trong một ứng dụng như trò chơi, có một lớp chính chạy vòng lặp chính, chẳng hạn như Chương trình hoặc Trò chơi. Câu hỏi của tôi là, tôi có duy trì mọi tham chiếu đến mọi phiên bản của một lớp trong lớp này không và biến nó thành cách duy nhất để chúng tương tác?

Ví dụ: trò chơi bài với người chơi, thẻ và bảng. Giả sử người chơi muốn đặt thẻ lên bảng, nhưng lớp Người chơi chỉ có Danh sách <> Thẻ và không biết gì về bảng trò chơi. Tuy nhiên, lớp Game chính biết về người chơi, thẻ bài và bảng trò chơi. Nếu nó thuộc vào lớp Trò chơi để đặt các thẻ lên bảng hoặc điều đó có ý nghĩa hơn, bởi vì đó là hành động của người chơi, nên nó nằm trong lớp Người chơi.

Thí dụ:

public class Game{
    private GameBoard gameBoard;
    private Player[] players;

   public Game(){
     gameBoard = new GameBoard(10,10);
     Player player1 = new Player();
     Player player2 = new Player();
     players = {player1, player2};
   }

   // Create method here?
   public void PlayerPlaceCard(int x, int y, int cardIndex){
      gameBoard.grid[1,1] = player1.cards[cardIndex];
   }
}

public class Player {
     public List<Cards> cards = new List<Cards>();

     public Player(){
     }

     // Or place method here?
     public PlaceCard(Card card, int x, int y, GameBoard gameBoard){
     }
}

public class GameBoard{
    public Card[,] grid;

    public GameBoard(int x, int y){
       // Make the game board
    }
}

public class Card{
   public string name;
   public string value;
}

Đối với tôi nó có ý nghĩa hơn khi có phương pháp trong Game, bởi vì Game biết về mọi thứ. Nhưng khi tôi thêm nhiều mã hơn, Game đang trở nên khá cồng kềnh và tôi đang viết rất nhiều hàm PlayerDoes This ().

Cảm ơn vì lời khuyên

Câu trả lời:


12

Chìa khóa ở đây không chỉ là sự tách biệt các mối quan tâm , mà còn là nguyên tắc trách nhiệm duy nhất . Hai mặt cơ bản là hai mặt khác nhau của cùng một đồng tiền: khi tôi nghĩ SOC tôi nghĩ từ trên xuống (tôi có những mối quan tâm này, làm cách nào để tách chúng ra?) Trong khi SRP có nhiều từ dưới lên (tôi có đối tượng này, nó có mối quan tâm duy nhất? Có nên chia tách? Mối quan tâm của nó đã bị chia tách quá nhiều chưa?).

Trong ví dụ của bạn, bạn có các thực thể sau và trách nhiệm của chúng:

  • Trò chơi: đây là mã làm cho chương trình "đi".
  • GameBoard: duy trì trạng thái của khu vực chơi.
  • Thẻ: một thực thể duy nhất trên bảng trò chơi.
  • Người chơi: thực hiện các hành động thay đổi trạng thái của bảng trò chơi.

Khi bạn nghĩ về trách nhiệm duy nhất của mỗi thực thể, các dòng trở nên rõ ràng hơn.

Trong một ứng dụng như trò chơi, có một lớp chính chạy vòng lặp chính, chẳng hạn như Chương trình hoặc Trò chơi. Câu hỏi của tôi là, tôi có duy trì mọi tham chiếu đến mọi phiên bản của một lớp trong lớp này không và biến nó thành cách duy nhất để chúng tương tác?

Thực sự có hai vấn đề ở đây cần ghi nhớ. Điều đầu tiên để quyết định là những gì thực thể biết về các thực thể khác? Những thực thể thuộc về các thực thể khác?

Nhìn vào những trách nhiệm tôi đã nêu ở trên. Người chơi thực hiện các hành động thay đổi trạng thái của bảng trò chơi. Nói cách khác, Người chơi gửi tin nhắn đến (gọi phương thức) trên bảng trò chơi. Những tin nhắn đó có khả năng liên quan đến thẻ: ví dụ: người chơi có thể đặt thẻ trên tay lên bảng hoặc thay đổi trạng thái của thẻ hiện tại (ví dụ: lật thẻ hoặc chuyển thẻ sang vị trí mới).

Rõ ràng, một người chơi phải biết về bảng trò chơi mâu thuẫn với giả định bạn đưa ra trong câu hỏi của mình. Nếu không, người chơi phải gửi tin nhắn đến trò chơi, sau đó chuyển tiếp tin nhắn đó đến bảng trò chơi. Vì người chơi thực hiện các hành động trên bảng trò chơi, người chơi phải biết về bảng trò chơi. Điều này làm tăng tính khớp nối: thay vì người chơi gửi tin nhắn trực tiếp, bây giờ hai diễn viên phải biết cách gửi tin nhắn đó. Các Luật của Demeter ngụ ý rằng nếu một đối tượng phải hành động trên đối tượng khác, trong trường hợp này, đối tượng khác cần được thông qua vào thông qua tham số để giảm khớp nối.

Tiếp theo, bạn lưu trữ nhà nước ở đâu? Trò chơi là trình điều khiển ở đây, nó phải tạo ra tất cả các đối tượng trực tiếp hoặc thông qua proxy (ví dụ: một nhà máy hoặc trong một nhà xây dựng mà trò chơi gọi). Câu hỏi tiếp theo hợp lý là những đối tượng cần những đối tượng khác? Về cơ bản, đây là những gì tôi đã hỏi ở trên, nhưng một cách khác để hỏi nó.

Cách tôi sẽ thiết kế nó là như thế này:

  • Trò chơi tạo ra tất cả các đối tượng cần thiết cho trò chơi.

  • Trò chơi xáo trộn các thẻ và chia chúng cho mỗi trò chơi mà nó đại diện (poker, solitaire, v.v.).

  • Trò chơi đặt các thẻ vào vị trí ban đầu của chúng: có thể một số trên bảng trò chơi, một số khác trong tay người chơi.

  • Trò chơi sau đó đi vào vòng lặp chính của nó đại diện cho một lượt.

Mỗi lượt sẽ như thế này:

  • Trò chơi gửi một thông điệp tới (gọi một phương thức trên) trình phát hiện tại và cung cấp một tham chiếu đến bảng trò chơi.

  • Trình phát thực hiện bất kỳ logic bên trong (trình phát máy tính) hoặc tương tác người dùng cần thiết để xác định những gì chơi để thực hiện.

  • Người chơi gửi tin nhắn đến bảng trò chơi được cung cấp cho nó để yêu cầu thay đổi trạng thái của bảng trò chơi.

  • Ban trò chơi quyết định xem di chuyển có hợp lệ hay không (có trách nhiệm duy trì trạng thái trò chơi hợp lệ).

  • Kiểm soát trở lại trò chơi, sau đó quyết định làm gì tiếp theo. Kiểm tra điều kiện thắng? Vẽ tranh? Người chơi tiếp theo? Lượt tiếp theo? Phụ thuộc vào trò chơi thẻ cụ thể đang được chơi.

Nếu nó thuộc vào lớp Trò chơi để đặt các thẻ lên bảng hoặc điều đó có ý nghĩa hơn, bởi vì đó là hành động của người chơi, nên nó nằm trong lớp Người chơi.

Cả hai: Game chịu trách nhiệm thiết lập ban đầu, nhưng Người chơi thực hiện các hành động trên bảng. GameBoard chịu trách nhiệm đảm bảo trạng thái hợp lệ. Ví dụ, trong Solitaire cổ điển, chỉ có thẻ trên cùng trên một cọc có thể ngửa lên.


Quay lại quan điểm ban đầu của tôi: bạn có những mối quan tâm đúng đắn. Bạn đã xác định các đối tượng thích hợp. Điều khiến bạn vấp ngã là tìm ra cách các thông điệp truyền qua hệ thống và đối tượng nào sẽ giữ các tham chiếu đến các đối tượng khác. Tôi sẽ thiết kế nó như thế này, đó là mã giả:

class Game {
  main();
}

class GameBoard {
  // Data structures specific to the game being played. There is a
  // lot of hand-waving here to give the general idea without
  // getting bogged down in the implementation.
  Map<Card, Location> cards;

  GameBoard(Map<Card, Location>);

  // Return false if the move is invalid.
  bool flip(Card);
  bool move(Card, Location);
}

class Card {
  // Make Rank and Suit enums.
  Suit suit;
  Rank rank;
  bool faceUp;
}

class Player {
  Set<Card> hand;

  Player(Set<Card>);
  void takeTurn(GameBoard);
}

1
Câu trả lời tốt, chỉ thiếu một điều tôi cảm thấy. Trong ví dụ của OP, anh ấy chuyển một x, y đến bảng trò chơi để cho nó biết chính xác nơi đặt thẻ. Trong khi người chơi sẽ gọi bảng trò chơi để đặt một thẻ, thì đó phải là bảng trò chơi quyết định x, y. Yêu cầu người chơi biết về tọa độ bảng tạo ra sự trừu tượng bị rò rỉ.
David Arno

@DavidArno nếu các thẻ có thể được phát tại các vị trí trong một lưới hình chữ nhật, người chơi nên chỉ ra tại vị trí nào để chơi chúng, nếu không phải bằng tọa độ? (Đó không phải là tọa độ màn hình, mà là tọa độ lưới.)
Paŭlo Ebermann

1
Các chi tiết cụ thể về cách đặt thẻ nên được trừu tượng hóa bằng cách nào đó bởi một lớp Location, đây sẽ là một phần rất nhỏ trong thiết kế này. Chắc chắn, tọa độ có thể làm việc. Nó cũng có thể thích hợp hơn để sử dụng các vị trí được đặt tên như "loại bỏ đống". Các chi tiết thực hiện là không quan trọng khi vạch ra thiết kế như đã nêu trong câu hỏi.

@Snowman Cảm ơn, câu trả lời này là tại chỗ. Nếu chúng ta có Người chơi luôn hành động trên GameBoard, thì sẽ có ý nghĩa hơn khi có một tham chiếu cục bộ trong lớp, điều đó sẽ được đặt trong trình xây dựng của nó? Bằng cách đó, GameBoard sẽ không phải được chuyển đến Người chơi mỗi lần (sẽ có rất nhiều tương tác với bảng).
tông31

@DavidArno Người chơi thực sự cần nói với bảng trò chơi nơi đặt nó, bảng trò chơi cần xác thực nó. Người chơi có thể nhặt và di chuyển thẻ.
tông31
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.