吕兰兰
(湖南科技学院电子与信息工程学院,永州 425199)
面向对象程序设计主要讲授使用C++来进行面向对象程序设计。文献[1]以四个面向来表现C++的本质:面向过程、面向泛型、基于对象、面向对象。与基于对象程序设计相比,面向对象程序设计的编程理念更为先进也更为复杂,学生不容易区分二者的差别。学生虽然学习了封装、继承、多态等面向对象特性在C++中的语法,但是在设计程序解决具体问题时,虽然编写的C++程序中定义了类,却很少利用C++的继承和多态来提高代码重用。不难发现,文献[2]中给出的猜数字游戏的面向对象解决方案,从严格意义上来说其实是一个基于对象的解决方案,因为其中并没有用到继承和多态。因此,如何引导学生实现“从基于对象到面向对象的程序设计”的转化,成为讲授面向对象程序设计必须解决的重要问题之一。
在面向对象的程序设计方法中,将各种事物称为“对象”。将同一类事物的共同特点概括出来,这个过程就称为“抽象”[3]。对象的抽象包括两个方面:属性和方法。在猜数字游戏中,出现了游戏、人类玩家、电脑玩家等对象。答案是游戏对象的一个属性;人类玩家和电脑玩家猜测的数,也可作为它们的属性。游戏对象具有“判断输赢”、“开始游戏”等方法,而人类玩家和电脑玩家对象则具有“猜数”等方法。
在完成抽象后,通过某种语法形式,将属性和方法在形式上写成一个整体,即“类”,这个过程就称为“封装”[3]。在猜数字游戏中,经过封装可以得到3个类:游戏类、人类玩家类和电脑玩家类。
猜数字游戏中的人类玩家和电脑玩家,它们具有许多共性,例如它们都具有“猜数”这一方法以及所猜的“数”这一属性。同时,它们也分别具有一些特性,例如人类玩家可以根据当前猜数结果自动调整猜数范围,而电脑玩家则需通过“更新范围”的方法达到这一目标。如何在描述两类玩家各自特性的同时,避免对它们的共性进行重复描述呢?这就可以借助面向对象方法中的继承与派生了。所谓继承,就是从先辈处得到属性和行为特征[4]。因此,可以创建一个新的类——玩家类,用它来描述两类玩家的共性,再将人类玩家类和电脑玩家类作为玩家类的派生类,这样就能够很好地描述两类账户的共性和各自的特性。
所谓多态具体是指,由继承而产生的相关的不同的类,其对象对同一消息会做出不同的响应[5]。猜数字游戏中的人类玩家和电脑玩家,是从玩家类派生出的两个相关但不同的子类,它们在接收到“猜数”这一消息后,人类玩家可由用户直接从键盘输入所猜的数,而电脑玩家则只能产生一个随机数作为要猜的数。显然,它们针对同一消息所做出的响应是不同的,这就是多态。因此,在C++中可以通过将玩家类中的“猜数”这一方法声明为虚函数,并在人类玩家类和电脑玩家类中重新定义“猜数”方法,达到实现多态的目的。
经测试,文献[2]中“面向对象解决方案”中实现的C++程序,其运行结果与文献[2]中“教学案例:猜数字游戏”部分给出的程序运行样例有本质区别。在程序实际运行结果中,可能会出现以下情况:人类玩家Human首先猜了一个数50,程序提示“太低”,但紧接着电脑玩家猜了一个数16。这显示电脑玩家很“笨”。不难发现,这是由于电脑玩家每次只会随机猜一个数而造成的。为了提高电脑玩家的游戏智能,必须使电脑玩家可以根据双方已经猜过的数来调整自己的猜数范围。因此,可以启发学生进一步精化文献[2]的交互建模[6]和设计建模[6],并按照精化的设计类图重新编程实现猜数字游戏,这样就可以完善猜数字游戏的基于对象解决方案。
如上所述,要提高电脑玩家的游戏智能,必须要将游戏双方已经猜过的数以及猜的结果通知电脑玩家,尤其是人类玩家“:Human”的猜数情况。此时,猜数字游戏中游戏对象“:Game”、人类玩家“:Human”、电脑玩家“:Computer”这三个对象之间的交互情况与文献[2]有所不同。可以使用UML顺序图表达精化交互建模的结果,如图1所示。
图1 猜数字游戏顺序图
从图 1中可以看到,游戏对象“:Game”在每次check 之后,均向电脑玩家“:Computer”发送了一条 up⁃date消息来通知电脑玩家“:Computer”来及时更新自己猜数时的下限和上限。
图1中新增的两条update消息均由游戏对象“:Game”发出,且均由电脑玩家“:Computer”接收。按照设计建模的原则,对于顺序图中的每一条消息,接收该消息的对象需要提供相应的方法来响应,从而获得每一个类的职责和属性以及类之间的关系。因此,需在Computer类中添加相应的update()方法来处理接收到的消息。可使用UML设计类图表达精化设计建模的结果,如图2所示。
图2 猜数字游戏设计类图
需要注意的是,图2中除了Computer类新增了update()方法来更新猜数范围,还在Computer类中新增了2个private属性high和low用于表示猜数范围的上限和下限。同时,图2中Game类的check()方法的返回值类型由bool改为int,用于表示猜数字游戏结果中的猜高了(1)、猜对了(0)和猜低了(-1)。对部分学生来讲,这些可能要到类的代码实现阶段才能想到。
根据图2可以方便地进行类的代码实现。下面仅给出电脑玩家类Computer的完整代码,以及游戏类的部分代码。
在上述程序中,即使人类玩家故意忽视游戏提示乱猜,电脑玩家依然能够不受干扰地猜一个合理的数。例如,在游戏双方猜的50和30都提示“太高”的情况下,人类玩家Human故意猜一个数70,紧接着电脑玩家猜的数4却是小于30的。这表明电脑玩家在更新自己猜数范围的下限和上限时,能够人类玩家猜的数是否处于合理范围内做出正确判断。这样,电脑玩家已经变得和人类玩家一样“聪明”。
之所以将上述解决方案称为“精化的基于对象解决方案”,是因为其中并没有用到继承和多态特性。下面将利用继承和多态特性,进一步引导学生在目前构造的基于对象的猜数字游戏程序基础上,设计并实现面向对象的猜数字游戏。
如1.3中所述,不管是人类玩家,还是电脑玩家,都是猜数字游戏中的玩家,因此可以将二者的共性抽象出来,封装在玩家Player类中,且Player类中有一个public方法guess()和一个protected属性num。利用类的继承特性,以玩家Player类作为基类,从Player类派生出人类玩家Human类和电脑玩家Computer类。可使用UML设计类图表达精化设计建模的结果,如图3所示。
图3 猜数字游戏设计类图
通过将游戏类Game中的play()方法的原型做如下更改:
void play(Player&p1,Player&p2);
就可将猜数字游戏从“人机对战”模式扩展到“人人对战”、“机机对战”模式,如下所示:
根据图3可以方便地进行类的代码实现。下面仅给出玩家类Player的完整代码,以及游戏类Game的部分代码。
需要说明的是,Game类的play()方法的实现对于部分学生来讲可能是一个难点,尤其是在第一个玩家p1和第二个玩家p2分别猜完数字之后,均要使用下列语句通知游戏双方:
本文以学生熟悉的猜数字游戏作为案例,阐述了该案例的基于对象解决方案与面向对象解决方案,并在两种解决方案的设计过程中融合了基于UML的面向对象软件建模技术,以图形化的方式直观表达了从基于对象到面向对象的过渡。目前,该案例已在我校软件工程专业2015级和2016级学生中进行了2学期的教学实践。我们发现,通过引入UML,分别有大约95%、75%和90%的学生独立实现了该案例的基于对象方案、基于对象精化方案和面向对象方案,各项数据较之前均有大幅上升。但是,基于对象精化方案本身属于进阶内容。为了降低难度,也可跳过该方案、直接对文献[2]的基于对象方案进行面向对象改进。对大部分学生而言,要达到熟练运用继承和多态实现代码重用的程度,实现从基于对象程序设计到面向对象程序设计的跨越,还需要设计更多更好的能贯穿基于对象和面向对象的案例。而在不同难度案例的选择和设计上,仍然需要进行进一步的研究与探索。