JUnit源码分析(四)——从Decorator模式说起

其实我这系列小文,名为源码分析,其实是自己读《设计模式》的读书笔记。Decorator模式在java的IO库中得到应用,java的IO库看起来复杂,其实理解了Decorator模式再回头看可以很好理解并使用。
    Decorator模式,也就是装饰器模式,是对象结构型模式之一。

1.意图:动态地给一个对象添加一些额外的职责。给对象添加功能,我们首先想到的是继承,但是如果每增一个功能都需要继承,类的继承体系将无可避免地变的庞大和难以理解。面向对象设计的原则:优先使用组合,而非继承,继承的层次深度最好不过三。

2.适用场景:
1)在不影响其他对象的情况下,以动态、透明的方式给单个对象添加额外的责任
2)处理可以撤销的职责
3)为了避免类的数目爆炸,或者不能采用生成子类的方法进行扩展时

3.UML图和协作:

Component——定义一个对象接口,可以给这些对象动态地添加职责

ConcreteComponent——定义一个对象,可以给这个对象添加职责

Decorator——维持一个指向Component的引用,并定义一个与Component一致的接口,作为装饰类的父类

ConcreteDecorator——具体装饰类

4.效果:
1)与静态继承相比,Decorator可以动态添加职责,更为灵活
2)避免产生复杂的类,通过动态添加职责,而不是一次性提供一个万能的接口
3)缺点是将产生比较多的小对象,对学习上有难度,显然,java.io就是这个问题

我们以一个例子来实现Decorator模式,假设这样一个场景:在某个应用中需要打印票据,我们写了一个PrintTicket接口,然后提供一个实现类(DefaultPrintTicket)实现打印的功能:

package com.rubyeye.design_pattern.decorator;
//抽象component接口
public interface PrintTicket {
    public void print();
}

//默认实现类,打印票据
package com.rubyeye.design_pattern.decorator;

public class DefaultPrintTicket implements PrintTicket {

    public void print() {
        System.out.println("ticket body");
    }

}

OK,我们的功能已经实现,我们还体现了针对接口编程的原则,替换一个新的打印方式很灵活,但是客户开始提需求了——人生无法避免的三件事:交税、死亡和需求变更。客户要求打印页眉,你首先想到的是继承:

package com.rubyeye.design_pattern.decorator;

public class AnotherPrintTicket implements PrintTicket {

    public void print() {
        System.out.println("ticket header");
        System.out.println("ticket body");
    }

}

请注意,我们这里只是简单的示例,在实际项目中也许意味着添加一大段代码,并且需要修改打印票据本体的功能。需求接踵而至,客户要求添加打印页码,要求增加打印花纹,要求可以联打......你的类越来越庞大,直到你看见这个类都想吐的地步!-_-。让我们看看另一个方案,使用Decorator模式来动态地给打印增加一些功能,首先是实现一个Decorator,它需要保持一个到PrintTicket接口的引用:

package com.rubyeye.design_pattern.decorator;

public class PrintTicketDecorator implements PrintTicket {

    protected PrintTicket printTicket;

    public PrintTicketDecorator(PrintTicket printTicket) {
        this.printTicket = printTicket;
    }

    //默认调用PrintTicket的print
    public void print() {
        printTicket.print();
    }

}

然后,我们实现两个具体的装饰类——打印页眉和页脚:

package com.rubyeye.design_pattern.decorator;

public class HeaderPrintTicket extends PrintTicketDecorator {

    public HeaderPrintTicket(PrintTicket printTicket){
        super(printTicket);
    }
    
    public void print() {
        System.out.println("ticket header");
        super.print();
    }
}

package com.rubyeye.design_pattern.decorator;

public class FooterPrintTicket extends PrintTicketDecorator {
    public FooterPrintTicket(PrintTicket printTicket) {
        super(printTicket);
    }

    public void print() {
        super.print();
        System.out.println("ticket footer");
    }
}

    使用起来也很容易:
   

package com.rubyeye.design_pattern.decorator;

public class DecoratorTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        PrintTicket print=new HeaderPrintTicket(new FooterPrintTicket(new DefaultPrintTicket()));
        print.print();
    }

}

输出:
ticket header
ticket body
ticket footer

    了解了Decorator模式,我们联系了下JUnit里面的应用。作为一个测试框架,应该方便地支持二次开发,也许用户开发自己的TestCase,添加自定义的功能,比如执行重复测试、多线程测试等等。动态添加职责,而又不想使用静态继承,这正是Decorator使用的地方。在junit.extensions包中有一个TestDecorator,正是所有装饰类的父类,也是作为二次开发的基础,它实现了Test接口,而Test接口就是我们定义的抽象接口:

public class TestDecorator implements  Test {
    //保有一个指向Test的引用
        protected Test fTest;
       
    public TestDecorator(Test test) {
        fTest= test;
    }
        
        public void basicRun(TestResult result) {
          fTest.run(result);
        }
        public void run(TestResult result) {
           basicRun(result);
        }
        
}

Junit已经提供了两个装饰类:junit.extensions.ActiveTest用于处理多线程,junit.extensions.RepeatedTest用于执行重复测试,看看RepeatedTest是怎么实现的:

public class RepeatedTest extends  TestDecorator {
        //重复次数
    private int fTimesRepeat;

    public RepeatedTest(Test test, int repeat) {
        super(test);
        fTimesRepeat= repeat;
    }
        public void run(TestResult result) {
        //重复执行
                for (int i= 0; i < fTimesRepeat; i++) {
            if (result.shouldStop())
                break;
            super.run(result);
        }
    }
        
}

    RepeatedTest继承TestDecorator ,覆写run(TestReult result)方法,重复执行,super.run(result)将调用传入的TestCase的run(TestResult result)方法,这已经在TestDecorator默认实现。看看使用方式,使用装饰模式的好处不言而喻。

TestSuite suite = new TestSuite();
suite.addTest(new TestSetup(new RepeatedTest(new Testmath("testAdd"),12)));

文章转自庄周梦蝶  ,原文发布时间5.17

时间: 2024-10-14 20:58:53

JUnit源码分析(四)——从Decorator模式说起的相关文章

JUnit源码分析 (三)——Template Method模式

在JUnit执行测试时,我们经常需要初始化一些环境供测试代码使用,比如数据库连接.mock对象等等,这些初始化代码应当在每一个测试之前执行并在测试方法运行后清理.在JUnit里面就是相应的setUp和tearDown方法.如果没有这两个方法,那么我们要在每个测试方法的代码内写上一大堆重复的初始化和清理代码,这是多么愚蠢的做法.那么JUnit是怎么让setUp和tearDown在测试执行前后被调用的呢?     如果你查看下TestCase方法,你会发现TestCase和TestSuite的run

JUnit源码分析(三)

三.微观--执行流程与代码风格 来过一遍JUnit的执行流程吧,这样你就能对JUnit有个清晰的认识,虽然作为一个使用者这完全是不必要的.从<JUnit in Action>直接拿来一张JUnit流程图. 哦,也许你看晕了,我来当下导游好了.上面已经提到了TestRunner是BaseTestRunner的子类,在三个不同的ui包中各有一个TestRunner.这里我们仅以junit.textui包中的为例. TestRunner作为入口程序是怎么被启动的呢?习惯了使用容器的我们现在也许很少考

JUnit源码分析(二)

    在上面我们已经提到了junit.extentions包中的内容TestSetup.来看看整个包的结构吧. 先简要的介绍下包中各个类的功能.ActiveTestSuite对TestSuite进行了改进,使得每个test运行在一个单独的线程里面,并且只到所有的线程都结束了才会结束整个测试.ExceptionTestCase是对TestCase进行的改进,可以方便的判断测试类是否抛出了期望的异常.而剩下的三个类,大概你看的出来是使用了装饰模式来设计的.其中TestDecorator为具体装饰类

JUnit源码分析(二)——观察者模式

    我们知道JUnit支持不同的使用方式:swt.swing的UI方式,甚至控制台方式,那么对于这些不同的UI我们如何提供统一的接口供它们获取测试过程的信息(比如出现的异常信息,测试成功,测试失败的代码行数等等)?我们试想一下这个场景,当一个error或者exception产生的时候,测试能够马上通知这些UI客户端:发生错误了,发生了什么错误,错误是什么等等.显而易见,这是一个订阅-发布机制应用的场景,应当使用观察者模式.那么什么是观察者模式呢? 观察者模式(Observer) Observ

第二人生的源码分析(四十三)虚拟文件系统线程

由于第二人生是一个3D显示的软件,因此它就需要不断地从服务器下载大量数据,比如纹理图片,不同的角色是使用不同的纹理图片来实现不同的衣服外表的.当显示这些角色时,就使用从服务器下载的纹理图片.如果显示的人物角色比较多,比如有30个人时,这些纹理图片就需要保存到磁盘里.那么怎么样保存到磁盘里呢?保存到磁盘里就需要一个好的文件系统来保存,以及读取数据出来.读写磁盘是一项比较慢的工作,因此需要使用一个线程来实现.还有时读写文件并不需要及时性的动作,可以让线程等到CPU空闲时再去做这些事情.   LLVF

第二人生的源码分析(四十四)虚拟文件系统的请求处理

在虚拟文件系统的消息队列里,主要就是LLVFSThread::Request类的请求,Request类是嵌套类,定义在LLVFSThread类里面.它主要实现对类LLVFS的封装访问,让操作更加方便一些,当然它是继续QueuedRequest类的,这样才可以添加到消息队列里去,否则不能添加到这个消息队列容器,也不能实现请求处理的多态了.   下面是类Request的构造函数. #001 LLVFSThread::Request::Request(handle_t handle, U32 prio

关于Junit源码的一些探索

Junit介绍 Junit 是由 Erich Gamma 和 Kent Beck 编写的一个开源的单元测试框架,主要用于白盒测试.Junit 的设计精简,易学易用,但是功能却非常强大. 在Junit3中,使用时只需继承TestCase类,并且 1. 测试方法名以test开头,返回void,方法中没有参数 2. 使用方法setUp()做初始化工作,使用方法tearDown()做清理工作 3. 使用Assert类的方法来判断测试用例结果 Junit源码分析 Junit的流程图: Junit的完整生命

OkHttp 3.7源码分析(四)——缓存策略

OkHttp3.7源码分析文章列表如下: OkHttp源码分析--整体架构 OkHttp源码分析--拦截器 OkHttp源码分析--任务队列 OkHttp源码分析--缓存策略 OkHttp源码分析--多路复用 合理地利用本地缓存可以有效地减少网络开销,减少响应延迟.HTTP报头也定义了很多与缓存有关的域来控制缓存.今天就来讲讲OkHttp中关于缓存部分的实现细节. 1. HTTP缓存策略 首先来了解下HTTP协议中缓存部分的相关域. 1.1 Expires 超时时间,一般用在服务器的respon

jQuery 1.9.1源码分析系列(十四)之常用jQuery工具_jquery

为了给下一章分析动画处理做准备,先来看一下一些工具.其中队列工具在动画处理中被经常使用. jQuery.fn. queue(([ queueName ] [, newQueue ]) || ([ queueName ,] callback ))(获取或设置当前匹配元素上待执行的函数队列. 如果当前jQuery对象匹配多个元素:获取队列时,只获取第一个匹配元素上的队列:设置队列(替换队列.追加函数)时,则为每个匹配元素都分别进行设置.如果需要移除并执行队列中的第一个函数,请使用dequeue()函