《驯服烂代码:在编程操练中悟道》一第3章 写main()方法测试一下

简介:

本节书摘来自华章出版社《驯服烂代码:在编程操练中悟道》一书中的第3章,作者 伍斌,更多章节内容可以访问云栖社区“华章计算机”公众号查看

第3章 写main()方法测试一下

“类图上的所有类都实现完了。咱们现在可以写个main()方法来测试一下了。”
先创建一个包含main()方法的类HotelWorldClocksRunner。
HotelWorldClocksRunner类的代码如下所示(CM: Added class HotelWorldClocksRunner with a main() method to have a try.):

+public class HotelWorldClocksRunner { 
+}

然后在这个类里面写main()方法。
main()方法的代码如下所示(CM: Added the main() method to class HotelWorldClocksRunner and wrote the expected code there.):

public class HotelWorldClocksRunner { 
+    public static void main(String[] args) { 
+        TimeSubject utcTime = new UtcTime(); 
+        utcTime.attach("beijing", new CityClock(8)); 
+        utcTime.attach("london", new CityClock(0)); 
+        utcTime.attach("moscow", new CityClock(4)); 
+        utcTime.attach("sydney", new CityClock(10)); 
+        utcTime.attach("newYork", new CityClock(-5)); 
+        Clock phoneClock = new PhoneClock(utcTime); 
+ 
+        phoneClock.setLocalTime(9); 
+ 
+        utcTime.printTimeOfAllClocks(); 
+    } 
 }

这段代码分3个部分,第1部分是做准备工作;第2部分是调用手机时钟的setLocalTime()方法来设定时间为北京时间上午9点,以触发所有城市时钟的自动调整;第3部分是打印所有时钟的本地时间。
在第1部分中,我们先创建一个具有TimeSubject类型的UtcTime实例;再把5个城市时钟的实例都分别attach到这个UtcTime实例上,每创建一个城市时钟实例,都把该城市与UTC时间的时差作为构造器的参数传进这个新创建的实例中。比如北京比UTC时间早8小时,所以在attach北京时钟时,用new CityClock(8)来创建北京时钟实例。最后创建手机时钟实例phoneClock,并把上面准备好的UtcTime实例作为构造器的参数传进去,以便在PhoneClock类的setLocalTime()方法中,调用UtcTime类的setUtcZeroTime()方法,来自动调整所有城市时钟的时间。
现在咱们先创建CityClock类的带有时差参数的构造器。
“等等!我觉得带有utcTime参数的创建PhoneClock的实例那句话写得有问题。因为PhoneClock和CityClock都继承同一个父类Clock,为何创建CityClock实例时要提供时差参数,而创建PhoneClock实例时却没有提供时差参数?难道PhoneClock的实例都不需要时差参数吗?”
问得好!PhoneClock的实例在创建时,确实也和创建CityClock实例时一样,需要传入当地时间与UTC时间之间的时差。需要改一改这个main()方法。
修改main()方法中创建PhoneClock实例的代码如下所示(CM: Updated the main() method to make the constructor of PhoneClock is the same with CityClock and add the UtcTime object to the phoneClock using method PhoneClock.setUtcTime().):

utcTime.attach("moscow", new CityClock(4)); 
         utcTime.attach("sydney", new CityClock(10)); 
         utcTime.attach("newYork", new CityClock(-5)); 
-        Clock phoneClock = new PhoneClock(utcTime); 
+        Clock phoneClock = new PhoneClock(8); 
+        phoneClock.setUtcTime(utcTime); 
 
         phoneClock.setLocalTime(9);

在创建PhoneClock实例时,把北京时间距离UTC时间的时差8作为构造器的参数传进去,然后在PhoneClock类上增加一个接口setUtcTime()方法,来把utcTime传给phoneClock实例。
“这样应该没问题了。再更改一下类图,在图中把这个更改标记为7号。”
在细化后的类图中对7号的更改如图3-1所示。

image

这个main()方法有不少编译错误,大概有4处。
HotelWorldClocksRunner类的main()方法的4处编译错误如图3-2所示。

image

现在从上往下看看这4个编译错误。第1个编译错误是CityClock类还没有一个接受该城市与UTC时间的时差的构造器;第2个编译错误是PhoneClock类也没有接受一个其所在城市与UTC时间的时差的构造器;第3个编译错误是PhoneClock类还没有创建setUtcTime()方法;第4个编译错误是UtcTime类还没有创建printTimeOfAllClocks()方法。
接着用Alt+Enter快捷键来让IDEA帮助咱们创建这些默认的构造器和方法。
现在先消除第1个编译错误,创建CityClock类的带有时差参数的构造器。
创建CityClock类的带有时差参数的构造器的代码如下所示(CM: Added constructor CityClock(int utcOffset).):

public class CityClock extends Clock { 
+    public CityClock(int utcOffset) { 
+        super(); 
+    } 
+ 
     @Override 
     public void setLocalTime(int localTime) { 
在CityClock类的带有时差参数的构造器里,时差参数
u```  
tcOffset应该作为参数传进super()方法里,并在父类Clock里也添加一个带时差参数的构造器来接收这个参数。
在CityClock类中将时差参数utcOffset作为参数传进super()方法的代码如下所示(CM: Added constructor Clock(int utcOffset).):

public class CityClock extends Clock {

 public CityClock(int utcOffset) { 
  • super();
  • super(utcOffset);

     }
    
在父类Clock里添加一个带时差参数的构造器的代码如下所示(CM同上):

public abstract class Clock {

  • protected static final int UTC_OFFSET = 0;
  • protected static int UTC_OFFSET;
    protected int localTime = 0;
  • public Clock(int utcOffset) {
  • UTC_OFFSET = utcOffset;
  • }
  • public abstract void setLocalTime(int localTime);
因为Clock类的成员变量UTC_OFFSET需要在其构造器里赋值,所以就不能是final的了。
再消除第2个编译错误,创建PhoneClock类的带有时差参数的构造器。
创建PhoneClock类的带有时差参数的构造器的代码如下所示(CM: Created constructor PhoneClock(int utcOffset).):

public class PhoneClock extends Clock {

 private UtcTime utcTime; 
  • public PhoneClock(int utcOffset) {
  • super(utcOffset);
  • }
    +
现在消除第3个编译错误,创建PhoneClock类的setUtcTime()方法。不过在main()方法中,变量phoneClock被声明为Clock类型了。如果是这样的话,setUtcTime()方法应该在父类Clock中创建,而成为一个公共接口,这使得Clock类的另一个子类CityClock也不得不实现这个它并不需要的接口。这就不大合理了。所以可以在main()方法中,把变量phoneClock声明为PhoneClock类型,这样setUtcTime()方法就只在PhoneClock类上创建了。
在main()方法中,把变量phoneClock声明为PhoneClock类型的代码如下所示(CM: Changed type of varialbe phoneClock in main() to be PhoneClock.):

utcTime.attach("newYork", new CityClock(-5));

  • Clock phoneClock = new PhoneClock(8);
  • PhoneClock phoneClock = new PhoneClock(8);

         phoneClock.setUtcTime(utcTime);
    
“main()方法里还有一个问题,就是变量utcTime的类型应该是UtcTime,而不应该是其父类TimeSubject。因为根据类图来看,PhoneClock类持有一个UtcTime的实例,以便调用后者的setUtcZeroTime()方法。而这个方法只在UtcTime类中定义了,其父类TimeSubject并没有定义。所以为了调用UtcTime类中的setUtcZeroTime()方法,main()方法里的变量utcTime的类型应该是UtcTime。”
好的,这就改过来。
在main()方法中,把变量utcTime声明为UtcTime类型的代码如下所示(CM: Changed type of varialbe utcTime in main() to be UtcTime.):

public class HotelWorldClocksRunner {

 public static void main(String[] args) { 
  • TimeSubject utcTime = new UtcTime();
  • UtcTime utcTime = new UtcTime();

         utcTime.attach("beijing", new CityClock(8));
    
现在可以创建PhoneClock类的setUtcTime()方法来消除第3个编译错误了。
在PhoneClock类中创建setUtcTime()方法的代码如下所示(CM: Created method Phone-Clock.setUtcTime(UtcTime).):

super.localTime = localTime;

     this.utcTime.setUtcZeroTime(localTime - UTC_OFFSET); 
 } 
  • public void setUtcTime(UtcTime utcTime) {
  • this.utcTime = utcTime;
  • }
    }
现在可以创建UtcTime类的printTimeOfAllClocks()方法来消除第4个编译错误了。这个方法专门是为测试用的,所以就不在类图中画出来了。
创建UtcTime类的printTimeOfAllClocks()方法的代码如下所示(CM: Created method UtcTime.printTimeOfAllClocks().):

clock.setLocalTime(Clock.toLocalTime(this.utcZeroTime));

     } 
 } 
  • public void printTimeOfAllClocks() {
  • for (String clockName : super.clocks.keySet()) {
  • System.out.println(clockName + ": " + super.clocks.get(clockName).getTime());
  • }
  • }
    }
因为utcTime能从其父类TimeSubject里继承Map类型的成员变量clocks,所以就能在printTimeOfAllClocks()方法里写一个循环语句,打印所有的时钟名称和时间。
现在就差创建Clock类的getTime()方法了。
创建Clock类的getTime()方法的代码如下所示(CM: Created method Clock.getTime().):

public static int toLocalTime(int utcZeroTime) {

     return utcZeroTime + UTC_OFFSET; 
 } 
  • public String getTime() {
  • return String.valueOf(this.localTime);
  • }
    }
看起来现在终于可以运行一下这个main()方法了。在IDEA里打开那个main()方法,把光标定位到类名上,然后按Ctrl+Shift+F10组合键运行一下。结果出来了,奇怪,所有的城市的时间都是9点。
第一次运行main()方法的结果如图3-3所示。
“现在的问题是,除了北京以外,其他所有城市的当地时间都是错的,而且都是9。加个断点调试一下吧。“
在调试前,咱们先看看这章做了什么工作:
1)开始编写main()方法,并通过打印语句,测试了一下先前按照细化后的类图所编写的代码。
2)在创建PhoneClock的实例时发现并修复了构造器的参数问题。
3)先在main()方法中编写调用了暂时不存在但期望存在的接口的代码(即意图代码),然后循着这些意图代码的红色编译错误,来编写生产代码以消除这些错误。
4)修改细化后的类图以反映设计的修改。

![image](https://yqfile.alicdn.com/edc16bce0cdc59264450a73d9a69504588e1bb33.png)
相关文章
|
25天前
|
Java 关系型数据库 数据库连接
Mybatis+MySQL动态分页查询数据经典案例(含代码以及测试)
Mybatis+MySQL动态分页查询数据经典案例(含代码以及测试)
24 1
|
1月前
|
安全 测试技术
BOSHIDA DC电源模块的安全性能评估与测试方法
BOSHIDA DC电源模块的安全性能评估与测试方法
 BOSHIDA DC电源模块的安全性能评估与测试方法
|
1月前
|
安全
DC电源模块的安全性能评估与测试方法
DC电源模块的安全性能评估与测试方法 DC电源模块的安全性能评估与测试方法应包括以下几个方面: 1. 输入安全性测试:包括输入电压范围、输入电压稳定性、输入电流范围、输入电流保护等方面的测试。测试方法可以是逐步增加输入电压或输入电流,观察模块的工作状态和保护功能。
DC电源模块的安全性能评估与测试方法
|
29天前
|
Java Spring
使用JDBCTemplate实现与Spring结合,方法公用 ——测试(EmpDaoImplTest)
使用JDBCTemplate实现与Spring结合,方法公用 ——测试(EmpDaoImplTest)
8 0
|
9天前
|
自然语言处理 测试技术 持续交付
现代软件测试方法与挑战
传统软件测试方法在当前快速发展的软件开发环境下面临着诸多挑战,因此,现代软件测试方法的探索与应用显得尤为重要。本文将介绍几种现代软件测试方法,并探讨其在应对软件开发挑战方面的作用。
10 0
|
24天前
|
传感器 监控 算法
【软件设计师备考 专题 】模块测试的方法和实践
【软件设计师备考 专题 】模块测试的方法和实践
68 0
|
24天前
|
安全 测试技术
【软件设计师备考 专题 】软件测试的原则与方法:确保软件质量的关键步骤
【软件设计师备考 专题 】软件测试的原则与方法:确保软件质量的关键步骤
41 0
|
27天前
|
Java
java面向对象高级分层实例_测试类(main方法所在的类)
java面向对象高级分层实例_测试类(main方法所在的类)
9 1
|
1月前
|
安全 测试技术 API
请描述在 Python WEB 开发中常用的测试方法。
请描述在 Python WEB 开发中常用的测试方法。
16 0
|
1月前
|
敏捷开发 数据管理 测试技术
深入探索软件测试:方法、挑战与最佳实践
【2月更文挑战第20天】 在数字化时代,软件已成为不可或缺的核心。确保软件的质量和可靠性,软件测试扮演着至关重要的角色。本文将深入探讨软件测试的各种方法,面临的挑战以及实施的最佳实践。我们将从单元测试的基础出发,逐步过渡到集成和系统测试,最后讨论自动化测试的策略。文章旨在为软件测试工程师提供深度洞见,帮助他们更有效地设计和执行测试计划,以确保软件产品的质量。