-
1 自顶向下设计
-
2 自底向上执行测试
自顶向下设计
本小节以模拟体育竞技进行竞技分析的实例,通过本实例了解程序设计中的“自顶向下,自底向上”的程序设计思想。
从各种球类比赛规则中抽象出这样的一种体育竞技规则:
(1)两个球员在一个有4而边界的场地上用球拍击球。
(2)开始比赛时,其中一个球员首先发球。然后,球员交替击球,直到可以判定得分为止,此过程称为回合。当一名球员未能进行一次合法击球时,回合结束。未能打中球的球员输掉这个回合。如果输掉这个回合的是发球方,那么发球权交给另一方; 如果输掉的是接球方,则仍然由这个回合的发球方继续发球。总之,每回合结束,由赢得该回合的一方发球。
(3)球员只能在其发球局中得分。
(4)先达到15分的球员赢得一该局的比赛。
计算机在模拟此竞技中,运动员的能力级别将通过发球发赢得本回合的概率来表示。例如,能力值0.6表示该球员在其发球局有60%的可能性赢得1分。程序首先接收两个球员的能力值,然后利用这个值采用概率方法模拟多场比赛。程序最后输出比赛运行结果。
用IPO来描述程序的模拟问题:
输入:球员A和球员B的能力值,模拟比赛的次数
处理:模拟比赛过程
输出:球员A和B分别赢得球赛的概率。
抽象这个问题时,将球员失误、犯规等可能性一并考虑在能力值中,在每局比赛中,球员A先发球。可以期望输出结果如下:
模拟比赛总次数:400
球员A获胜次数:201(50.2%)
球员B获胜次数:199(49.8%)
接下来,我们从自顶向下设计该程序。
自顶向下设计的基本思想是以一个总问题开始,试图把它表达为很多小问题组成的解决方案。再用同样的技术依次解决小问题,最终问题变得非常小,以至于可以很容易解决。然后只需把所有的碎片组合起来,就组成解决总问题的程序。
顶层设计:最重要部分
依据以上IPO描述,将模拟问题化解为以下4个基本设计步骤:
step1:打印程序的介绍信息。
step2:获得程序运行所需的参数:球员A能力值,球员B能力值,模拟次数: probA,probB,n
step3:依据probA,probB,模拟n 次比赛
step4: 输出球员A和球员B获胜场次及获胜率
依据将基础设计,作为开始进行自顶向下设计的顶层设计。顶层设计不写出具体的代码,仅给出函数定义,将这些函数放入main()函数中。
step1是为提高用户体验,打印一些必要的说明信息。定义这个功能名printIntro()
def main()
printIntro()
step2获得用户输入,得到probA,probB,n 。 定义这个功能名为getInputs()
def main()
printIntro()
probA,probB,n= getInputs()
step3用probA,probB,模拟n次比赛。定义名为simNGames()函数,返回结果为球员A赢次数和B赢的次数:winsA, winsB。
def main()
printIntro()
probA,probB,n= getInputs()
winsA, winsB=simNGames(n, probA,probB)
step4输出结果。定义名为printSummary()函数完成。
def main()
printIntro()
probA,probB,n= getInputs()
winsA, winsB=simNGames(n, probA,probB)
printSummary(winsA, winsB)
2.第二层设计
经过顶层设计,main()成为顶得结构,其程序结构图如下:

图1 体育竞技分析程序结构图:顶层设计
自顶向下设计的第二阶段是实现或进一步抽象第2层函数。
printIntro()仅是输出程序的介绍,用print()表达。
def printIntro():
print("模拟两选手A和B的比赛")
print("程序运行需要A和B的能力值[0..1]")
getInputs()函数提示用户输入3个值,代码也可直接表达:
def getInputs():
a=eval(input("输入A的能力值:"))
b=eval(input("输入B的能力值:"))
n=eval(input("输入模拟的场次:"))
return a,b,n
simNGames()函数是整个程序的核心,模拟n场比赛,并记录球员的赢的次数。重复n次,每次记录赢的情况,用计数器变量完成。代码:
def simNGames(n,proA,proB):
winsA,winsB=0,0
for i in range(n):
scoreA,scoreB=simOneGame(proA,proB)
if scoreA > scoreB:
winsA+=1
else:
winsB+=1
return winsA,winsB
代码中设计了simOneGame()函数,用于模拟一场比赛,这个函数需要知道球员的能力值,返回球员的得分,以下图给出了这个设计的整体结构图更新:

图2 体育竞技分析程序结构图:第二阶段
3.第三层设计
第三层设计,需要实现simOneGame()函数。为模拟一场比赛,需要根据比赛规则来编写代码,两球员持续对攻直到比赛结束。可以用循环结构,判断条件是比赛结束。同时还要记录比赛的得分,保留发球局标记,总之,尽可能详细模拟比赛过程。那如何判断发球方的得分呢?可以考虑用随机数与能力值对比来模拟。如果球员A拥有发球权,其概率值大于随机值,表明其得分,否则发球权交给球员B,同样情形适合于B。代码如下:
def simOneGame(proA,proB):
scoreA,scoreB=0,0
serving = "A"
while not gameOver(scoreA,scoreB):
if serving == "A":
if random()<proA:
scoreA +=1
else:
serving="B"
else:
if random()<proB:
scoreB +=1
else:
serving="A"
return scoreA,scoreB
这里进一步设计了gameOver()函数,用来表示一场比赛结束的条件,对于不同比赛结束条件可能不同,单独将其封装为函数有助于简化根据不同规则修改函数的代价,提高代码可维护性。gameOver()返回值True或False。

图3 体育竞技分析程序结构图:第三阶段
若比赛规则为,当任意一球员分数达到15分时比赛结束。那gameOver()函数实现代码如下:
def gameOver(scoreA,scoreB):
return scoreA==15 or scoreB==15
最后,第二层的printSummary()函数,代码如下:
def printSummary(winsA,winsB):
n= winsA +winsB
print("比赛开始,现模拟{}场比赛。".format(n))
print("A选手赢了{}场比赛,占{:.1%}。".format(winsA,winsA/n))
print("B选手赢了{}场比赛,占{:.1%}。".format(winsB,winsB/n))
将上述代码放在一起,就形成了解决整个问题的代码simulateGame.py。
代码可从平台“资料”中“实例代码”中下载。
4.自顶向下设计过程总结
从问题输入输出确定开始,整体设计逐渐向下进行。每一层以大体算法描述开始,然后逐步细化成代码,细节被函数封装,整个过程可以概括为以下4个步骤。
(1)将算法表达为一系列小问题
(2)为每个小问题设计接口
(3)通过将算法表达为接口关联的多个小问题来细化算法
(4)为每个小问题重复上述过程
自顶向下设计是一种开发复杂程序最具价值的设计理念和工具,设计过程自然且简单,自顶向下设计通过封装实现抽象,利用了模块化设计的思想。

