设计原则:
- 少用继承,多用组合
- 类应该对扩展开放,对修改关闭
目录
本文的结构如下:
- 什么是装饰者模式
- 为什么要用该模式
- 模式的结构
- 代码示例
- 优点和缺点
- 适用环境
- 总结
一、什么是装饰模式
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。和代理模式很相似,但在对被装饰的对象的控制程度是不同的;装饰者模式是对对象功能的加强,而代理模式是对对象施加控制,并不提供对对象本身功能的加强。
通俗点讲,假设现在一杯纯豆浆(Soya)卖1元钱,你可以选择往里边加糖0.5元(Suger),加蜂蜜0.5元(Honey),加牛奶1元(Milk),加黑豆1元(BlackBean)。这里面纯豆浆就是被装饰者,而糖、蜂蜜、牛奶、黑豆就是装饰者了。
二、为什么要用该模式
通过继承的方式可以使子类具有父类的属性和方法。子类继承父类后,因为一些业务需求可以通过重写的方式来加强父类的方法的一些功能,也可以重新定义某些属性,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。
而装饰者模式的最基本的功能就是对传入的一个对象进行功能的加强与优化。
那么问题来了,既然继承方式也可以对已有类或对象进行加强,那为什么还要衍生出装饰者模式这一思想呢?
装饰者模式的意图定义为:动态地给一个对象添加一些额外的职责。装饰者模式存在的更重要的意义就在于动态的为对象添加一些功能(或分配额外职责)。
以豆浆的例子为例,先尝试用继承的方式分析一下:设豆浆类为基类,每个类中有一个money属性,那么豆浆加牛奶可模拟为Soya类继承Milk并重写pay()方法,如此继承确实可以计算出每种组合的价钱,于是有下图:
一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
在不想增加很多子类的情况下扩展类,如何实现呢?这时就要用到今天的主角:装饰者模式了。
Java中的IO机制就用到了装饰者模式。比如最常用的语句:
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filepath)));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
这就是最常见的装饰者模式了,通过BufferedReader对已有对象FileReader的功能进行加强和优化。其实它不仅可以加强FileReader,所有的字符输入流都可以通过这种方式进行包装。
它是如何实现的呢?
将所有的字符输入流抽象出了一个基类或接口即Reader,然后通过构造方法的形式将Reader传递给BufferedReader,此时BufferedReader就可以对所有的字符输入流进行拦截和优化了。
如果采用继承机制,每个XXXReader就要衍生出一个BufferedXXXReader,再加上字符输出流和字节输入输出流,那么Java的IO体系结构该是多么的臃肿不堪啊!而装饰者模式的出现解决了这个问题,并且,装饰者的出现也再一次的证明了面向对象的设计原则:多用组合,少用继承!对扩展开放,对修改关闭!
三、模式的结构
- Component,给出一个抽象接口或者抽象类,规范实现类的一些方法;
- ConcreteComponent:具体的一个组件,实现了抽象方法;
- Decorator:抽象的装饰者,对抽象接口或者抽象类的一个引用,在 method 方法里面使用这个引用完成任务;(代理模式需要实例化)
- ConcreteDecorator:具体的装饰者,对抽象装饰者的抽象部分进行实现。
第一步:抽象组件
/**
* 抽象组件
*
* Created by w1992wishes on 2017/10/30.
*/
public abstract class Component {
public abstract void method();
}
第二步:具体组件
/**
* 具体组件
*
* Created by w1992wishes on 2017/10/30.
*/
public class ConcreteComponent extends Component {
public void method() {
System.out.println("work");
}
}
第三步:抽象装饰者
/**
* 抽象的装饰者
*
* Created by w1992wishes on 2017/10/30.
*/
public abstract class Decorator extends Component{
private Component component;
public Decorator(Component component){
this.component = component;
}
@Override
public void method(){
beforeM();
component.method();
afterM();
}
public abstract void beforeM();
public abstract void afterM();
}
第四步:具体的抽象者
/**
* 具体的装饰者
*
* Created by w1992wishes on 2017/10/30.
*/
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component){
super(component);
}
@Override
public void beforeM() {
System.out.println("Relax, first having a game before work!");
}
public void afterM() {
System.out.println("Relax, first having a game after work!");
}
}
第五步:客户端运行
/**
* Created by w1992wishes on 2017/10/30.
*/
public class Client {
public static void main(String[] args) {
Component c = new ConcreteComponent();
Decorator d = new ConcreteDecorator(c);
d.method();
}
}
结果:
Relax, first having a game before work!
work
Relax, first having a game after work!
四、代码示例
前面其实可以看作是一个代码示例,下面接着用豆浆的例子来段代码(这里使用抽象接口):
首先抽象出一个接口,作为装饰者构造函数的参数,即被装饰者的父类:
/**
* Created by w1992wishes on 2017/10/30.
*/
public interface Drink {
public float money();//获取价格。
public String description();//返回商品信息。
}
接下来就是装饰者类,继承此接口并通过构造方法获取被装饰对象公共接口:
/**
* Created by w1992wishes on 2017/10/30.
*/
public abstract class Decorator implements Drink{
private Drink drink;
public Decorator (Drink drink){
this.drink = drink;
}
@Override
public String description() {
return drink.description();
}
@Override
public float money() {
return drink.money();
}
}
下面是被装饰者Soya:
/**
* 具体的装饰者对象,纯豆浆
*
* Created by w1992wishes on 2017/10/30.
*/
public class Soya implements Drink {
public String description() {
return "纯豆浆";
}
public float money() {
return 2f;
}
}
下面分别是装饰者Suger,Milk,BlackBean,Honey类:
Suger
/**
* 具体的装饰者类:糖
*
* Created by w1992wishes on 2017/10/30.
*/
public class Suger extends Decorator {
public Suger(Drink drink) {
super(drink);
}
public String description() {
return super.description()+"+糖";
}
public float money() {
return super.money()+1.5f;
}
}
Milk
/**
* 具体的装饰者对象:牛奶
*
* Created by w1992wishes on 2017/10/30.
*/
public class Milk extends Decorator {
public Milk(Drink drink) {
super(drink);
}
public String description() {
return super.description()+"+牛奶";
}
public float money() {
return super.money()+1.5f;
}
}
Honey
/**
* 具体的装饰者类:蜂蜜
*
* Created by w1992wishes on 2017/10/30.
*/
public class Honey extends Decorator {
public Honey(Drink drink) {
super(drink);
}
public String description() {
return super.description()+"+蜂蜜";
}
public float money() {
return super.money()+1.5f;
}
}
BlackBean
/**
* 具体的装饰者对象:黑豆
*
* Created by w1992wishes on 2017/10/30.
*/
public class BlackBean extends Decorator {
public BlackBean(Drink drink) {
super(drink);
}
public String description() {
return super.description()+"+黑豆";
}
public float money() {
return super.money()+0.5f;
}
}
最后是测试代码:
/**
* Created by w1992wishes on 2017/10/30.
*/
public class Client {
public static void main(String[] args) {
//定义一杯纯豆浆
Soya soya = new Soya();
System.out.print("-----------------");
System.out.println(soya.description()+" 价钱:"+soya.money());
//豆浆加奶
Milk milkSoya = new Milk(soya);
System.out.print("-----------------");
System.out.println(milkSoya.description()+" 价钱:"+milkSoya.money());
//黑豆豆浆+奶
BlackBean beanMilkSoya = new BlackBean(milkSoya);
System.out.print("-----------------");
System.out.println(beanMilkSoya.description()+" 价钱:"+beanMilkSoya.money());
//黑豆豆浆+奶+蜂蜜
Honey honeyBeanMilkSoya = new Honey(beanMilkSoya);
System.out.print("-----------------");
System.out.println(honeyBeanMilkSoya.description()+" 价钱:"+honeyBeanMilkSoya.money());
}
}
结果:
-----------------纯豆浆 价钱:2.0
-----------------纯豆浆+牛奶 价钱:3.5
-----------------纯豆浆+牛奶+黑豆 价钱:4.0
-----------------纯豆浆+牛奶+黑豆+蜂蜜 价钱:5.5
五、优点和缺点
5.1、优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
5.2、缺点:
- 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
- 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
六、适用环境
因为装饰者模式的以下特点:
- 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
- 装饰对象包含一个真实对象的引用(reference)。
- 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
- 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。
所以在以下情况下可以使用装饰模式:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类)。
七、总结
- 装饰模式用于动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。它是一种对象结构型模式。
- 装饰模式包含四个角色:抽象构件定义了对象的接口,可以给这些对象动态增加职责(方法);具体构件定义了具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法);抽象装饰类是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现;具体装饰类是抽象装饰类的子类,负责向构件添加新的职责。
- 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。
- 装饰模式的主要优点在于可以提供比继承更多的灵活性,可以通过一种动态的 方式来扩展一个对象的功能,并通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,而且具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类;其主要缺点在于使用装饰模式进行系统设计时将产生很多小对象,而且装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
- 装饰模式适用情况包括:在不影响其他对象的情况下,以动态、透明的方式给 单个对象添加职责;需要动态地给一个对象增加功能,这些功能也可以动态地被撤销;当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
- 装饰模式可分为透明装饰模式和半透明装饰模式:在透明装饰模式中,要求客 户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构件类型和具体装饰类型,而应该全部声明为抽象构件类型;半透明装饰模式允许用户在客户端声明具体装饰者类型的对象,调用在具体装饰者中新增的方法。