花了一天时间,边看这个Money例子,边把这个java写的Money示例改成了Ruby,只是代码上加了注释,可以按书上的介绍来看。
总结一下:TDD方面,感觉确实是很好的开发方法。这种开发方法应该一直贯穿下去。
Ruby方面,途中对Ruby的多态(duck type)有了更深的了解。但是也碰到一些问题,没有列出来。解决了再说。
测试代码:
test/test_dollar.rb
CODE: $: .unshift File.join(File.dirname(__FILE__),"..","lib") require'test/unit' require'dollar' class TestDollar < Test::Unit::TestCase #测试数值对象的相等性。这里需要在dollar.rb里重载==方法。 #因为比较值大小,ruby和java不同,ruby重载的是==方法,而不是equals. #再添加一行测试,应用三角法。 def testEquality assert(Dollar.new(5) == (Dollar.new(5))) assert_equal(false,Dollar.new(5) == (Dollar.new(6))) end #第六步测试要重写testEquality方法 #第七步测试让美元对象和法郎对象进行比较,失败!修改==方法 #修改完,搞定第七步测试 def testEquality assert(Dollar.new(5) == (Dollar.new(5))) assert_equal(false,Dollar.new(5) == (Dollar.new(6))) assert(Franc.new(5) == (Franc.new(5))) assert_equal(false,Franc.new(5) == (Franc.new(6))) assert_equal(false,Franc.new(5) == (Dollar.new(5))) end #测试八,修改testEquality方法 def testEquality assert(Money.dollar(5) == (Money.dollar(5))) assert_equal(false,Money.dollar(5) == (Money.dollar(6))) assert(Money.franc(5) == (Money.franc(5))) assert_equal(false,Money.franc(5) == (Money.franc(6))) assert_equal(false,Money.franc(5) == (Money.dollar(5))) end #测试九 def testCurrency assert_equal("USD",Money.dollar(1).currency) assert_equal("CHF",Money.franc(1).currency) end #测试十二,加法 def testSimpleAddition five = Money.dollar(5) sum = five.plus(five) bank = Bank.new reduced = bank.reduce(sum,"USD") assert_equal(Money.dollar(10),reduced) end #测试十二 def testPlusRetrunSum five = Money.dollar(5) result = five.plus(five) sum = result assert_equal(five,sum.augend) assert_equal(five,sum.addend) end #测试十二 def testReduceSum sum = Sum.new(Money.dollar(3),Money.dollar(4)) bank = Bank.new result = bank.reduce(sum,"USD") assert_equal(Money.dollar(7),result) end #测试十三,当Money为参数时 def testReduceMoney bank = Bank.new result = bank.reduce(Money.dollar(1),"USD") assert_equal(Money.dollar(1),result) end #自己加的 def testHashEquals hash = Hash["from,to" => 1] assert_equal(1,hash["from,to"]) end #测试十四,拥抱变化,带换算的Reduce Money def testReduceMoneyDifferentCurrency bank = Bank.new bank.addRate("CHF","USD",2) result = bank.reduce(Money.franc(2),"USD") assert_equal(Money.dollar(1),result) end #每个Bank对象都不同。。。 #加了第二个测试用例 def testIdentityRate bank = Bank.new bank.addRate("CHF","USD",2) assert_equal(1,Bank.new.rate("USD","USD")) assert_equal(2,Bank.new.rate("CHF","USD")) end def testMixedAddition fiveBucks = Money.dollar(5) tenFrancs = Money.franc(10) bank = Bank.new bank.addRate("CHF","USD",2) result = bank.reduce(fiveBucks.plus(tenFrancs),"USD") assert_equal(Money.dollar(10),result) end =begin #测试十一,对子类有引用的testcase可取消 #测试乘法 def testMultiplication five = Dollar.new(5) five.times(2) assert_equal(10,five.amount) end #测试Dollar类的副作用,这里重写了第一个断言。 #当添加另一个测试five.times(3)的时候,失败了。 #这是因为,第一次测试的时候,已经把amount的直由5变成了10 #需要添加另一个对象 def testMultiplication five = Dollar.new(5) product = five.times(2) assert_equal(10,product.amount) product = five.times(3) assert_equal(15,product.amount) end =end =begin #测试十一,对子类有引用的testcase可取消 #重写第二个断言,让Dollar对象之间进行比较 #Ruby中的实例变量默认是私有的,所以私有性测试就不做了,第四步测试完 def testMultiplication five = Dollar.new(5) assert_equal(Dollar.new(10),five.times(2)) assert_equal(Dollar.new(15),five.times(3)) end #测试一下法郎是不是在哭泣,第五步测试 def testFrancMultiplication five = Franc.new(5) assert_equal(Franc.new(10),five.times(2)) assert_equal(Franc.new(15),five.times(3)) end =end =begin #测试八,修改testMultiplication方法和testFrancMultiplication方法。 #测试十一,对子类有引用的testcase可取消 def testMultiplication five = Money.dollar(5) assert_equal(Dollar.new(10,"USD"),five.times(2)) assert_equal(Dollar.new(15,"USD"),five.times(3)) end def testFrancMultiplication five = Money.franc(5) assert_equal(Franc.new(10,"CHF"),five.times(2)) assert_equal(Franc.new(15,"CHF"),five.times(3)) end =end =begin #测试十 #测试十一,对子类有引用的testcase可取消 def testDifferentClassEquality assert(Money.new(10,"CHF")==(Franc.new(10,"CHF"))) end =end end lib/dollar.rb CODE: #为了避免这种复制粘贴代码的恶性循环,我们用继承来解决这个问题。 #测试十一,消除了子类。 class Money #测试八,为了消除重复的times方法,增加两个Money的类方法。(工厂模式) #测试十一,将对子类的引用修改为对父类的引用,即,把原来的Dollar.new,Franc.new修改为Money.new #这样我们就可以删除掉子类了 def self.dollar(amount) @amount = amount return Money.new(@amount,"USD") end def self.franc(amount) @amount = amount return Money.new(@amount,"CHF") end attr_reader :amount,:currency def initialize(amount=nil,currency=nil) @amount = amount @currency = currency end #测试九 def currency return @currency end #第十步测试完毕 def times(multiplier) return Money.new(@amount * multiplier,currency) end #第12,加法 def plus(addend) return Sum.new(self,addend) end #为了消除类判定,Money中引进reduce方法 def reduce(bank,to) @rate = bank.rate(currency,to) return Money.new(@amount/@rate,to) end #第七步测试,要判断两个money对象是否相等,当且仅当它们的值和类均相同才行。 #即,苹果不能和桔子比较 #到测试十的时候,为了消除子类,我们引进了货币,这个时候比较的就不是类了,应该是货币。我们修改 def ==(obj) money = obj return @amount.eql?(money.amount) && (self.currency).eql?(money.currency) end end #在第十二步和十三步测试的时候,我们引入了Bank类和Sum类。 #Sum类的对象作为计算两个Money对象值的"钱包"对象 #测试十三到此结束,对duck type有了更深的理解 class Bank attr_reader :rates,:rate #用一个Hash对象来存储 汇率 @@rates = Hash.new def addRate(from,to,rate) @@rates["#{from},#{to}"]=rate end def reduce(source,to) # return source if source.class == Money return source.reduce(self,to) end #调用rate方法是查找汇率 def rate(from,to) # return from.eql?("CHF") && to.eql?("USD") ? 2 : 1 return 1 if from.eql?(to) @rate = @@rates["#{from},#{to}"] return @rate end end #第十五步测试 #Ruby到这一步就结束了。因为没有那个Expression接口 class Sum attr_reader :augend,:addend,:amount def reduce(bank,to) @amount = augend.reduce(bank,to).amount + addend.reduce(bank,to).amount return Money.new(amount,to) end def initialize(augend,addend) @augend = augend @addend = addend end end #测试十一,消除子类 #让Dollar继承自Money #class Dollar < Money =begin #第六步测试要把这个添加到了Money中 #改造构造函数,由initialize(amount)改成下面形式 #这个是我自己加的,可以通过无参数的构造函数来创建对象 #测试九,增加货币 #上移构造函数到Money,这里用super就行 def initialize(amount=nil,currency=nil) super(amount,currency) end =end =begin #消除重复设计,测试代码里有5和2,现在把5和2用变量代替 #既@amount = 5 * 2 替换成 @amount = @amount * multiplier #进一步重构,把@amount = @amount * multiplier 替换成 @amount *= multiplier #这样,我们的第一个测试,乘法测试到此完成。 def times(multiplier) @amount *= multiplier end =end =begin #为了消除Dollar的副作用,返回一个新的对象 #到此为止完成了第二个测试 #测试九修改了times方法,使用了工厂方法return Money.dollar(@amount * multiplier) #测试十为了消除子类的times方法,以退为进。和Franc类一样的修改,这样就可以把times方法上移到Money类中。 def times(multiplier) return Money.new(@amount * multiplier,currency) end =end =begin #测试相等性的时候,先直接return true。通过,然后再添加测试。 #通过三角法,一般化了==方法的代码。第三个测试完毕。 #第六步测试要把判等方法上移到Money中 def ==(obj) dollar = obj @amount.eql?(dollar.amount) end =end #end #法郎在哭泣?通过丑陋的copy代码的方法来止住法郎的哭泣 #class Franc < Money =begin attr_reader :amount def initialize(amount=nil,currency=nil) super(amount,currency) end =end =begin #测试十,修改Franc为Money,试验测试能否工作,不行,再恢复原貌 #在修改了==方法以后,我们又可以用Money def times(multiplier) return Money.new(@amount * multiplier,currency) end =end #end