8.4.3 编程案例:汇率换算器

最后更新于:2022-04-02 00:31:13

### 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://docs.gechiui.com/gc-content/uploads/sites/kancloud/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://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56cafce699034.png) 图 8.27 图形版汇率换算器 从图 8.27 还可看到,我们的图形界面中还有一个前面未提到的按钮 Update Rates,这是 用来更新汇率数据的,但本程序中没有为此按钮编写处理程序,作为练习,读者可以自己试 着完善这个功能①。另外,支持换算的外币种类也很容易扩充。 至此我们完成了一个汇率换算器程序。通过这个程序的设计,我们看到,即使是如此简 单的程序,它的 GUI 设计也相当复杂。一般而言,图形界面由很多构件组成,创建构件并进 行布局是枯燥而繁琐的工作;而为构件编写相应的处理程序通常都比较简单直接。
';