Bạn có thể có bản trống / lớp trừu tượng không?


10

Tất nhiên là bạn có thể, tôi chỉ tự hỏi liệu nó có hợp lý để thiết kế theo cách như vậy không.

Tôi đang tạo một bản sao đột phá và đang thực hiện một số thiết kế lớp. Tôi muốn sử dụng tính kế thừa, mặc dù tôi không phải áp dụng những gì tôi đã học được trong C ++. Tôi đã suy nghĩ về thiết kế lớp học và nghĩ ra một thứ như thế này:

GameObject -> lớp cơ sở (bao gồm các thành viên dữ liệu như x và y offset, và một vectơ SDL_Surface *

MovableObject: GameObject -> lớp trừu tượng + lớp dẫn xuất của GameObject (một phương thức void move () = 0;)

NonMovableObject: GameObject lớp trống ... không có phương thức hoặc thành viên dữ liệu nào ngoài hàm tạo và hàm hủy (ít nhất là bây giờ?).

Sau đó, tôi đã lên kế hoạch để lấy một lớp từ NonMovableObject, như Tileset: NonMovableObject. Tôi chỉ tự hỏi liệu các lớp trừu tượng "trống" hay chỉ các lớp trống thường được sử dụng ... Tôi nhận thấy rằng cách tôi đang làm điều này, tôi chỉ tạo lớp NonMovableObject chỉ vì mục đích phân loại.

Tôi biết tôi đang suy nghĩ quá nhiều thứ chỉ để tạo một bản sao đột phá, nhưng tôi không tập trung vào trò chơi và tập trung vào việc kế thừa và thiết kế một số khung trò chơi.

Câu trả lời:


2

Trong C ++, bạn có nhiều kế thừa nên thực sự không có lợi ích gì khi có các lớp trống đó. Nếu bạn muốn có Ball kế thừa từ GameObject và MovableObject sau (ví dụ: bạn muốn giữ một mảng GameObjects và gọi một phương thức Tick mỗi giây trong một giây để làm cho tất cả chúng di chuyển), điều đó đủ dễ thực hiện.

Nhưng, trong tình huống đó, cá nhân tôi đề nghị bạn nên nhớ tiên đề " thích thành phần (đóng gói) hơn thừa kế " thay vào đó và nhìn vào mẫu Trạng thái . Nếu bạn muốn mỗi đối tượng có trạng thái chuyển động của nó sau này, hãy chuyển nó vào GameObject. Ví dụ: Dia chéoBouncingMovementState cho quả bóng, HoriazontalControlledMovementState cho mái chèo, NoMovementState cho một viên gạch.

1) Điều này sẽ giúp bạn dễ dàng viết các bài kiểm tra đơn vị hơn , nếu bạn chọn (một lần nữa, tôi muốn giới thiệu) - bởi vì bạn có thể kiểm tra GameObject và từng trạng thái của bạn một cách độc lập.

2) Giả sử bạn có mã thông báo rơi từ các viên gạch nhưng sau đó bạn muốn thay đổi một trong số chúng để chúng di chuyển theo đường chéo - thay vì chuyển nó xung quanh một số thứ bậc phức tạp của kế thừa lớp, bạn có thể thay đổi trạng thái chuyển động được mã hóa cứng được chuyển sang nó .

3) Quan trọng nhất: Khi bạn muốn bắt đầu thay đổi trong trò chơi cách bóng và / hoặc mái chèo di chuyển dựa trên các mã thông báo đó, bạn chỉ cần tiếp tục thay đổi trạng thái chuyển động của nó (Dia chéoMovementState trở thành Dia chéoWithGravityMovementState).

Bây giờ, không ai trong số này cho rằng thừa kế luôn xấu, chỉ để chứng minh tại sao chúng ta thích đóng gói hơn thừa kế. Bạn vẫn có thể muốn lấy từng loại đối tượng từ GameObject và để các lớp đó hiểu Trạng thái Chuyển động ban đầu của chúng. Bạn gần như chắc chắn sẽ muốn lấy được từng trạng thái chuyển động của mình từ một lớp MovementState trừu tượng vì C ++ không có giao diện.

0,02 đô la


8

Trong Java, các giao diện "trống" được sử dụng làm điểm đánh dấu (ví dụ: Nối tiếp), bởi vì trong thời gian chạy, các đối tượng có thể được kiểm tra thời tiết hoặc chúng không "thực hiện" giao diện đó; nhưng trong C ++, nó dường như khá vô nghĩa đối với tôi.

IMO, các tính năng ngôn ngữ nên được coi là công cụ, một cái gì đó giúp bạn đạt được các mục tiêu nhất định và không phải là nghĩa vụ. Chỉ vì bạn có thể sử dụng một tính năng không có nghĩa là bạn phải làm . Kế thừa vô nghĩa không làm cho một chương trình tốt hơn.


3

Bạn không quá suy nghĩ về nó; bạn đang nghĩ và điều đó là tốt

Như đã nêu, không sử dụng tính năng ngôn ngữ chỉ vì nó ở đó. Học đánh đổi tính năng ngôn ngữ là chìa khóa để thiết kế tốt.

Người ta không nên sử dụng các lớp cơ sở để phân loại. Điều đó có thể ngụ ý loại kiểm tra. Đó là một ý tưởng tồi.

Xem xét việc viết tóm tắt thuần túy xác định hợp đồng của các đối tượng của bạn. Hợp đồng của các đối tượng của bạn là giao diện hướng ra ngoài. Giao diện công cộng của nó. Những gì nó làm.

Lưu ý: Điều quan trọng là phải hiểu đây không phải là dữ liệu mà nó có x, ymà là những gì nó làm move().

Trong thiết kế của bạn, bạn có một lớp GameObject. Bạn đang dựa vào nó cho dữ liệu của nó chứ không phải chức năng của nó. Nó cảm thấy efficent và chính xác để sử dụng thừa kế để chia sẻ những gì có vẻ như dữ liệu chung chung, trong trường hợp của bạn xy.

Đây không phải là cách sử dụng chính xác của thừa kế. Luôn nhớ rằng, thừa kế là hình thức khớp nối chặt chẽ nhất mà bạn có thể có và bạn nên cố gắng để khớp nối lỏng lẻo mọi lúc.

Bởi bản chất của NonMovableObjectnó không di chuyển. Nó xychắc chắn nên được tuyên bố const. Bởi bản chất của nó MoveableObjectcần phải di chuyển. Nó không thể tuyên bố nó xynhư const. Do đó, đây không phải là cùng một dữ liệu và nó không thể được chia sẻ.

Xem xét chức năng hoặc hợp đồng của đối tượng của bạn. Họ nên làm gì ? Nhận quyền đó và dữ liệu sẽ đến. Làm việc trên một đối tượng tại một thời điểm. Lo lắng về từng lượt. Bạn sẽ tìm thấy thiết kế tốt của bạn có thể tái sử dụng.

Có lẽ không có gì NonMovableObjectcả. Điều gì về một sự trừu tượng thuần túy GameObjectBaseđịnh nghĩa một move()phương thức? Hệ thống của bạn kích hoạt GameObjectnhững gì thực hiện move()và hệ thống chỉ di chuyển những cái cần di chuyển.

Điều này chỉ là khởi đầu; một hương vị. Lỗ thỏ đi sâu hơn nhiều. Không có muỗng.


Mặc dù đúng là cả hai đều MovableObjectkhông NonMovableObjectthể thừa hưởng lẫn nhau, cả hai đều có một vị trí; như vậy, cả hai nên được tiêu thụ bằng mã, ví dụ như muốn hướng mục tiêu của quái vật về phía đối tượng mà không quan tâm đến việc đối tượng đó có thể di chuyển hay không.
supercat

2

Nó có các ứng dụng trong C # như ammoQ đã đề cập, nhưng trong C ++, ứng dụng duy nhất sẽ là nếu bạn muốn có một danh sách các con trỏ NonMovableObject.

Nhưng sau đó tôi sẽ tưởng tượng bạn sẽ hủy hoại các đối tượng thông qua con trỏ NonMovizableObject, trong trường hợp đó bạn cần các hàm hủy ảo và nó sẽ không còn là một lớp trống nữa. :)


Tôi cũng nghĩ về trường hợp đó, nhưng bạn có thể làm gì với một danh sách các đối tượng không có hành vi như vậy? Nhiều khả năng, bằng cách nào đó, bạn sẽ tìm ra loại thực và sử dụng một nhóm để truy cập các phương thức và trường có sẵn - chắc chắn là mùi mã.
user281377

Vâng, đó là một điểm tốt.
tenpn

1

Một tình huống mà bạn có thể thấy đây là nơi bạn muốn một bộ sưu tập được đánh mạnh mẽ để chứa các loại không đồng nhất. Tôi không nói đây là một ý tưởng tuyệt vời, nó có thể chỉ ra sự trừu tượng yếu hoặc thiết kế kém, nhưng nó xảy ra. Như bạn đề cập, đó là một cách phân loại công cụ và nó có thể hữu ích và hoàn toàn hợp lệ.

Ví dụ: Bạn muốn có một bộ sưu tập để giữ Mèo và Chó. Trong thiết kế của bạn, bạn quyết định họ có nhiều điểm chung, vì vậy hãy có một lớp cơ bản của Mamal và sử dụng loại này trong bộ sưu tập của bạn (ví dụ: Danh sách). Tất cả đều tốt. Sau đó, bạn quyết định muốn thêm một số Ếch vào bộ sưu tập của mình nhưng chúng không phải là mamals, vì vậy một cách để làm điều này là tạo ra phân lớp Thú và ếch, nhưng có lẽ bạn không thể nghĩ nhiều về Ếch và Mamals Thông thường nên Thú vẫn trống. Sau đó, bạn nhập bộ sưu tập của bạn với Animal thay vì Mamal và tất cả đều tốt.

Trong cuộc sống thực, tôi thấy rằng ngay cả khi tôi tạo ra một lớp cơ sở trống thì sớm muộn một số chức năng cũng kết thúc ở đó, nhưng chắc chắn có những lúc chúng tồn tại.


Tuy nhiên, một câu hỏi được đặt ra là: nếu các loại không có gì chung, tại sao chúng lại được giữ trong cùng một bộ sưu tập? Không có thao tác nào bạn có thể thực hiện trên tất cả các mục trong bộ sưu tập, loại nào đánh bại mục đích có một bộ sưu tập như vậy. Bây giờ có thể có một số thao tác nhân tạo mà bạn có thể muốn thêm vào cả hai để đơn giản hóa logic - ví dụ: triển khai mẫu thiết kế của Khách truy cập - tại thời điểm đó nó có thể trở nên hữu ích trở lại, nhưng bây giờ chúng có điểm chung ...
Jules
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.