[关闭]
@w1992wishes 2017-11-01T10:14:01.000000Z 字数 6762 阅读 935

设计模式--装饰者模式

设计模式 结构型模式


设计原则:

  1. 少用继承,多用组合
  2. 类应该对扩展开放,对修改关闭

目录

本文的结构如下:

一、什么是装饰模式

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。和代理模式很相似,但在对被装饰的对象的控制程度是不同的;装饰者模式是对对象功能的加强,而代理模式是对对象施加控制,并不提供对对象本身功能的加强。

通俗点讲,假设现在一杯纯豆浆(Soya)卖1元钱,你可以选择往里边加糖0.5元(Suger),加蜂蜜0.5元(Honey),加牛奶1元(Milk),加黑豆1元(BlackBean)。这里面纯豆浆就是被装饰者,而糖、蜂蜜、牛奶、黑豆就是装饰者了。

二、为什么要用装饰者模式

通过继承的方式可以使子类具有父类的属性和方法。子类继承父类后,因为一些业务需求可以通过重写的方式来加强父类的方法的一些功能,也可以重新定义某些属性,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。

而装饰者模式的最基本的功能就是对传入的一个对象进行功能的加强与优化。

那么问题来了,既然继承方式也可以对已有类或对象进行加强,那为什么还要衍生出装饰者模式这一思想呢?

装饰者模式的意图定义为:动态地给一个对象添加一些额外的职责。装饰者模式存在的更重要的意义就在于动态的为对象添加一些功能(或分配额外职责)。

以豆浆的例子为例,先尝试用继承的方式分析一下:设豆浆类为基类,每个类中有一个money属性,那么豆浆加牛奶可模拟为Soya类继承Milk并重写pay()方法,如此继承确实可以计算出每种组合的价钱,于是有下图:

一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

在不想增加很多子类的情况下扩展类,如何实现呢?这时就要用到今天的主角:装饰者模式了。

Java中的IO机制就用到了装饰者模式。比如最常用的语句:

  1. BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filepath)));
  2. BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

这就是最常见的装饰者模式了,通过BufferedReader对已有对象FileReader的功能进行加强和优化。其实它不仅可以加强FileReader,所有的字符输入流都可以通过这种方式进行包装。

它是如何实现的呢?

将所有的字符输入流抽象出了一个基类或接口即Reader,然后通过构造方法的形式将Reader传递给BufferedReader,此时BufferedReader就可以对所有的字符输入流进行拦截和优化了。

如果采用继承机制,每个XXXReader就要衍生出一个BufferedXXXReader,再加上字符输出流和字节输入输出流,那么Java的IO体系结构该是多么的臃肿不堪啊!而装饰者模式的出现解决了这个问题,并且,装饰者的出现也再一次的证明了面向对象的设计原则:多用组合,少用继承!对扩展开放,对修改关闭

三、装饰者模式的结构

  1. Component,给出一个抽象接口或者抽象类,规范实现类的一些方法;
  2. ConcreteComponent:具体的一个组件,实现了抽象方法;
  3. Decorator:抽象的装饰者,对抽象接口或者抽象类的一个引用,在 method 方法里面使用这个引用完成任务;(代理模式需要实例化)
  4. ConcreteDecorator:具体的装饰者,对抽象装饰者的抽象部分进行实现。

第一步:抽象组件

  1. /**
  2. * 抽象组件
  3. *
  4. * Created by w1992wishes on 2017/10/30.
  5. */
  6. public abstract class Component {
  7. public abstract void method();
  8. }

第二步:具体组件

  1. /**
  2. * 具体组件
  3. *
  4. * Created by w1992wishes on 2017/10/30.
  5. */
  6. public class ConcreteComponent extends Component {
  7. public void method() {
  8. System.out.println("work");
  9. }
  10. }

第三步:抽象装饰者

  1. /**
  2. * 抽象的装饰者
  3. *
  4. * Created by w1992wishes on 2017/10/30.
  5. */
  6. public abstract class Decorator extends Component{
  7. private Component component;
  8. public Decorator(Component component){
  9. this.component = component;
  10. }
  11. @Override
  12. public void method(){
  13. beforeM();
  14. component.method();
  15. afterM();
  16. }
  17. public abstract void beforeM();
  18. public abstract void afterM();
  19. }

第四步:具体的抽象者

  1. /**
  2. * 具体的装饰者
  3. *
  4. * Created by w1992wishes on 2017/10/30.
  5. */
  6. public class ConcreteDecorator extends Decorator {
  7. public ConcreteDecorator(Component component){
  8. super(component);
  9. }
  10. @Override
  11. public void beforeM() {
  12. System.out.println("Relax, first having a game before work!");
  13. }
  14. public void afterM() {
  15. System.out.println("Relax, first having a game after work!");
  16. }
  17. }

第五步:客户端运行

  1. /**
  2. * Created by w1992wishes on 2017/10/30.
  3. */
  4. public class Client {
  5. public static void main(String[] args) {
  6. Component c = new ConcreteComponent();
  7. Decorator d = new ConcreteDecorator(c);
  8. d.method();
  9. }
  10. }

结果:
Relax, first having a game before work!
work
Relax, first having a game after work!

四、代码示例

前面其实可以看作是一个代码示例,下面接着用豆浆的例子来段代码(这里使用抽象接口):

首先抽象出一个接口,作为装饰者构造函数的参数,即被装饰者的父类:

  1. /**
  2. * Created by w1992wishes on 2017/10/30.
  3. */
  4. public interface Drink {
  5. public float money();//获取价格。
  6. public String description();//返回商品信息。
  7. }

接下来就是装饰者类,继承此接口并通过构造方法获取被装饰对象公共接口:

  1. /**
  2. * Created by w1992wishes on 2017/10/30.
  3. */
  4. public abstract class Decorator implements Drink{
  5. private Drink drink;
  6. public Decorator (Drink drink){
  7. this.drink = drink;
  8. }
  9. @Override
  10. public String description() {
  11. return drink.description();
  12. }
  13. @Override
  14. public float money() {
  15. return drink.money();
  16. }
  17. }

下面是被装饰者Soya:

  1. /**
  2. * 具体的装饰者对象,纯豆浆
  3. *
  4. * Created by w1992wishes on 2017/10/30.
  5. */
  6. public class Soya implements Drink {
  7. public String description() {
  8. return "纯豆浆";
  9. }
  10. public float money() {
  11. return 2f;
  12. }
  13. }

下面分别是装饰者Suger,Milk,BlackBean,Honey类:
Suger

  1. /**
  2. * 具体的装饰者类:糖
  3. *
  4. * Created by w1992wishes on 2017/10/30.
  5. */
  6. public class Suger extends Decorator {
  7. public Suger(Drink drink) {
  8. super(drink);
  9. }
  10. public String description() {
  11. return super.description()+"+糖";
  12. }
  13. public float money() {
  14. return super.money()+1.5f;
  15. }
  16. }

Milk

  1. /**
  2. * 具体的装饰者对象:牛奶
  3. *
  4. * Created by w1992wishes on 2017/10/30.
  5. */
  6. public class Milk extends Decorator {
  7. public Milk(Drink drink) {
  8. super(drink);
  9. }
  10. public String description() {
  11. return super.description()+"+牛奶";
  12. }
  13. public float money() {
  14. return super.money()+1.5f;
  15. }
  16. }

Honey

  1. /**
  2. * 具体的装饰者类:蜂蜜
  3. *
  4. * Created by w1992wishes on 2017/10/30.
  5. */
  6. public class Honey extends Decorator {
  7. public Honey(Drink drink) {
  8. super(drink);
  9. }
  10. public String description() {
  11. return super.description()+"+蜂蜜";
  12. }
  13. public float money() {
  14. return super.money()+1.5f;
  15. }
  16. }

BlackBean

  1. /**
  2. * 具体的装饰者对象:黑豆
  3. *
  4. * Created by w1992wishes on 2017/10/30.
  5. */
  6. public class BlackBean extends Decorator {
  7. public BlackBean(Drink drink) {
  8. super(drink);
  9. }
  10. public String description() {
  11. return super.description()+"+黑豆";
  12. }
  13. public float money() {
  14. return super.money()+0.5f;
  15. }
  16. }

最后是测试代码:

  1. /**
  2. * Created by w1992wishes on 2017/10/30.
  3. */
  4. public class Client {
  5. public static void main(String[] args) {
  6. //定义一杯纯豆浆
  7. Soya soya = new Soya();
  8. System.out.print("-----------------");
  9. System.out.println(soya.description()+" 价钱:"+soya.money());
  10. //豆浆加奶
  11. Milk milkSoya = new Milk(soya);
  12. System.out.print("-----------------");
  13. System.out.println(milkSoya.description()+" 价钱:"+milkSoya.money());
  14. //黑豆豆浆+奶
  15. BlackBean beanMilkSoya = new BlackBean(milkSoya);
  16. System.out.print("-----------------");
  17. System.out.println(beanMilkSoya.description()+" 价钱:"+beanMilkSoya.money());
  18. //黑豆豆浆+奶+蜂蜜
  19. Honey honeyBeanMilkSoya = new Honey(beanMilkSoya);
  20. System.out.print("-----------------");
  21. System.out.println(honeyBeanMilkSoya.description()+" 价钱:"+honeyBeanMilkSoya.money());
  22. }
  23. }

结果:
-----------------纯豆浆 价钱:2.0
-----------------纯豆浆+牛奶 价钱:3.5
-----------------纯豆浆+牛奶+黑豆 价钱:4.0
-----------------纯豆浆+牛奶+黑豆+蜂蜜 价钱:5.5

五、装饰者模式的优点和缺点

5.1、装饰模式的优点:

5.2、装饰模式的缺点:

六、装饰者模式的适用环境

因为装饰者模式的以下特点:

  1. 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
  2. 装饰对象包含一个真实对象的引用(reference)。
  3. 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
  4. 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。

所以在以下情况下可以使用装饰模式:

七、总结

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注