### 8.4.3 编程案例:汇率换算器
本节通过一个应用实例来介绍 MV 方法的具体应用。我们希望设计一个汇率换算器程序, 其功能是将外币换算成人民币,或者相反。最终的版本是图形用户界面的,但在设计过程中, 我们还会设计一个文本界面的版本用来测试程序功能的正确性。
我们首先设计程序模型,这是由 CCApp 类实现的。设计 CCApp 时并不限定将使用的界 面,但随着 CCApp 的细化设计,我们会得到有关界面的功能需求,从而有助于程序用户界 面的设计和实现。
程序规格
汇率换算器程序用于在外币与人民币之间进行换算。 输入:外币币种,换算方向,金额 输出:等值的目标币种金额
明确候选对象
根据程序需求来确定对解题有用的对象。汇率换算器处理的是货币,货币用一个简单的 符号或数值就能表示,没有必要封装成对象。我们只将应用程序模型部分封装成一个类 CCApp,该类的实例需要记录当前的汇率信息,并至少需要实现构造器方法 init__和程序启 动方法 run。也许还需要若干辅助方法,这只有到设计主算法时才会明确。
除了核心部分,程序的另一个组成部分是用户界面。我们将界面也封装成对象,这样做 的好处是:在不改变模型的情况下,通过换用不同界面对象即可改变程序的外观。假设程序 将使用的界面对象是 xInterface,目前还不清楚这个类应有的行为,但随着我们精化 CCApp 的设计,就会明确需要从用户输入什么信息和向用户显示什么信息,所有输入和输出正对应 着 xInterface 类要实现的方法。
实现模型
由于本章重点是用户界面,所以作为例子的汇率换算器程序的模型很简单:按照存储的 当前汇率信息,将给定数额的外币换算成人民币,或将人民币换算成外币。对如此简单的问 题,只需一个 CCApp 类即可实现。当然,对于复杂程序,模型会涉及多种对象,那样就需 要实现多个类。模型的设计可采用自顶向下逐步求精方法,在此逐步细化的过程中,即可逐 步明确用户界面应该提供哪些方法。下面是 CCApp 类的定义:
【程序 8.12 之一】ccapp.py
```
class CCApp:
def init (self, inter):
self.xRate = {'USD':6.306,
'Euro':8.2735,
'Yen':0.0775,
'Pound':10.0486}
self.interface = inter
def run(self):
while True:
to,fc,amount,bye = self.interface.getInfo()
if bye:
break
elif to == 'RMB':
result = amount * self.xRate[fc]
else:
result = amount / self.xRate[fc]
self.interface.showInfo(result)
```
首先看构造器 \_\_init\_\_(),它负责汇率换算器的初始化,具体就是用一个字典 self.xRate 来存储若干外币名称及其对人民币的汇率①。注意, \_\_init\_\_方法还有个参数 inter,它代表程 序的用户界面,后面我们会分别用两种用户界面对象传递给此参数,从而得到两种版本的换 算器程序。
将来创建汇率换算器实例之后,通过调用实例的 run 方法来启动换算功能。换算器的核 心算法是个循环,每次循环完成一次换算,换算所需的各种信息都来自用户界面。可以看出 总体上换算过程仍然是简单的 IPO(即输入——处理——输出)算法模式,涉及的输入信息 包括换算方向、换算的外币、换算金额和退出标志(通过界面提供的 getInfo 方法获得),输 出信息就是换算结果(通过界面提供的 showInfo 方法显示)。当用户在用户界面选择退出时, 则不再循环,程序结束。
至此,我们实现了汇率换算器的核心功能,实现了程序的模型部分。但现在还无法测试 程序,因为还没有建立用户界面。但模型对用户界面的基本要求已经确定了,就是要提供 getInfo 和 showInfo 方法,参见图 8.26。
![](https://box.kancloud.cn/2016-02-22_56cafce688322.png)
图 8.26 模型-视图方法例 基于文本的用户界面
CCApp 类的定义中用到了很多用户界面的功能,可见模型的设计实现过程也揭示了用户 界面应当如何设计。和模型一样,我们将视图(用户界面)的功能也封装成一个类,这个界 面类必须包括 CCApp 类中所用到的所有方法:quit、close、getCurr、getDirection、getAmount 和 display。如果以不同方式来实现界面类 CCInterface,就能产生具有不同外观的换算器程序, 注意作为基础的模型 CCApp 类是不变的。可见视图与模型可以独立地设计,为同一模型可 以设计多种视图。
> ① 程序中的汇率数据是 2012 年 4 月 18 日的汇率。
由于一般来说 GUI 比较复杂,为了尽快测试模型的正确性,可以先设计一个简单的文本界面。这个界面纯粹用于测试,无需过多考虑用户友好性,因此我们以最简单最直接的方式 来实现 CCApp 所需的各种界面功能。
【程序 8.12 之二】ti.py
```
class TextInterface:
def __init__ (self):
print "Welcome to Currency Converter!"
self.qFlag = False # Quit flag
self.fc = 'USD' # foreign currency selected
self.to = 'RMB' # convert to?
self.amt = 0 # amount to be converted
def getInfo(self):
self.qFlag = self.getQFlag()
if self.qFlag:
self.close()
else:
self.fc = self.getFC()
self.to = self.getTo()
self.amt = self.getAmount()
return self.qFlag,self.fc,self.to,self.amt
def getQFlag(self):
ans = raw_input("Want to quit? (y/n) ")
if ans[0] in 'yY':
return True
else:
return False
def getFC(self):
return raw_input("Choose among {USD,Euro,Yen,Pound}: ")
def getTo(self):
ans = raw_input("Convert to RMB? (y/n) ")
if ans[0] in 'yY':
return 'RMB'
else:
return self.fc
def getAmount(self):
if self.to == 'RMB':
return input("How much " + self.fc + "? ")
else:
return input("How much RMB? ")
def showInfo(self,r):
if self.to == 'RMB':
print "%.2f %s ==> %.2f RMB" % (self.amt,self.fc,r)
else:
print "%.2f RMB ==> %.2f %s" % (self.amt,r,self.fc)
def close(self):
print "Goodbye!"
```
下面我们利用此用户界面来测试 CCApp 的正确性。为此目的,只需创建一个文本界面 对象,再创建 CCApp 对象,然后启动换算。测试程序如下:
【程序 8.12 之三】testti.py
```
from ccapp import CCApp
from ti import TextInterface
inter = TextInterface()
cc = CCApp(inter)
cc.run()
```
以下是测试运行示例,黑体部分是用户输入。结果表明程序的模型部分实现了预期的功 能。
```
Welcome to Currency Converter! Want to quit? (y/n) n
Choose among {USD,Euro,Yen,Pound}: USD
Convert to RMB? (y/n) y
How much USD? 100
## 100.00 USD ==> 630.60 RMB
Want to quit? (y/n) n
Choose among {USD,Euro,Yen,Pound}: Euro
Convert to RMB? (y/n) n
How much RMB? 10000
## 10000.00 RMB ==> 1208.68 Euro
Want to quit? (y/n) y
Goodbye!
```
实现 GUI
经过文本界面的测试,如果确信核心部分没有问题,即可转向设计更复杂但更加用户友 好的图形界面。我们要做的是确定图形界面的各种构件及布局,然后编写构件的处理代码。 与文本界面类似,图形界面需要提供的功能在模型设计过程已经确定了,图形界面必须支持 与文本界面相同的方法,另外也许还需要一些辅助方法。
根据模型部分所要求的界面功能来设计图形界面:选择要换算的外币种类,由于每次只 处理一种外币,故可用单选钮实现;输入和显示外币及等价人民币的金额,可用两个录入框 实现;双向换算和退出用三个命令按钮实现。至此即大致确定了图形界面的外观,接下来即 可为构件(主要是命令按钮)实现处理代码。最终得到如下 GUInterface 类定义:
【程序 8.12 之四】gui.py
```
from Tkinter import * class GUInterface:
def init (self): self.root = Tk()
self.root.title("Currency Converter")
self.qFlag = False # Quit flag
self.fc = StringVar() # foreign currency selected self.fc.set('USD')
self.to = 'RMB' # convert to?
self.amt = 0 # amount to be converted self.aRMB = StringVar() # amount of RMB self.aRMB.set('0.00')
self.aFC = StringVar() # amount of foreign currency self.aFC.set('0.00')
Label(self.root,textvariable=self.fc).grid( row=0,column=0,sticky=W)
Label(self.root,text='RMB').grid( row=0,column=2,sticky=W)
self.e1 = Entry(self.root,textvariable=self.aFC) self.e1.grid(row=1,column=0,rowspan=2)
self.e2 = Entry(self.root,textvariable=self.aRMB) self.e2.grid(row=1,column=2,rowspan=2)
self.b1 = Button(self.root,
text='---->',command=self.toRMB) self.b1.grid(row=1,column=1)
self.b2 = Button(self.root,
text='<----',command=self.toFC) self.b2.grid(row=2,column=1)
self.f = Frame(self.root) self.f.grid(row=3,column=0,columnspan=3) self.r1 = Radiobutton(self.f,text='USD',
variable=self.fc,value='USD') self.r1.grid(row=0,column=0)
self.r2 = Radiobutton(self.f,text='Euro',
variable=self.fc,value='Euro') self.r2.grid(row=0,column=1)
self.r3 = Radiobutton(self.f,text='Yen',
variable=self.fc,value='Yen') self.r3.grid(row=0,column=2)
self.r4 = Radiobutton(self.f,text='Pound',
variable=self.fc,value='Pound') self.r4.grid(row=0,column=3)
self.rate = Button(self.root,text='Update Rates') self.rate.grid(row=4,column=1)
self.qb = Button(self.root,text='Quit',command=self.close) self.qb.grid(row=5,column=1)
def getInfo(self): self.root.mainloop()
return self.qFlag,self.fc.get(),self.to,self.amt
def showInfo(self,r): rStr = "%.2f" % r if self.to == 'RMB':
self.aRMB.set(rStr) else:
self.aFC.set(rStr)
def toRMB(self): self.to = 'RMB'
self.amt = eval(self.aFC.get()) self.root.quit()
def toFC(self):
self.to = self.fc.get()
self.amt = eval(self.aRMB.get()) self.root.quit()
def close(self): self.qFlag = True self.root.quit() self.root.destroy()
```
这个类中的 getInfo 和 showInfo 是被模型部分调用的方法,用于输入和输出;其他几个 方法都是辅助方法,用来设置输入输出的信息。在此需要解释一下用到的技术性手段:当核 心程序调用界面的 getInfo 方法时,self.root.mainloop 方法使图形界面进入事件循环,从而能 够处理用户在界面上的各种交互事件(如在录入框中输入数据、点击单选钮选择货币、点击 换算按钮等)。当用户点击换算按钮,相应的处理程序 toRMB 和 toFC 在设置有关信息后必 须用 self.root.quit 方法来退出事件循环,从而使 getInfo 方法结束并将控制返回核心部分。
下面我们利用此图形用户界面来实现图形版的汇率换算器。和前面测试文本界面一样, 在主程序中先创建一个图形界面对象,再创建 CCApp 对象,然后启动换算器。程序如下:
【程序 8.12 之五】testgui.py
```
from ccapp import CCApp
from gui import GUInterface
inter = GUInterface()
cc = CCApp(inter)
cc.run()
```
执行此程序,在图形界面中选择 Euro,并在 RMB 录入框中输入 10000,最后点击“<----” 按钮,得到的结果如图 8.27 所示:
![](https://box.kancloud.cn/2016-02-22_56cafce699034.png)
图 8.27 图形版汇率换算器
从图 8.27 还可看到,我们的图形界面中还有一个前面未提到的按钮 Update Rates,这是 用来更新汇率数据的,但本程序中没有为此按钮编写处理程序,作为练习,读者可以自己试 着完善这个功能①。另外,支持换算的外币种类也很容易扩充。
至此我们完成了一个汇率换算器程序。通过这个程序的设计,我们看到,即使是如此简 单的程序,它的 GUI 设计也相当复杂。一般而言,图形界面由很多构件组成,创建构件并进 行布局是枯燥而繁琐的工作;而为构件编写相应的处理程序通常都比较简单直接。
- 前言
- 第 1 章 计算与计算思维
- 1.1 什么是计算?
- 1.1.1 计算机与计算
- 1.1.2 计算机语言
- 1.1.3 算法
- 1.1.4 实现
- 1.2 什么是计算思维?
- 1.2.1 计算思维的基本原则
- 1.2.2 计算思维的具体例子
- 1.2.3 日常生活中的计算思维
- 1.2.4 计算思维对其他学科的影响
- 1.3 初识 Python
- 1.3.1 Python 简介
- 1.3.2 第一个程序
- 1.3.3 程序的执行方式
- 1.3.4 Python 语言的基本成分
- 1.4 程序排错
- 1.5 练习
- 第 2 章 用数据表示现实世界
- 2.1 数据和数据类型
- 2.1.1 数据是对现实的抽象
- 2.1.1 常量与变量
- 2.1.2 数据类型
- 2.1.3 Python 的动态类型*
- 2.2 数值类型
- 2.2.1 整数类型 int
- 2.2.2 长整数类型 long
- 2.2.3 浮点数类型 float
- 2.2.4 数学库模块 math
- 2.2.5 复数类型 complex*
- 2.3 字符串类型 str
- 2.3.1 字符串类型的字面值形式
- 2.3.2 字符串类型的操作
- 2.3.3 字符的机内表示
- 2.3.4 字符串类型与其他类型的转换
- 2.3.5 字符串库 string
- 2.4 布尔类型 bool
- 2.4.1 关系运算
- 2.4.2 逻辑运算
- 2.4.3 布尔代数运算定律*
- 2.4.4 Python 中真假的表示与计算*
- 2.5 列表和元组类型
- 2.5.1 列表类型 list
- 2.5.2 元组类型 tuple
- 2.6 数据的输入和输出
- 2.6.1 数据的输入
- 2.6.2 数据的输出
- 2.6.3 格式化输出
- 2.7 编程案例:查找问题
- 2.8 练习
- 第 3 章 数据处理的流程控制
- 3.1 顺序控制结构
- 3.2 分支控制结构
- 3.2.1 单分支结构
- 3.2.2 两路分支结构
- 3.2.3 多路分支结构
- 3.3 异常处理
- 3.3.1 传统的错误检测方法
- 3.3.2 传统错误检测方法的缺点
- 3.3.3 异常处理机制
- 3.4 循环控制结构
- 3.4.1 for 循环
- 3.4.2 while 循环
- 3.4.3 循环的非正常中断
- 3.4.4 嵌套循环
- 3.5 结构化程序设计
- 3.5.1 程序开发过程
- 3.5.2 结构化程序设计的基本内容
- 3.6 编程案例:如何求 n 个数据的最大值?
- 3.6.1 几种解题策略
- 3.6.2 经验总结
- 3.7 Python 布尔表达式用作控制结构*
- 3.8 练习
- 第 4 章 模块化编程
- 4.1 模块化编程基本概念
- 4.1.1 模块化设计概述
- 4.1.2 模块化编程
- 4.1.3 编程语言对模块化编程的支持
- 4.2 Python 语言中的函数
- 4.2.1 用函数减少重复代码 首先看一个简单的用字符画一棵树的程序:
- 4.2.2 用函数改善程序结构
- 4.2.3 用函数增强程序的通用性
- 4.2.4 小结:函数的定义与调用
- 4.2.5 变量的作用域
- 4.2.6 函数的返回值
- 4.3 自顶向下设计
- 4.3.1 顶层设计
- 4.3.2 第二层设计
- 4.3.3 第三层设计
- 4.3.4 第四层设计
- 4.3.5 自底向上实现与单元测试
- 4.3.6 开发过程小结
- 4.4 Python 模块*
- 4.4.1 模块的创建和使用
- 4.4.2 Python 程序架构
- 4.4.3 标准库模块
- 4.4.4 模块的有条件执行
- 4.5 练习
- 第 5 章 图形编程
- 5.1 概述
- 5.1.1 计算可视化
- 5.1.2 图形是复杂数据
- 5.1.3 用对象表示复杂数据
- 5.2 Tkinter 图形编程
- 5.2.1 导入模块及创建根窗口
- 5.2.2 创建画布
- 5.2.3 在画布上绘图
- 5.2.4 图形的事件处理
- 5.3 编程案例
- 5.3.1 统计图表
- 5.3.2 计算机动画
- 5.4 软件的层次化设计:一个案例
- 5.4.1 层次化体系结构
- 5.4.2 案例:图形库 graphics
- 5.4.3 graphics 与面向对象
- 5.5 练习
- 第 6 章 大量数据的表示和处理
- 6.1 概述
- 6.2 有序的数据集合体
- 6.2.1 字符串
- 6.2.2 列表
- 6.2.3 元组
- 6.3 无序的数据集合体
- 6.3.1 集合
- 6.3.2 字典
- 6.4 文件
- 6.4.1 文件的基本概念
- 6.4.2 文件操作
- 6.4.3 编程案例:文本文件分析
- 6.4.4 缓冲
- 6.4.5 二进制文件与随机存取*
- 6.5 几种高级数据结构*
- 6.5.1 链表
- 6.5.2 堆栈
- 6.5.3 队列
- 6.6 练习
- 第 7 章 面向对象思想与编程
- 7.1 数据与操作:两种观点
- 7.1.1 面向过程观点
- 7.1.2 面向对象观点
- 7.1.3 类是类型概念的发展
- 7.2 面向对象编程
- 7.2.1 类的定义
- 7.2.2 对象的创建
- 7.2.3 对象方法的调用
- 7.2.4 编程实例:模拟炮弹飞行
- 7.2.5 类与模块化
- 7.2.6 对象的集合体
- 7.3 超类与子类*
- 7.3.1 继承
- 7.3.2 覆写
- 7.3.3 多态性
- 7.4 面向对象设计*
- 7.5 练习
- 第 8 章 图形用户界面
- 8.1 图形用户界面概述
- 8.1.1 程序的用户界面
- 8.1.2 图形界面的组成
- 8.1.3 事件驱动
- 8.2 GUI 编程
- 8.2.1 UI 编程概述
- 8.2.2 初识 Tkinter
- 8.2.3 常见 GUI 构件的用法
- 8.2.4 布局
- 8.2.5 对话框*
- 8.3 Tkinter 事件驱动编程
- 8.3.1 事件和事件对象
- 8.3.2 事件处理
- 8.4 模型-视图设计方法
- 8.4.1 将 GUI 应用程序封装成对象
- 8.4.2 模型与视图
- 8.4.3 编程案例:汇率换算器
- 8.5 练习
- 第 9 章 模拟与并发
- 9.1 模拟
- 9.1.1 计算机建模
- 9.1.2 随机问题的建模与模拟
- 9.1.3 编程案例:乒乓球比赛模拟
- 9.2 原型法
- 9.3 并行计算*
- 9.3.1 串行、并发与并行
- 9.3.2 进程与线程
- 9.3.3 多线程编程的应用
- 9.3.4 Python 多线程编程
- 9.3.5 小结
- 9.4 练习
- 第 10 章 算法设计和分析
- 10.1 枚举法
- 10.2 递归
- 10.3 分治法
- 10.4 贪心法
- 10.5 算法分析
- 10.5.1 算法复杂度
- 10.5.2 算法分析实例
- 10.6 不可计算的问题
- 10.7 练习
- 第 11 章 计算+X
- 11.1 计算数学
- 11.2 生物信息学
- 11.3 计算物理学
- 11.4 计算化学
- 11.5 计算经济学
- 11.6 练习
- 附录
- 1 Python 异常处理参考
- 2 Tkinter 画布方法
- 3 Tkinter 编程参考
- 3.1 构件属性值的设置
- 3.2 构件的标准属性
- 3.3 各种构件的属性
- 3.4 对话框
- 3.5 事件
- 参考文献