单人纸牌(Elevens Lab)活动8:使用abstract class Board
导言
Elevens是一系列单人纸牌游戏中的一种。在本活动中你会学到这些相关游戏中的几个。接着你会了解可以如何通过继承来重用这些游戏的通用代码,避免不必要的工作量。
探索:相关游戏
Thirteens
Elevens的近亲Thirteens一次在桌面上放置十张牌。A、2-10、J、Q分别对应1、2-10、11和12。和为13的一对牌可从桌面上移除,K则单独移。赢得游戏的概率据说是二分之一。
Tens
Elevens的另一近亲Tens一次在桌面上放置十三张牌。和为10的一对牌可从桌面上移除,10、J、Q、K的四张不同花色(如K♠,K♥,K♦与K♣)可被一起移除。赢得游戏的概率据说是八分之一。
探索:abstract class
阅读Elevens和其他近似的游戏后,你很快会发现这些游戏有共同的状态和行为。它们中的每个都要:
- 状态(实例变量)——牌组以及桌面上的牌
- 行为(method)——发牌、移除并替换所选的牌、检查是否赢得游戏、检查选中的牌是否符合游戏规则、检查是否还有其他可用的合法选择
考虑到这些共同的状态和行为,看起来继承可以让我们只写一次代码并重用它,而不是每写一个游戏就复制一次。
但如何做到这一点呢?对于is-a测试而言,ThirteensBoard
并不是ElevensBoard
。它们有许多共同之处,但两者之间的继承关系并不存在。所以我们该如何创建一个继承架构来利用这两种相近牌局的共同之处呢?
答案是利用一个共同的parent class。将这些牌局所共有的状态和行为抽离出来,放到一个新的class Board
中,然后让ElevensBoard
、TensBoard
和ThirteensBoard
继承class Board
。这么做说得通,因为这三个class都是不同的牌局变种。ElevensBoard
是Board
,ThirteensBoard
是Board
,TensBoard
也是Board
。这些class的继承关系如下所示,注意Board
是abstract
的,我们会在之后讨论原因。
让我们看看按照这种思路如何拆分在活动7中完成的原始ElevensBoard
代码。因为每种游戏都需要牌组和桌面上的牌局,所以所有的实例变量都可以放到Board
里。诸如deal
之类的method对每种游戏都一样,所以它们也可以被放到Board
里。containsJQK
之类的method是Elevens所特有的,只能在ElevensBoard
里。进展不错。
但是对于isLegal
和anotherPlayIsPossible
method,我们又该怎么办呢?所有Elevens的近亲都会有这些method,但对于不同的游戏它们的行为是不同的。这正是Java有abstract
method的原因。因为三个游戏都需要这两个method,我们会把它们包括在Board
里。然而因为这些method的实现取决于特定游戏,在Board
中我们将它们标记为abstract
,不提供具体实现。在child class中,我们则可以重写所有的abstract
method,实现针对特定游戏的特定行为。
考虑到我们无论如何都要在每个游戏牌局class内实现isLegal
和anotherPlayIsPossible
,我们为什么还要在Board
中引入abstract
method呢?考虑到使用牌局的class,比如活动6中你用到的GUI程序。这种class被称为class Board
的客户端(client)。GUI程序并不需要知道具体显示的是哪种游戏!它仅知道这个游戏涉及牌局并用到了Board
,也只与class Board
的method交互。因为isLegal
和anotherPlayIsPossible
method包括在Board
内,GUI程序可以调用它们。
最后,我们需要理解GUI程序如何能执行正确的isLegal
和anotherPlayIsPossible
method。当GUI程序启动时,其得到一个继承Board
的class的object。如果你想玩Elevens,你可以提供ElevensBoard
object。如果你想玩Tens,你可以提供TensBoard
object。所以当GUI程序用这个object来调用isLegal
和anotherPlayIsPossible
时,它会自动选择object里提供的实现。这被称作多态(polymorphism)。
问题
- 讨论Elevens,Thirteens和Tens的异同。
- 按照之前的分析,所有的实例变量都在
class Board
中声明。但仅在class ElevensBoard
中才“知道”桌面上的牌数以及牌组中扑克牌的大小、花色和点数值。Board
的实例变量是如何被初始化为ElevensBoard
的值的?具体的机制是什么? - 查看目录中的
Board.java
和ElevensBoard.java
文件。找到Board.java
中的abstract
method。查看这些method在ElevensBoard
中的实现。它们是否体现了问题1中所讨论的Elevens
、Thirteens
和Tens
的区别?为什么?
0 条评论