`
airu
  • 浏览: 270793 次
  • 性别: Icon_minigender_1
  • 来自: 云南
社区版块
存档分类
最新评论

TDD 学习笔记(二)

 
阅读更多
上一次讲到了私有化amount,并且,重写了equals方法,对于hashCode方法(假设返回0,这样退化到线性查找,但是暂且不影响我们的正确性),我们暂且放下。我们现在还是看看to-do lists


  • 5美元 * 2 = 10 美元
  • 5美元 等于 5美元
  • 5美元不等于6美元
  • 私有化amount
  • 5法郎 * 2= 10 法郎
  • 5法郎 等于 5法郎
  • 5法郎 不等于 5法郎
  • 5美元 不等于 5法郎

上面是引入新的货币,法郎,我们发现其实和美元很相像。于是,我们创建和美元一样的测试,然后把美元的代码copy过来,修改成法郎。
上面的动作,或者就是大家工作中的真实写照了吧。不过不要着急,我们的测试驱动开发正是要解决这种不可容忍的重复代码的。
测试:

@Test public void testTimes(){  
   Dollar five = new Dollar(5); 
   Franc ten = new Franc(10); 
   assertEquals(five.times(2),new Dollar(10));  
   assertEquals(five.times(3),new Dollar(15));  
   assertequals(ten.times(2),new Franc(20));
   assertequals(ten.times(3),new Franc(30));
}

@Test public void testEqualitiy(){
    assertTrue((new Dollar(5)).equals((new Dollar(5))));
    assertFalse((new Dollar(5)).equals((new Dollar(7))));
    assertTrue((new Franc(5)).equals((new Franc(5))));
    assertFalse((new Franc(5)).equals((new Franc(7))));
    assertFalse((new Dollar(5)).equals((new Franc(5))));
} 

注意,测试中,新加入一个测试,那就是 5美元不等于7美元。
下面是新的Franc类,是不是和美元类一个模子。
public class Franc{
private amount;
public Franc(int amount){
this.amount=amount;
}
public Franc times(int multipler){
   return new Franc(amount*multipler);
}
/*
 *I recommand you add @Override if you can't
 *remember the method clearly 
 */
@Override
public boolean equals(Object obj){
 return this.amount==((Franc)obj).amount;
}
}


面对这个问题,不知道你怎么想。但是这里,我们开始OO编程了。OO的目的不就是消除一些不必要的重复吗?既然这两个类那么相同,那么他们是否应该又一个父类呢。
于是我们引入了Money类。
Money类的引入,并不能马上消除重复。这里需要想办法做些改变,只有两个一摸一样的方法 ,我们才可以把他们放到父类中。
这里,显而易见的便是amount。

public class Money{
protected int amount;
}

然后Dollar和Franc都继承Money。我们接下来看到,equals方法很是最相像的。于是我们做些小变化。
对于Dollar类
public boolean equals(Object obj){
 Money money = (Money)obj;
 return amount == money.amount;
}

这里我又多走了几步。首先,把 (Dollar)obj变为 (Money)obj 这一步,运用Liskov替换原则,子类可以转换为父类。当然,声明也可以用父类了。这样,对Franc类的操作一样,我们就可以看到,两个equals方法完全一样,然后上移到Money中。
public class Money{
protected int amount;
public boolean equals(Object obj){
 Money money = (Money)obj;
 return amount == money.amount;
}
}

一切似乎很顺利,我们运行测试。啊,竟然没有通过,5美元竟然等于5法郎了。
原来我们的equals函数还有问题。既然是美元和法郎,就应该有不同,都转换成Money了,那么就没有区别了。这里,我们一眼看不出来应该怎么办,那么就使用triangulation吧。
public class Money{
protected int amount;
public boolean equals(Object obj){
 Money money = (Money)obj;
 return amount == money.amount &&
        this.getClass() == obj.getClass();
}
}

书上说,这种比较很不好,因为不能依赖于java语言中的对象比较,而是要真正弄清法郎和美元到底是什么不同,从业务上说明。但是这就是一个stub吧。我们还要继续测试。
测试通过了。
这时,测试清单上的测试完了。但是我们还需要进行重构,消除重复。
Franc和Dollar中的times方法进入我们的视线。他们太像了。
首先可以一样看出来的,就是,我们可以让他们都返回Money对象。
Dollar 类
public Money times(int multiplier){
      return new Dollar(amount*multiplier);
}


Franc 类
public Money times(int multiplier){
      return new Franc(amount*multiplier);
}


似乎就到这里,我们很难在改动了。难道就这样了?
但是这两个类实在是让我们觉得很不爽,因为他们两个的逻辑太一样了。
这里我也很迷茫,kent大叔说,可以引入一个工厂模式,既然我们不好区分这两个类,那么使用工厂模式来区分,这样,就可以看到他们需要如何区别自己了。

有了这个想法,测试程序可以改动了。
@Test public void testTimes(){  
   Money five = Money.dollar(5); 
   Franc ten = new Franc(10); 
   assertEquals(five.times(2),Money.dollar(10));  
   assertEquals(five.times(3),Money.dollar(15));  
   assertequals(ten.times(2),Money.franc(20));
   assertequals(ten.times(3),Money.franc(30));
}

然后改动Money类。这里我们不知道Money的times方法啊,别急,那就把Money改为abstract类吧。
public abstract class Money{
protected int amount;
public boolean equals(Object obj){
 Money money = (Money)obj;
 return amount == money.amount &&
        this.getClass() == obj.getClass();
}
public static dollar(int amount){
    return new Dollar(amount);
}

public static franc(int amount){
    return new Franc(amount);
}
public abstract Money times(int multipler);
}

测试通过了。这时,我们看Dollar和Franc其实就是两个的名字不一样。这里,我差点忘记了,我们的to-do lists里应该再加上
  • 消除Dollar与Franc类

这样,我们可以引入一个区分货币的东西了。越简洁约好,我们可能使用一个常量,或者一个字符串。作者使用了一个字符串来区分。美元就是”USD”,法郎就是"CHF"。这比较符合常理,我们都说,人民币100,或者美元100,区分就在于前面这个字符串。于是可以写测试了。

@Test public void testCurrency(){
    assertEquals("USD",Money.dollar(1).currency());
    assertequals("CHF",Money.franc(1).currency());
}


为此,我们可以看出,只要添加一个成员函数,currency()即可。
Money
public abstract String currency();


Dollar
public String currency(){return "USD";}


Franc
public String currency(){return "CHF";}


测试通过。我们开始消除重复,这里Dollar和Franc的currency方法可否统一呢?
引入 currency变量
Money
protected String currency;


Dollar
public Dollar(int amount){
  this.amount = amount;
  currency = "USD";
}
public String currency(return currency;}


Franc
public Franc(int amount){
  this.amount = amount;
  currency = "CHF";
}
public String currency(return currency;}


这下一样了,于是我们把这个方法放入Money中
这时,我们也看到,Dollar和Franc的构造函数很像了。采用同样的方法,我们改造构造函数。

Dollar
public Dollar(int amount, String currency){
    this.amount = amount;
    this.currency = currency;
}


Franc也一样,我们把他们提到Money中,这时,我们会发现,Dollar类,和Franc类基本一摸一样了。除了times方法。
Money
public Money(int amount, String currency){
   this.amount = amount;
   this.currency = currency;
}


Dollar
public Dollar(int amount, String currency){
    super(amount,currency);
}


Franc
public Franc(int amount,String currency(){
    super(amount,currency);
}


好了,又减少了一个方法。但是我们还是不能完全消除Dollar类和Franc类。因为times方法。
现在就来看一看times方法。

Dollar 类
public Money times(int multiplier){
      return new Dollar(amount*multiplier,null);
}


Franc 类
public Money times(int multiplier){
      return new Franc(amount*multiplier,null);
}


这里的方法,我们还需要一个参数,暂时使用null替代。

这下我们看到了new,然后考虑是否使用工厂模式来创建呢?

Dollar 类
public Money times(int multiplier){
      return Money.dollar(amount*multiplier);
}


Franc 类
public Money times(int multiplier){
      return Money.franc(amount*multiplier);
}


这里还是无济于事。那么在打回,并且实现,而不是使用null替代。
Dollar 类
public Money times(int multiplier){
      return new Dollar(amount*multiplier,“USD”);
}


Franc 类
public Money times(int multiplier){
      return new Franc(amount*multiplier,“CHF”);
}

我们使用了常量来做,这个已经和Money中的工厂方法很像了。一个Money,也有自己的currency,那么如果我们返回的是Money呢?
是否可以这样
Dollar 类
public Money times(int multiplier){
      return new Money(amount*multiplier,"USD");
}


Franc 类
public Money times(int multiplier){
      return new Money(amount*multiplier,"CHF");
}

看起来快要成功了。我们忘了还有个currency,这就是这个Money的币类了。
换上
Dollar 类
public Money times(int multiplier){
      return new Money(amount*multiplier,currency);
}


Franc 类
public Money times(int multiplier){
      return new Money(amount*multiplier,currency);
}

这下完全一样了。说实话,这一步,真的很不容易。因为我们一样看不出来,并且也很难想到吧。但是我们确实可以删除Dollar和Franc了。因为他们已经没有方法了。
一切顺利,但是还有错误,你不能返回一个抽象类的实例吧。那就把Money改为非abstract吧。同时Money的times也变成一个实际方法了。

测试,失败了。这里我们疑惑了,为什么测试不过呢?看起来很诡异。
我觉得测试如果失败,并且一时看不出来,可能是类的信息,那么我们就需要使用以下toString了
重写toString方法,打印我们看得懂的

Money 类
public String toString(){
    return amount + " " + currency;
}

好了,这下看懂了。10 USD 期望的是 10 USD
这不一样嘛?
其实再看,就发现,原来是类不一致。这就是equals方法潜伏的问题。
我们这下可以按照业务需求去实现比较了

public boolean equals(Object obj){
    Money money = (Money)obj;
    return amount == money.amount &&
           currency.equals(money.currency());
}

这下心里好受了。但是这里还没完,我们应该用不到Dollar和Franc类了啊。
原来是工厂方法还在用,马山改掉。
修改Money的工厂方法

Money 类
public static Money dollar(int amount){
    return new Money(amount,"USD");
}
public static Money franc(int amount){
    return new Money(amount,"CHF");
}

这下测试,通过。我们删掉Dollar和Franc类,在看看有没有什么地方引用到这两个类。
没有。很好。终于完成了这一步。接下来,我们的to-do lists又该有什么了呢?
既然有了两种货币,就该比较,并且混合计算了。
分享到:
评论

相关推荐

    编程学习笔记

    《编程学习笔记》 在编程领域,学习是一个永无止境的过程。这份“编程学习笔记”正是作者在学习过程中对已掌握知识的整理与总结,旨在帮助读者更好地理解和应用编程概念。笔记通常以简洁明了的方式呈现关键点,是...

    java校招学习笔记

    "java校招学习笔记"显然是针对应届毕业生或求职者准备的,旨在帮助他们掌握Java的基础知识和校招面试中常见的技术问题。这份笔记可能包含了从基础概念到进阶主题的全面概述,以提高求职者的竞争力。 首先,Java的...

    Study__TDD:个人TDD学习资料库

    埃迪的TDD学习笔记 :police_car_light: 警告不要合并任何PR。 :memo: 内容所有摘要都放在“问题”选项卡中。 当前,列出了以下内容: NHN FE개발랩摘要-Finsihed 견고한JS소프트웨어만들기-进行中测试Vue.js应用程序...

    达内微软NET学习笔记

    《达内微软NET学习笔记》是一份详实的.NET软件工程师培训资料,涵盖了.NET框架的基础知识、核心概念以及实际开发中的应用技巧。这份笔记源于作者在加拿大达内科技的培训经历,旨在与学习者共享珍贵的学习心得,由于...

    rationalrose2002学习笔记.rar_Rational Rose_rational _rose

    《Rational Rose 2002 学习笔记》是一份深入探讨软件建模工具Rational Rose 2002的教程资料。这份文档详细介绍了Rational Rose在系统分析、设计与实现中的应用,旨在帮助用户掌握这款强大的统一建模语言(UML)工具...

    java学习笔记4

    Java学习笔记第四部分主要涵盖了Java编程语言的深入学习内容,可能是继基础语法、面向对象编程之后的高级主题。"良葛格"作为编著者,可能以易懂且实用的方式阐述了这些概念。以下是根据标题和描述可能包含的知识点:...

    C#学习笔记精华,详细。

    本篇“C#学习笔记精华”旨在为初学者和进阶者提供一个全面且深入的学习指南,帮助读者掌握C#的核心概念和实用技巧。 1. **基础语法**:C#的基础包括变量、数据类型、常量、运算符、流程控制(如if语句、switch语句...

    C#学习笔记-C# Study Notes

    【C#学习笔记-C# Study Notes】 这是一份详尽的C#学习资源,旨在帮助初学者深入理解和掌握C#编程语言。这份笔记不仅包含了语言基础,还涵盖了从实际问题出发的学习过程,强调理解而非机械记忆,使得学习更加生动且...

    spring培训学习笔记

    ### Spring培训学习笔记知识点梳理 #### 一、Spring框架概览 - **启动时间与背景**:Spring项目始于2003年2月,最初的基础代码来源于书籍《Expert One-On-One J2EE Design and Development》。 - **核心特性**: -...

    python自动化测试开发框架学习笔记

    在"python自动化测试开发框架学习笔记"中,我们将深入探讨Python测试领域的核心概念和常用工具,特别是Allure报告框架。 Python作为一门流行的编程语言,因其简洁易读的语法和丰富的库支持,在自动化测试领域有着...

    Junit良葛格学习笔记

    "Junit良葛格学习笔记"很可能包含了一系列关于如何有效利用JUnit进行测试的教程和示例。下面将详细阐述JUnit的核心概念、功能以及在实际开发中的应用。 1. **JUnit简介**: JUnit是由Ernst Leiss和Kent Beck开发的...

    .NET 快速重构 - 学习笔记

    .NET 快速重构学习笔记是针对开发者提升代码质量和可维护性的重要指南。重构是一个系统性的过程,旨在改善软件设计,优化代码结构,而不改变其外在行为。在.NET开发环境中,重构是不可或缺的技能,它可以帮助开发者...

    面向对象学习笔记········

    本学习笔记将深入探讨面向对象的基本概念、原则以及常见设计模式。 1. **基本概念** - **对象**:对象是类的实例,具有属性(数据成员)和方法(函数成员)。对象是现实世界中的实体在程序中的抽象。 - **类**:...

    UML学习笔记 建模语言

    ### UML学习笔记:建模语言详解 #### 统一建模语言(UML)概述 统一建模语言(UML, Unified Modeling Language)是一种被广泛应用于软件开发领域的标准化建模语言,它为软件密集型系统的可视化建模提供了一种统一的...

    ssh_web开发测试程序学习笔记

    这个“ssh_web开发测试程序学习笔记”涵盖了使用这三个框架进行Web开发和测试的基本概念、步骤和最佳实践。 Struts2是MVC(Model-View-Controller)设计模式的一个实现,它提供了强大的表单处理、国际化、异常处理...

    appfuse 学习笔记

    AppFuse 是一个开源项目,旨在加速和简化J2EE应用程序的开发流程。由Matt Raible设计,它作为一...通过深入阅读和实践AppFuse的学习笔记,开发者可以逐步掌握其核心功能,并将其应用到实际项目中,提升开发质量和效率。

    python后端开发学习笔记,知识体系,技术栈 python Django mysql性能优化 redis 面向对象.zip

    这份学习笔记聚焦于Python后端开发的知识体系和技术栈,涵盖了Python基础、Django框架、MySQL数据库性能优化、Redis缓存以及面向对象编程等核心内容。 首先,Python是一种高级编程语言,以其简洁清晰的语法和丰富的...

    notes:学习笔记

    【标题】:“学习笔记” 【描述】:“学习笔记”通常是指个人在学习过程中记录的知识点、理解、思考和问题解答等内容的集合。这样的笔记可能涵盖了各种主题,包括但不限于编程语言、操作系统、网络技术、数据库管理...

Global site tag (gtag.js) - Google Analytics