2015-04-27 36 views
6

Tôi đã gặp một vấn đề trong trò chơi mà tôi đang thực hiện trong C#. Đó là một trò chơi kết hợp dựa trên gạch đơn giản và vấn đề nảy sinh khi tôi cố gắng tạo ra:Sử dụng GetType/instanceof trong C# so với các lựa chọn thay thế

Giả sử chúng ta có các loại gạch cơ bản, hình vuông và hình thoi, tất cả đều là lớp con của Ngói. Thay vì có vòng tròn chỉ phù hợp với vòng kết nối, tôi đã cố gắng trích xuất hành vi "đối sánh" thành phương thức Gạch trừu tượng: canMatchWith (Ngói t). Gạch cũng có hai phương pháp để thêm/loại bỏ Gạch mà chúng có thể khớp với.

Vì vậy, hãy nói rằng chúng tôi có một hình tròn ở giữa trò chơi của chúng tôi và chúng tôi có một powerup có nội dung "Gạch tròn có thể khớp với ô vuông lần lượt này". Tôi sẽ đi qua tất cả các gạch Circle và nói circleTile.addCanMatchWith (typeof (Square)). Bên trong, chúng ta có một List canMatchWith.

Sau đó, tôi muốn nói "Vòng kết nối không còn có thể khớp với hình vuông" và chỉ cần nói circleTile.removeCanMatchWith (typeOf (Square)).

Đây là giải pháp hiện tại của tôi và nó hoạt động tuyệt vời không có hiệu suất mà tôi đã nhận thấy (Đó là trò chơi xếp hình dựa trên gạch, vì vậy các loại này chỉ được đánh giá một lần cho mỗi lần di chuyển, chứ không phải khung theo khung). Tuy nhiên, giọng nói trong đầu tôi đang nói với tôi rằng đây là một cách tồi tệ để hoàn thành hành vi này. Vì vậy, tôi có một số lựa chọn thay thế:

  1. Enums ... Mỗi ô có thể được tạo với một biến kiểu Tiletype. Điều này sẽ được khởi tạo trong hàm tạo và đặt thành Type.SQUARE cho các ô vuông, v.v. Sau đó, mỗi Tile sẽ có một List canMatchWith, và chức năng giống như việc thực hiện ban đầu của tôi. Ngoại trừ trong trường hợp này, nó phức tạp hơn một chút. Nói rằng tôi có một số lớp con vòng tròn, hình bầu dục và hình elip. Tôi muốn hình bầu dục để có thể phù hợp với hình vuông chỉ, nhưng elipses có thể phù hợp với tất cả các vòng tròn và không vuông.

Vấn đề ở đây là dự phòng, enum của tôi bây giờ sẽ có OVAL và ELIPSE, và lớp Elipse sẽ có (CIRCLE, OVAL, ELIPSE TileTypes) là loại có thể khớp với. Điều này là hoàn toàn dư thừa, tôi muốn chỉ nói "Circle" mà tôi có thể với các loại. Tôi cho rằng các Brick có thể có TileType baseType và TileType actualType.

  1. Một số dạng thành phần hành vi. Hãy quên đi các lớp con Tile, chỉ cần đưa ra các phương thức Tiles và một biến cá thể cho List. Sau đó, tại thời gian chạy, chúng tôi chỉ có thể nói someTile.addCanMatch (mới CircleMatchBehavior()). Điều này có vẻ ngớ ngẩn, như tôi sẽ có một loạt các lớp học chỉ nói rằng bạn có thể phù hợp với một hình dạng cụ thể.

Tóm lại, những gì tôi đang cố gắng thực hiện là có nhiều loại đối tượng có thể tương tác với bất kỳ số loại nào khác nhau. Câu hỏi đặt ra là, tôi nên sử dụng cái gì cho Loại. Có sử dụng GetType ở đây không? Enums? Hoặc là có một chiến lược tốt hơn ai đó sẽ giới thiệu? Tôi đang cố gắng hết sức có thể, những viên gạch này không nên có bất kỳ phụ thuộc mã hóa nào trên các loại gạch khác, và phải có khả năng thay đổi những người mà chúng có thể tương tác khi đang bay. Giả sử tôi tạo một lớp con Tile mới, ngũ giác ... tốt, Pentagons có thể phù hợp với Squares, Circles và Pentagons. Dễ dàng với việc thực hiện của tôi, nhưng một cái gì đó nói với tôi đây là một thực hành OOP bẩn.

Tôi cảm thấy mình phải sử dụng các loại/Enums vì tôi không cố gắng nói thisTile.addCanMatch (Tile someOtherObject). Đó là quá cụ thể, tôi muốn thisTile để có thể phù hợp với tất cả các gạch là trường hợp của một lớp học cụ thể.

+0

Tôi sợ rằng tôi không phải là một nhà thiết kế có tay nghề đủ để đề xuất một thiết kế hoàn chỉnh cho bạn, nhưng nếu không có thực tế * khác biệt * chức năng trong cách Circles và Các hoạt động của Ovals có thể tốt hơn nếu có tất cả các cá thể là lớp 'Tile' và sau đó thiết lập một số thuộc tính' Behavior'. Tất cả các vòng tròn trên bảng có thể chia sẻ cùng một đối tượng 'Hành vi', và sau đó khi các quy tắc thay đổi cho một lượt, bạn có thể đặt các thay đổi trên đối tượng' Hành vi' đó. Điều này có thể dẫn đến sao chép mã ít hơn, nếu tôi hiểu được tình huống. – Katana314

+0

Nếu tôi hiểu chính xác bạn, điều này giống như Thành phần hành vi # 2 của tôi. Nỗi sợ hãi của tôi ở đây là hành vi nổ. Ví dụ, MatchesWithCirclesAndSquaresBehavior. Xem những gì tôi đang nhận được? Nếu có 5 loại gạch, thì chúng ta có 5 chọn 1 cộng 5 chọn 2 cộng 5 chọn 3 cộng 5 chọn 4 cộng 5 chọn 5 tổ hợp hành vi. Điều này có vẻ còn tồi tệ hơn GetType. Tuy nhiên, tôi có thể hiểu nhầm điều gì đó trong đề xuất của bạn. Các gạch tự làm bất cứ điều gì họ muốn, người ta có thể làm một hình ảnh động vụ nổ, vv Điều này chỉ cho nếu gạch có thể phù hợp với nhau. – user2045279

+0

Tôi đã không đề xuất Hành vi là một Enum hoặc một lớp không thể sửa đổi. Bạn có thể phân lớp nó theo cách, nhưng chủ yếu tôi chỉ mong đợi nó có một bộ sưu tập nội bộ đại diện cho loại đó là (Circle) và loại (s) mà nó có thể phù hợp với (Square). Sau đó, bạn có thể thay đổi điều đó theo cách có phần dữ liệu. Tôi chắc chắn đồng ý rằng bất cứ điều gì sẽ dẫn đến khối lượng lớn nếu/else/switch nên tránh để ủng hộ việc thiết lập đối tượng tốt, nhưng chúng tôi cũng muốn tránh viết một lớp mới để giải thích một số biến thể không mới. – Katana314

Trả lời

2

Nếu tất cả các hình dạng của một loại tương tự sẽ luôn chia sẻ hành vi, thì có nghĩa là không lưu trữ hành vi đó ở cấp 'cho mỗi trường hợp'.Thay vào đó, bạn có thể có một 'CanMatchManager', lưu trữ một từ điển danh sách, được lập chỉ mục theo kiểu hình dạng. Sau đó, khi một vòng tròn cố gắng so sánh một trận đấu, nó sẽ yêu cầu các loại mà nó có thể khớp với MatchManager. Ngoài ra, MatchManager có thể lấy hai hình dạng, và xác định xem những trận đấu đó. Đây là Mediator Pattern

+1

Thú vị, vì vậy một cái gì đó như: CanMatchManager :: canMatch (Tile một, Tile hai) {return dictionary [one] .contains (hai)}, nơi bản đồ từ điển Tile Type vào danh sách các loại phù hợp. Sau đó, nếu tôi muốn thêm một tương tác, tôi có thể nói tileManager.addInteraction (Tile one, Tile two) và nó cập nhật như {dict [one] .add (hai), dict [two] .add (one)}. Điều đó có vẻ hiệu quả hơn những gì tôi đang làm, về mặt bộ nhớ cần thiết (Lấy nó từ các trường hợp, đưa nó cho một người quản lý). Nhưng nó vẫn còn sử dụng GetType phải không? – user2045279

+0

Câu hỏi thực sự của tôi là "Có được sử dụng GetType" cho loại vấn đề này không. Hoặc nó sẽ làm cho tinh thần để cơ cấu lại chương trình của tôi để có thể không sử dụng tên Class (loại). Một phần của tôi đang nói "Nếu nó hoạt động, và mã không phức tạp, có lẽ là ok trong trường hợp này" nhưng cũng "Instanceof và đó là ilk bốc mùi của thiết kế xấu". Để xây dựng trên nhận xét đầu tiên, từ điển sẽ là từ điển >. Làm thế nào để chúng ta có được các loại? someObject.GetType() và typeOf (SomeClass) – user2045279

+0

Nó vẫn đang sử dụng GetType nhưng có thể dễ dàng làm việc với một từ điển. Ít nhất, bởi vì nó là trung tâm, bạn có thể dễ dàng chuyển đổi cách nó hoạt động với một Enum. Vấn đề lớn của tôi với các loại Enum, không phải là một cách hay để có một lớp học nói Enum là gì và ở một thời điểm nào đó, nó cảm thấy giống như việc sao chép một hệ thống đã có sẵn. Ngoài ra, với các loại, bạn luôn có thể sử dụng sự phản chiếu để cho phép người quản lý của bạn hoạt động bằng cách sử dụng thừa kế ... Tôi đã chỉnh sửa câu trả lời của mình để liên kết với tài liệu mẫu. – Psymunn

2

Tôi biết câu hỏi đã được trả lời và chấp nhận, nhưng tôi đã làm điều gì đó như thế này một lần và tôi nghĩ tôi chỉ đăng mã ở đây.

public class TypeMatchManager 
    { 
     private Dictionary<Type, List<Type>> savedMatches = new Dictionary<Type, List<Type>>(); 

     public TypeMatchManager() { } 

     public void AddMatch(Type firstType, Type secondType) 
     { 
      this.addToList(firstType, secondType); 
      this.addToList(secondType, firstType); 
     } 

     public void DeleteMatch(Type firstType, Type secondType) 
     { 
      this.deleteFromList(firstType, secondType); 
      this.deleteFromList(secondType, firstType); 
     } 

     public bool CanMatch(Type firstType, Type secondType) 
     { 
      List<Type> firstTypeList = this.findListForType(firstType); 
      List<Type> secondTypeList = this.findListForType(secondType); 
      return (firstTypeList.Contains(secondType) || secondTypeList.Contains(firstType)); 
     } 

     private void addToList(Type firstType, Type secondType) 
     { 
      var matchingTypes = this.findListForType(firstType); 
      if (!matchingTypes.Contains(secondType)) 
      { 
       matchingTypes.Add(secondType); 
      } 
     } 

     private void deleteFromList(Type firstType, Type secondType) 
     { 
      var matchingTypes = this.findListForType(firstType); 
      if (matchingTypes.Contains(secondType)) 
      { 
       matchingTypes.Remove(secondType); 
      } 
     } 

     private List<Type> findListForType(Type type) 
     { 
      foreach (var keyValuePair in savedMatches) 
      { 
       if (keyValuePair.Key == type) 
       { 
        return keyValuePair.Value; 
       } 
      } 
      savedMatches.Add(type, new List<Type>()); 
      return findListForType(type); 
     } 
    } 

Lớp học được thiết kế sao cho thông số bạn cung cấp không quan trọng; nó kiểm tra type1.list có type2 và type2.list có kiểu.

Một ví dụ đơn giản:

 typeManager.AddMatch(a, b); 
     Console.WriteLine(typeManager.CanMatch(a, b)); // True 
     typeManager.DeleteMatch(b, a); 
     Console.WriteLine(typeManager.CanMatch(a, b)); // False 
     Console.WriteLine(typeManager.CanMatch(a, c)); // False 
     typeManager.AddMatch(a, c); 
     Console.WriteLine(typeManager.CanMatch(a, c)); // True 
     Console.WriteLine(typeManager.CanMatch(a, d)); // False 
     typeManager.AddMatch(b, d); 
     Console.WriteLine(typeManager.CanMatch(a, d)); // False 
     Console.WriteLine(typeManager.CanMatch(d, b)); // True 
     typeManager.DeleteMatch(d, b); 
     Console.WriteLine(typeManager.CanMatch(d, b)); // False 
     Console.WriteLine(typeManager.CanMatch(b, d)); // False 
+0

Đó là cơ bản chính xác như thế nào tôi nghĩ về làm cho nó. Rất đơn giản và thanh lịch, và nhỏ là tốt. Cảm ơn vì đầu vào của bạn! – user2045279

Các vấn đề liên quan