安居多伦多
  • 多市生活
    • 多市生活
    • 加国税务
    • 旅游度假
    • 生活安全
    • 行车安全
    • 窍门集锦
  • 多市书苑
    • 热门
    • 小说
    • 教育
  • 家居信息
    • 家居信息
    • 房屋保养
    • 房屋贷款
    • 房屋租赁
    • 房屋建筑
    • 房前屋后
    • 家居风水
  • 健康保健
    • 健康保健
    • 饮食起居
    • 食品安全
    • 健身锻炼
  • 书苑账户
    • 书苑登入
    • 书苑注册
    • 忘记书苑密码
    • 书苑账户信息
    • 关于我们
    • 联系我们
    • 隐私政策
多伦多书苑
在线书籍:随时阅读,随身听书。
所有书籍 | 人文 | 人物 | 人生 | 健康 | 儿童 | 医学 | 历史 | 历史 | 古典 | 哲学宗教 | 商业 | 外国 | 寓言 | 小说 | 教育 | 风水 | 管理 | 语言 |
为使本公益资源网站能继续提供免费阅读,请勿屏蔽广告。谢谢!报告弹出广告被滥用。
  1. 安居多伦多
  2. 网上书苑
  3. IT
  4. 编程
  5. 深入浅出设计模式

深入浅出设计模式

2021-08-06 0人点赞 0条评论
点赞
x
语速1.0: 2.0
进度0:

上一页   |   返回目录   |   下一页

装饰模式

一、引子

?肯定让你想起又黑又火的家庭装修来。其实两者在道理上还是有很多相像的地方。家庭装修无非就是要掩盖住原来实而不华的墙面,抹上一层华而不实的涂料,让生活多一点色彩。而墙还是那堵墙,他的本质一点都没有变,只是多了一层外衣而已。

那设计模式中的,是什么样子呢?

二、定义与结构

(Decorator)也叫包装器模式(Wrapper)。GOF 在《设计模式》一书中给出的定义为:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator 模式相比生成子类更为灵活。

让我们来理解一下这句话。我们来设计“门”这个类。假设你根据需求为“门”类作了如下定义:

装饰模式

现在,在系统的一个地方需要一个能够报警的Door,你来怎么做呢?你或许写一个Door的子类 AlarmDoor,在里面添加一个子类独有的方法 alarm()。嗯,那在使用警报门的地方你必须让客户知道使用的是警报门,不然无法使用这个独有的方法。而且,这个还违反了Liskov 替换原则。

也许你要说,那就把这个方法添加到 Door 里面,这样不就统一了?但是这样所有的门都必须有警报,至少是个“哑巴”警报。而当你的系统仅仅在一两个地方使用了警报门,这明显是不合理的——虽然可以使用缺省适配器来弥补一下。

这时候,你可以考虑采用来给门动态的添加些额外的功能。

下面我们来看看的组成,不要急着去解决上面的问题,到了下面自然就明白了!

1) 抽象构件角色(Component):定义一个抽象接口,以规范准备接收附加责任的对象。

2) 具体构件角色(Concrete Component):这是被装饰者,定义一个将要被装饰增加功能的类。

3) 装饰角色(Decorator):持有一个构件对象的实例,并定义了抽象构件定义的接口。

4) 具体装饰角色(Concrete Decorator):负责给构件添加增加的功能。

看下的类图:

装饰模式

装饰模式

图中 ConcreteComponent 可能继承自其它的体系,而为了实现,他还要实现Component 接口。整个的结构是按照组合模式来实现的——两者都有类似的结构图,都基于递归组合来组织可变数目的对象。但是两者的目的是截然不同的,组合(Composite)模式侧重通过递归组合构造类,使不同的对象、多重的对象可以“一视同仁”;而装饰(Decorator)模式仅仅是借递归组合来达到定义中的目的。

三、举例

这个例子还是来自我最近在研究的 JUnit,如果你对 JUnit 还不太了解,可以浏览一些关于 Junit 的文章。不愧是由 GoF 之一的 Erich Gamma 亲自开发的,小小的东西使用了 N种的模式在里面。下面就来看看 JUnit 中的。

在 JUnit 中,TestCase 是一个很重要的类,允许对其进行功能扩展。

在 junit.extensions 包中,TestDecorator、RepeatedTest 便是对 TestCase 的扩展。下面我们将它们和上面的角色对号入座。

装饰模式

呵呵,看看源代码吧,这个来的最直接!

//这个就是抽象构件角色
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);
}
//具体构件对象,但是这里是个抽象类
public abstract class TestCase extends Assert implements Test {
……
public int countTestCases() {
return 1;
}
……
public TestResult run() {
TestResult result= createResult();
run(result);
return result;
}
public void run(TestResult result) {
result.run(this);
}
……
}
//装饰角色
public class TestDecorator extends Assert implements Test {
//这里按照上面的要求,保留了一个对构件对象的实例
protected Test fTest;
public TestDecorator(Test test) {
fTest= test;
}
/**
* The basic run behaviour.
*/
public void basicRun(TestResult result) {
fTest.run(result);
}
public int countTestCases() {
return fTest.countTestCases();
}
public void run(TestResult result) {
basicRun(result);
}
public String toString() {
return fTest.toString();
}
public Test getTest() {
return fTest;
}
}
//具体装饰角色,这个类的增强作用就是可以设置测试类的执行次数
public class RepeatedTest extends TestDecorator {
private int fTimesRepeat;
public RepeatedTest(Test test, int repeat) {
super(test);
if (repeat < 0)
throw new IllegalArgumentException("Repetition count must be > 0");
fTimesRepeat= repeat;
}
//看看怎么装饰的吧
public int countTestCases() {
return super.countTestCases()*fTimesRepeat;
}
public void run(TestResult result) {
for (int i= 0; i < fTimesRepeat; i++) {
if (result.shouldStop())
break;
super.run(result);
}
}
public String toString() {
return super.toString()+"(repeated)";
}
}

使用的时候,就可以采用下面的方式:

TestDecorator test = new RepeatedTest(new TestXXX() , 3);
让我们在回想下上面提到的“门”的问题,这个警报门采用了后,可以采用下面的方式来产生。

DoorDecorator alarmDoor = new AlarmDoor(new Door());

四、应用环境

GOF 书中给出了以下使用情况:

1) 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

2) 处理那些可以撤消的职责。

3) 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

来分析下 JUnit 的使用是属于哪种情况。首先实现了比静态继承更加灵活的方式,动态的增加功能。试想为 Test 的所有实现类通过继承来增加一个功能,意味着要添加不少的功能类似的子类,这明显是不太合适的。

而且,这就避免了高层的类具有太多的特征,比如上面提到的带有警报的抽象门类。

五、透明和半透明

对于面向接口编程,应该尽量使客户程序不知道具体的类型,而应该对一个接口操作。

这样就要求装饰角色和具体装饰角色要满足 Liskov 替换原则。像下面这样:

Component c = new ConcreteComponent();
Component c1 = new ConcreteDecorator(c);
JUnit 中就属于这种应用,这种方式被称为透明式。而在实际应用中,比如 java.io 中往往因为要对原有接口做太多的扩展而需要公开新的方法(这也是为了重用)。所以往往不能对客户程序隐瞒具体的类型。这种方式称为“半透明式”。

在 java.io 中,并不是纯的范例,它是、适配器模式的混合使用。

六、其它

采用 Decorator 模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。这是 GOF 提到的的缺点,你能体会吗?他们所说的小对象我认为是指的具体装饰角色。这是为一个对象动态添加功能所带来的副作用。

上一页   |   返回目录   |   下一页

Author:

标签: 暂无
最后更新:2021-08-06

本书评论

取消回复

©2021 安居多伦多 - 版权所有

本站由 好事来 Hostlike.com 提供技术支持。