首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[设计模式] 装饰者模式

[设计模式] 装饰者模式

作者头像
呼延十
发布2019-07-01 16:56:24
3570
发布2019-07-01 16:56:24
举报
文章被收录于专栏:呼延呼延

一句话总结

通过继承自同一父类,来实现给某一个类动态的添加新的职责,原理是每一个装饰者持有被装饰者的实例,并可以用自身替代他.

前言

本文写于阅读《Head First 设计模式》第三章之后,因此文中举例大部分是”复盘”书中所写,以起到加深理解和记忆的作用.

介绍

定义

装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

设计原则

  1. 多组合,少继承。
  2. 类应设计的对扩展开放,对修改关闭。

类图

举个栗子(以书中”星巴慈咖啡”为例)

其实日常我们经常会去一些饮品店买饮料,咖啡等,有没有想过这个订单系统是如何实现的呢?

项目背景: 店里有多种饮料(咖啡,茶等),还有多种调料(奶,糖,椰果,珍珠等),比较暴躁的方法当然是遍历一下所有的,,比如新建”双份奶一糖0椰果2珍珠奶茶”等等好多类,然后根据用户的需求下订单,但是这也太蠢了8.

而装饰者可谓是及其适用这个场景了.

首先,将咖啡,茶等饮料定义为被装饰者,奶,糖,椰果,珍珠等定义为装饰者,当用户下单时,用户购买了一个”被装饰者”以及若干个“装饰者”,我们只需要将奶,糖,椰果,珍珠等东西装饰在被用户购买的这个被装饰者上即可.

1. 定义基类

在装饰者模式中,被装饰者和装饰者继承自同一个基类,我们定义为Component,每个Component有自己的名字以及价钱.

public class Component {

  private String name;
  private float prize;

  public String getName() {
    return name;
  }

  public float getPrize() {
    return prize;
  }

}

2. 定义咖啡类(本文被装饰者只实现咖啡,装饰者只实现’奶’,’糖’两个)

在构造方法中添加咖啡的名字以及价格.

public class Coffee extends Component {


  public Coffee() {
    this.name = "coffee";
    this.prize = 10.0f;
  }

  @Override
  public String getName() {
    return name;
  }

  @Override
  public float getPrize() {
    return prize;
  }
}

3.定义”装饰者”类

其实在这里有一问题,装饰者被装饰者同样继承自Component,为什么被装饰者直接继承,而装饰者却还需要再写一个子类呢?

我的理解是,对咖啡来说,名字及价格直接返回自身的名字及价格即可,与Component定义相同,而对于装饰者Decorator来说,需要再这个类中,重写返回名字及价格的方法,在返回名字时,将他所持有的饮料实例的名字也进行返回.

比如我们希望拿到的是1份奶+咖啡或者咖啡,而不是1份奶这样单独的价格.

public class Decorator extends Component {

  protected Component component;

  @Override
  public String getName() {
    return name + "," + component.getName();
  }

  @Override
  public float getPrize() {
    return prize + component.getPrize();
  }

  public Decorator(Component component) {
    this.component = component;
  }
}

4. 定义奶和糖

public class Milk extends Decorator {

  public Milk(Component component) {
    super(component);
    this.name = "Milk";
    this.prize = 1.0f;
  }

}
public class Sugar extends Decorator {

  public Sugar(Component component) {
    super(component);
    this.name = "Sugar";
    this.prize = 2.0f;
  }
}

测试代码-生成订单

好,现在基本的定义已经完成,我们在测试类里进行一次下单的模拟.

假设用户需要的是:2份糖1份奶的咖啡.

public static void main(String[] args) {
  //一杯咖啡
  Component coffee = new Coffee();
  //加一份奶
  coffee = new Milk(coffee);
  //再加一份奶
  coffee = new Milk(coffee);
  //加一份糖
  coffee = new Sugar(coffee);
  //将最后的总价值及所有名字列出来
  System.out.print(coffee.getName() + coffee.getPrize());

}

上述代码的输出结果为:

Sugar,Milk,Milk,coffee14.0

这样的实现方法在类少的时候看不出来优势,甚至有点麻烦,但是普通的饮料店,饮料种类动辄几十种,粗暴方法肯定是解决不了的.

而使用装饰者模式,可以很轻松的处理各种附加要求.

特点

通过上面的例子,我们可以总结一下装饰者模式的特点。 (1)装饰者和被装饰者有相同的接口(或有相同的父类)。 (2)装饰者保存了一个被装饰者的引用。 (3)装饰者接受所有客户端的请求,并且这些请求最终都会返回给被装饰者(参见韦恩图)。 (4)在运行时动态地为对象添加属性,不必改变对象的结构。

优缺点

优点

  1. 扩展性好
  2. 符合开闭原则

缺点

  1. 会有许多的装饰类,导致程序复杂性提高

装饰者模式在JDK中的应用

在书中介绍完”星巴慈咖啡”的例子后,提到了在java.io包中大量使用了装饰者模式,这里对io包内的FileInputStream类及其相关类进行一个了解及学习.

这是我画的一个InputStream相关类的类图,当然没有画完整,但是已经足够用了.

在图中,InputStream是所有类的基类,相当于Component.

左边的FileInputStream,StringBufferInputStream等是具体的被装饰者,相当于Coffee.

右侧的FilterInputStream是一个抽象的装饰者,相当于Decorator.

继承自FilterInputStreamBufferedInputStreamDataInputStream就是具体的装饰者了.

可以理解为,我们拿到一个输入流,这个输入流负责从不同的数据源读取到数据,之后我们以各种装饰者装饰它,可以为其加上各种功能,比如,将读取到的每一个大写字符转换成小写字符.

那么我们就实现这个装饰器吧!

public class CuteInputStream extends FilterInputStream {

  /**
   * Creates a <code>FilterInputStream</code> by assigning the  argument <code>in</code> to the
   * field
   * <code>this.in</code> so as to remember it for later use.
   *
   * @param in the underlying input stream, or <code>null</code> if this instance is to be created
   * without an underlying stream.
   */
  protected CuteInputStream(InputStream in) {
    super(in);
  }

  @Override
  public int read() throws IOException {
    int i = super.read();
    return Character.toLowerCase((char) i);
  }

  @Override
  public int read(byte[] b, int off, int len) throws IOException {
    int i = super.read(b, off, len);
    return Character.toLowerCase((char) i);
  }
}

测试代码:

InputStream in = new CuteInputStream(new FileInputStream("/Users/pfliu/study/test/test.txt"));
int c;
try{
  while ( (c=in.read()) >=0){
    System.out.print((char)(c));
  }
  in.close();
}catch (Exception e){
  e.printStackTrace();
}

完。

ChangeLog

2019-01-06 完成

以上皆为个人所思所得,如有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文链接。

联系邮箱:huyanshi2580@gmail.com


本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一句话总结
  • 前言
  • 介绍
    • 定义
      • 设计原则
        • 类图
        • 举个栗子(以书中”星巴慈咖啡”为例)
          • 1. 定义基类
            • 2. 定义咖啡类(本文被装饰者只实现咖啡,装饰者只实现’奶’,’糖’两个)
              • 3.定义”装饰者”类
                • 4. 定义奶和糖
                  • 测试代码-生成订单
                  • 特点
                  • 优缺点
                    • 优点
                      • 缺点
                        • ChangeLog
                    • 装饰者模式在JDK中的应用
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档