2009-08-16 27 views
15

Hãy tưởng tượng hai loại sau đây của một trò chơi cờ vua:Làm thế nào để tránh tham chiếu đơn vị vòng tròn?

TChessBoard = class 
private 
    FBoard : array [1..8, 1..8] of TChessPiece; 
... 
end; 

TChessPiece = class abstract 
public 
    procedure GetMoveTargets (BoardPos : TPoint; Board : TChessBoard; MoveTargetList : TList <TPoint>); 
... 
end; 

Tôi muốn hai lớp được định nghĩa trong hai đơn vị riêng biệt ChessBoard.pasChessPiece.pas.

Làm cách nào để tránh tham chiếu đơn vị vòng tròn mà tôi chạy vào đây (mỗi đơn vị là cần thiết trong phần giao diện của đơn vị khác)?

Trả lời

11

Thay đổi đơn vị xác định TChessPiece để trông giống như sau:

TYPE 
    tBaseChessBoard = class; 

    TChessPiece = class 
    procedure GetMoveTargets (BoardPos : TPoint; Board : TBaseChessBoard; ...  
    ... 
    end;  

sau đó sửa đổi các đơn vị xác định TChessBoard để trông giống như sau:

USES 
    unit_containing_tBaseChessboard; 

TYPE 
    TChessBoard = class(tBaseChessBoard) 
    private 
    FBoard : array [1..8, 1..8] of TChessPiece; 
    ... 
    end; 

này cho phép bạn để vượt qua các trường hợp cụ thể để các mảnh cờ mà không cần phải lo lắng về một tham chiếu vòng tròn. Kể từ khi hội đồng quản trị sử dụng Tchesspieces trong tư nhân của nó, nó thực sự không phải tồn tại trước khi tuyên bố Tchesspiece, cũng giống như nơi giữ. Bất kỳ biến trạng thái nào mà tChessPiece phải biết về khóa học phải được đặt trong tBaseChessBoard, nơi chúng sẽ có sẵn cho cả hai.

+0

Chấp nhận điều này, vì nó ngắn và cung cấp giải pháp cho câu hỏi của tôi. Tôi đã bình chọn tất cả các câu trả lời hay khác. – jpfollenius

+0

Tôi nhận được lỗi "E2086 Type 'tBaseChessBoard' chưa được xác định hoàn toàn". – Ampere

+0

@Alaun, đây không phải là một đoạn trích hoàn toàn làm việc, vừa đủ để thể hiện đường dẫn đến một giải pháp. TBaseChessBoard nên có các phương thức cần thiết bởi GetMoveTargets nhưng được triển khai như trừu tượng hoặc ảo. – skamradt

2

Sẽ tốt hơn nếu di chuyển lớp ChessPiece thành đơn vị ChessBoard.
Nếu vì lý do nào đó bạn không thể, hãy thử đặt một mệnh đề sử dụng mệnh đề cho phần triển khai trong một đơn vị và để nguyên một phần khác trong phần giao diện.

+0

Về điểm thứ hai của bạn: như tôi đã đề cập, điều này không hiệu quả vì tôi cần định nghĩa từ đơn vị khác trong phần giao diện! – jpfollenius

+1

Ồ, tôi đã bỏ lỡ điều đó :-) Thực sự cần thiết ChessPiece để * biết * ChessBoard, hoặc cách khác xung quanh? –

+0

Vâng, các mảnh cờ vua nên quyết định nơi nó có thể di chuyển (sẽ có lớp con cho các phần khác nhau) và nó cần phải biết hội đồng quản trị để xác định nơi nó có thể đi. Một hướng khác khá rõ ràng. – jpfollenius

16

Một giải pháp có thể là giới thiệu đơn vị thứ ba chứa các khai báo giao diện (IBoard và IPiece).

Sau đó, phần giao diện của hai đơn vị với tờ khai lớp có thể tham khảo các lớp khác bằng giao diện của nó:

TChessBoard = class(TInterfacedObject, IBoard) 
private 
    FBoard : array [1..8, 1..8] of IPiece; 
... 
end; 

TChessPiece = class abstract(TInterfacedObject, IPiece) 
public 
    procedure GetMoveTargets (BoardPos: TPoint; const Board: IBoard; 
    MoveTargetList: TList <TPoint>); 
... 
end; 

(modifier const trong GetMoveTargets tránh tính tham khảo không cần thiết)

+0

không phải là việc sử dụng Giao diện quá nhiều (trong trường hợp này) ??? – Ampere

+0

@ user928177263 có thể. nhưng một khi đã học, giải pháp có thể được áp dụng cho các biến thể lớn hơn của cùng một vấn đề – mjn

0

Nó không giống như TChessBoard.FBoard cần phải là một mảng của TChessPiece, nó cũng có thể được TObject và được downcasted trong cờ vua Piece.pas.

+0

-1 Không, nó không thể. Có các phương thức trong TChessBoard gọi là FBoards [I, J] .GetMoveTargets (...). Thêm vào đó một downcast không phải là giải pháp sạch tôi đang tìm kiếm. – jpfollenius

+0

thì tại sao bạn không nói như vậy trong câu hỏi của bạn? Tôi chỉ đưa cho bạn một gợi ý đơn giản, không cần phải downvote tôi vì tôi không biết về các lớp học của bạn. – Ozan

+0

Đó không phải là lý do tôi bỏ phiếu. Lý do tôi downvoted là tôi không xem xét một giải pháp với một downcast một giải pháp sạch. Nhưng vì tôi đã không yêu cầu một cách rõ ràng giải pháp hướng đối tượng rõ ràng, tôi lấy lại phần còn lại của tôi. Xin lỗi vì điều đó. – jpfollenius

1

Với Delphi Prism bạn có thể trải rộng không gian tên của mình qua các tệp riêng biệt, vì vậy bạn sẽ có thể giải quyết nó theo cách rõ ràng.

Cách hoạt động của đơn vị chỉ bị phá vỡ cơ bản với triển khai Delphi hiện tại của chúng. Chỉ cần xem cách "db.pas" cần có TField, TDataset, TParam, vv trong một tệp .pas quái dị vì giao diện của chúng tham chiếu lẫn nhau.

Dù sao, bạn luôn có thể di chuyển mã sang một tệp riêng biệt và bao gồm chúng với {$include ChessBoard_impl.inc} chẳng hạn. Bằng cách đó bạn có thể chia nhỏ nội dung trên các tệp và có các phiên bản riêng biệt thông qua vcs của bạn. Tuy nhiên, nó chỉ là một chút không hài lòng để chỉnh sửa các tập tin theo cách đó.

Giải pháp dài hạn tốt nhất là thúc đẩy embarcadero mương một số ý tưởng có ý nghĩa vào năm 1970 khi pascal được sinh ra, nhưng điều đó không nhiều hơn nỗi đau trong ass cho các nhà phát triển hiện nay. Trình biên dịch một lần là một trong số đó.

+1

Rất tiếc là mọi người chỉ giảm cân mà không bình luận về phần nào họ không đồng ý. –

+2

+1 để đề xuất trình biên dịch một lần nên là lịch sử. Tôi đoán rằng sự gia tăng trung bình của thời gian biên dịch sẽ không quá 5% cho một trình biên dịch hai vượt qua. Có lẽ ai đó biết chính xác số liệu :)? – mjn

+1

Tệp đơn vị không bị "hỏng". Điều duy nhất ngăn cản các tệp .pas khổng lồ đó bị tách ra là khả năng tương thích ngược. Phần lớn khớp nối đó có thể tránh được bằng cách sử dụng giao diện tự do. Tôi đồng ý về trình biên dịch một-pass mặc dù. Nó đặt các yêu cầu không cần thiết khi nhà phát triển và ngăn chặn một số tối ưu hóa thời gian chạy rất hữu ích mà yêu cầu nhiều vé để thực hiện. –

0

Cách tiếp cận khác:

Làm cho bảng của bạn về tBaseChessPiece. Nó trừu tượng nhưng chứa các định nghĩa bạn cần tham khảo.

Hoạt động bên trong nằm trong tChessPiece xuống từ tBaseChessPiece.

Tôi đồng ý rằng việc Delphi xử lý những thứ liên quan đến nhau là xấu - về tính năng tồi tệ nhất của ngôn ngữ. Tôi đã từ lâu gọi các tờ khai chuyển tiếp hoạt động trên các đơn vị. Trình biên dịch sẽ có thông tin cần thiết, nó sẽ không phá vỡ tính chất một-pass mà làm cho nó quá nhanh.

22

Các đơn vị Delphi không bị "cơ bản bị hỏng". Cách họ làm việc tạo điều kiện cho tốc độ hiện tượng của trình biên dịch và thúc đẩy thiết kế lớp học sạch sẽ.

Có thể truyền bá các lớp trên các đơn vị theo cách mà Prims/.NET cho phép là cách tiếp cận được cho là cơ bản bị phá vỡ vì nó khuyến khích tổ chức hỗn loạn của các lớp bằng cách cho phép nhà phát triển bỏ qua nhu cầu thiết kế đúng khuôn khổ của họ việc áp dụng các quy tắc cấu trúc mã tùy ý như "một lớp cho mỗi đơn vị", không có thành tích kỹ thuật hoặc tổ chức như là một nguyên tắc phổ quát.

Trong trường hợp này, tôi ngay lập tức nhận thấy một đặc tính riêng trong thiết kế lớp học phát sinh từ tình trạng khó xử tham khảo vòng tròn này.

Tức là, tại sao một mảnh bao giờ có cần tham chiếu đến bảng không?

Nếu một phần được lấy từ bảng, tham chiếu như vậy sẽ không có ý nghĩa hoặc có thể "Mục tiêu di chuyển" hợp lệ cho một phần đã bị xóa chỉ là phần hợp lệ cho phần đó làm "vị trí bắt đầu" trong trò chơi mới? Nhưng tôi không nghĩ rằng điều này có ý nghĩa như bất cứ điều gì khác hơn là một biện minh tùy ý cho một trường hợp yêu cầu GetMoveTargets hỗ trợ lời gọi với một tham chiếu bảng NIL.

Các vị trí cụ thể của một mảnh cá nhân tại bất kỳ thời điểm nào là một tài sản của một trò chơi cá nhân của cờ vua, và không kém các HỢP LỆ di chuyển mà bạn có thể CÓ THỂ cho bất kỳ mảnh nhất định được phụ thuộc vào vị trí của Các phần khác trong trò chơi.

TChessPiece.GetMoveTargets không cần kiến ​​thức về trạng thái trò chơi hiện tại. Đây là trách nhiệm của TChessGame. Và TChessPiece không cần tham chiếu đến trò chơi hoặc bảng để xác định mục tiêu di chuyển hợp lệ từ một vị trí hiện tại nhất định. Các ràng buộc của bảng (8 cấp bậc và tệp) là các hằng số miền, không phải là các thuộc tính của một cá thể bảng đã cho.

Vì vậy, một TChessGame là yêu cầu mà gói gọn kiến ​​thức kết hợp nhận thức của một hội đồng quản trị, các mảnh và - điều quan trọng - các quy tắc, nhưng hội đồng quản trị và các mảnh không cần kiến ​​thức về nhau OR của trò chơi. Có thể có vẻ hấp dẫn khi đặt các quy tắc liên quan đến các phần khác nhau trong lớp đối với loại bản thân, nhưng đây là một lỗi sai, vì nhiều quy tắc dựa trên tương tác với các phần khác và trong một số trường hợp cụ thể loại mảnh. Hành vi "hình ảnh lớn" như vậy đòi hỏi một mức độ over-sight (đọc: tổng quan) của tổng số trạng thái trò chơi đó là không thích hợp trong một lớp mảnh cụ thể.

ví dụ: một TChessPawn có thể xác định rằng một mục tiêu di chuyển hợp lệ là một hoặc hai ô vuông về phía trước hoặc một hình vuông theo đường chéo về phía trước nếu một trong hai ô vuông chéo đó bị chiếm đóng. Tuy nhiên, nếu sự di chuyển của cầm đồ cho thấy nhà vua đến một tình huống KIỂM TRA, thì cầm đồ không di chuyển được chút nào.

Tôi sẽ tiếp cận điều này bằng cách đơn giản cho phép lớp cầm đồ chỉ ra tất cả các mục tiêu di chuyển có thể di chuyển - 1 hoặc 2 ô vuông về phía trước và cả hai hình vuông về phía trước theo đường chéo. TChessGame sau đó xác định cái nào trong số này hợp lệ bằng cách tham chiếu đến khả năng chiếm dụng của các mục tiêu di chuyển và trạng thái trò chơi đó.2 ô vuông về phía trước chỉ có thể nếu cầm đồ ở cấp nhà, hình vuông phía trước bị chiếm đóng BLOCK a di chuyển = mục tiêu không hợp lệ, ô vuông đường chéo UNACupITE di chuyển, và nếu bất kỳ di chuyển hợp lệ nào khác cho thấy King thì di chuyển đó cũng không hợp lệ.

Một lần nữa, sự cám dỗ có thể là đặt các quy tắc áp dụng chung trong lớp cơ sở TChessPiece (ví dụ như một động thái đã cho thấy vua?), Nhưng áp dụng quy tắc đó yêu cầu nhận thức về trạng thái trò chơi tổng thể - phần khác - vì vậy nó đúng hơn thuộc như một hành vi tổng quát của lớp TChessGame, IMHO

Bên cạnh đó để di chuyển mục tiêu, miếng cũng cần phải chỉ ra CaptureTargets, mà trong trường hợp của hầu hết các mảnh là như nhau, nhưng trong một số trường hợp khá khác nhau - cầm đồ là một ví dụ điển hình. Nhưng một lần nữa, trong đó - nếu có - tất cả các hình ảnh tiềm năng có hiệu lực đối với bất kỳ động thái nào là - imho - một đánh giá về các quy tắc của trò chơi, không phải là hành vi của một mảnh hoặc một phần của các mảnh. Như trường hợp trong 99% tình huống như vậy (ime - ymmv), tiến thoái lưỡng nan có lẽ được giải quyết tốt hơn bằng cách thay đổi thiết kế lớp để thể hiện tốt hơn vấn đề đang được mô hình hóa, không tìm cách để thiết kế lớp học thành một tùy ý tổ chức tệp.

+1

Trình biên dịch * KHÔNG * thực thi một lớp trên mỗi đơn vị khi xử lý các biểu mẫu. Vì vậy, bạn phải nhảy qua hoops để cho phép hai hình thức hợp tác. Thường thì điều này có nghĩa là nó phải là một hình thức nhưng đôi khi bạn muốn các phần di động độc lập. –

+0

"Không sạch" trong thiết kế lớp có chứa các mối quan hệ M: N chẳng hạn? QA có nên từ chối các phụ thuộc M: N trong thiết kế lớp, với lý do chúng sẽ dẫn đến hỗn loạn? – mjn

+0

Chỉ một bình luận về thiết kế bạn đề xuất: bạn sẽ phải thêm một câu lệnh điều kiện lớn vào lớp TChessGame của bạn, một nhánh cho mỗi phần. Các mảnh sẽ mất tất cả chức năng trong trường hợp đó. Đó là những gì tôi muốn tránh. Nhưng tôi thừa nhận rằng thiết kế của tôi có thể không phải là tốt nhất. Đó chỉ là một thử đầu tiên. Có lẽ chúng tôi sẽ nhận được một số ý kiến ​​khác về điều đó. – jpfollenius

0

gì về phương pháp này:

bàn cờ đơn vị:

TBaseChessPiece = class 

public 

    procedure GetMoveTargets (BoardPos : TPoint; Board : TChessBoard; MoveTargetList : TList <TPoint>); virtual; abstract; 

... 

TChessBoard = class 
private 
    FBoard : array [1..8, 1..8] of TChessPiece; 

    procedure InitializePiecesWithDesiredClass; 
... 

mảnh đơn vị:

TYourPiece = class TBaseChessPiece 

public 

    procedure GetMoveTargets (BoardPos : TPoint; Board : TChessBoard; MoveTargetList : TList <TPoint>);override; 

... 

Trong đơn vị aproach bàn cờ này sẽ bao gồm các tài liệu tham khảo của đơn vị mảnh chỉ trong việc thực hiện phần (do phương pháp mà trong thực tế sẽ tạo ra các đối tượng) và đơn vị miếng sẽ có một tham chiếu đến đơn vị bảng cờ trong giao diện. Nếu tôi không sai này xử lý vấn đề của bạn một cách dễ dàng ...

+0

yep, đó là khá nhiều giải pháp tương tự như đã được đề xuất chỉ với các lớp học trao đổi. BTW: tại sao bạn không sử dụng thụt lề để làm cho mã dễ đọc hơn? Chỉ cần sử dụng 4 dấu cách không gian – jpfollenius

0

lấy TChessBoard từ TObject

TChessBoard = class (TObject)

sau đó bạn có thể khai báo GetMoveTargets thủ tục (BoardPos: TPoint; Board: TObject; MoveTargetList: TList);

khi bạn gọi proc, sử dụng TỰ như đối tượng Board (nếu bạn đang gọi nó từ đó), sau đó bạn có thể tham khảo nó với

(Ban như TChessBoard). và truy cập các thuộc tính vv từ đó.

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