现在我准备采用源码驱动设计模式的学习方式来学习。因为junit
框架中用到了组合模式,现在我们就对组合模式进行一下分析。我准备从以下几个方面来分析组合模式。
1. 定义与结构
2. 透明性与安全性
3. 该模式在junit
中的应用
定义与结构
组合模式的定义:将对象以树形结构组织起来,以达到“部分-整体”的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。
从定义中可以得到使用组合模式的环境为:在设计中想表示对象“部分-整体”层次结构;希望用户忽略组合对象和单个对象的不同,统一地使用组合结构中的所有对象。
组合模式的组成:
1. 抽象构件角色Component
:它为组合中的对象声明接口,也可以为共有接口实现缺省行为,
2. 树叶构件角色Leaf
:在组合中表示叶节点对象—没有子节点,实现抽象构件角色声明的接口
3. 树枝构件角色Composite
:在组合中表示分支节点对象—有子节点,实现抽象构件角色声明的接口。
如图所示:一个Composite
实例可以像一个简单的Leaf
实例一样,可以把它传递给任何使用Component
的方法或者对象,并且它表现的就像是一个Leaf
一样。
我们来看一下一个简单的示例:
Component
类
public abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void add(Component component);
public abstract void remove(Component component);
public abstract void display(int depth);
}
Composite
类
public class Composite extends Component {
private List<Component> components = new ArrayList<>();
public Composite(String name) {
super(name);
}
public void add(Component component) {
components.add(component);
}
public void remove(Component component) {
components.remove(component);
}
public void display(int depth) {
System.out.println(new String("-" + depth) + " " + name);
for (Component component : components) {
component.display(depth + 2);
}
}
}
Leaf
类
public class Leaf extends Component {
public Leaf(String name) {
super(name);
}
public void add(Component component) {
System.out.println("没有子节点,不能新增子节点");
}
public void remove(Component component) {
System.out.println("没有子节点,不能删除子节点");
}
public void display(int depth) {
System.out.println(new String("-" + depth) + " " + name);
}
}
透明性与安全性
组合模式中必须提供子对象的管理方法。不然无法完成对子对象的添加删除等等操作,也就失去了灵活性和扩展性。但是管理方法是在Component
中声明还是在Composite
中声明呢?
一种方式是在Component
中声明所有的用来管理子对象的方法,以达到Component
接口最大化(如下图所示)。目的就是为了使客户看来在接口层次上树叶和分支没有区别—透明性。但树叶是不存在子类的,因此Component
声明的一些方法对于树叶来说是不适用的,这样也就带来了一些安全问题。
另一种方式就是只在Composite
里面声明所有的用来管理子类对象的方法(如下图所示)。这样就避免了上一种方式的安全性问题,但是由于叶子和分支有不同的接口,所以又失去了透明性。
《设计模式》一书认为:在这一模式中,相对于安全性,我们比较强调透明性,对于第一种方式中叶子节点内不需要的方法可以使用空处理或者异常报告的方式来解决。
该模式在junit
中的应用
junit
是一个单元测试框架。按照此框架来编写单元测试代码,就可以使单元测试自动化。为了达到自动化的目的,junit
中定义了两个概念:TestCase
和TestSuite
,其中TestCase
是对一个类或者servlet等等编写的测试类;而TestSuite
是一个不同TestCase
的集合,当然这个集合里面也可以包含TestSuite
元素,这样运行一个TestSuite
会将其包含的TestCase
全部运行。
然而在真实运行测试程序的时候,是不需要关心这个类是TestCase
还是TestSuite
,我们只关心测试运行结果如何。这就是为什么junit
使用组合模式的原因。
junit
为了采用组合模式将TestCase
和TestSuite
统一起来,创建一个Test
充当了抽象构件角色,TestCase
充当了树叶构件角色,TestSuite
充当了树枝构件角色。下面对这三个类的有关代码分析如下:
Test
类—充当抽象构件角色
package junit.framework;
/**
* A <em>Test</em> can be run and collect its results.
*
* @see TestResult
*/
public interface Test {
/**
* Counts the number of test cases that will be run by this test.
* 统计测试用例的条数
*/
public abstract int countTestCases();
/**
* Runs a test and collects its result in a TestResult instance.
* 运行测试用例
*/
public abstract void run(TestResult result);
}
TestCase
类—充当了树叶构件的角色
public abstract class TestCase extends Assert implements Test {
public int countTestCases() {
return 1;
}
/**
* Runs the test case and collects the results in TestResult.
*/
public void run(TestResult result) {
result.run(this);
}
......
}
TestSuite
类—充当了树枝构件角色
public class TestSuite implements Test {
//用Vector来保存添加的Test
private Vector fTests= new Vector(10);
private String fName;
/**
* Adds a test to the suite.
*/
public void addTest(Test test) {
//注意这里的参数是Test类型的,这就意味着`TestCase`和`TestSuite`以及以后实现`Test`接口的任何类都可以被添加进来。
fTests.addElement(test);
}
/**
* Counts the number of test cases that will be run by this test.
*/
public int countTestCases() {
int count= 0;
for (Enumeration e= tests(); e.hasMoreElements(); ) {
Test test= (Test)e.nextElement();
count= count + test.countTestCases();
}
return count;
}
/**
* Runs the tests and collects their result in a TestResult.
*/
public void run(TestResult result) {
for (Enumeration e= tests(); e.hasMoreElements(); ) {
if (result.shouldStop() )
break;
Test test= (Test)e.nextElement();
runTest(test, result);
}
}
//这个方法里面就是递归的调用了。
public void runTest(Test test, TestResult result) {
test.run(result);
}
......
}