[设计模式]-装饰者模式
一句话总结
通过继承自同一父类,来实现给某一个类动态的添加新的职责,原理是每一个装饰者持有被装饰者的实例,并可以用自身替代他.
前言
本文写于阅读《Head First 设计模式》第三章之后,因此文中举例大部分是"复盘"书中所写,以起到加深理解和记忆的作用.
介绍
定义
装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
设计原则
- 多组合,少继承。
- 类应设计的对扩展开放,对修改关闭。
类图
举个栗子(以书中"星巴慈咖啡"为例)
其实日常我们经常会去一些饮品店买饮料,咖啡等,有没有想过这个订单系统是如何实现的呢?
项目背景:
店里有多种饮料(咖啡,茶等),还有多种调料(奶,糖,椰果,珍珠等),比较暴躁的方法当然是遍历一下所有的,比如新建"双份奶一糖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)在运行时动态地为对象添加属性,不必改变对象的结构。
优缺点
优点
- 扩展性好
- 符合开闭原则
缺点
- 会有许多的装饰类,导致程序复杂性提高
装饰者模式在JDK中的应用
在书中介绍完"星巴慈咖啡"的例子后,提到了在java.io包中大量使用了装饰者模式,这里对io包内的FileInputStream
类及其相关类进行一个了解及学习.
这是我画的一个InputStream
相关类的类图,当然没有画完整,但是已经足够用了.
在图中,InputStream
是所有类的基类,相当于Component
.
左边的FileInputStream
,StringBufferInputStream
等是具体的被装饰者,相当于Coffee
.
右侧的FilterInputStream
是一个抽象的装饰者,相当于Decorator
.
继承自FilterInputStream
的BufferedInputStream
和DataInputStream
就是具体的装饰者了.
可以理解为,我们拿到一个输入流
,这个输入流
负责从不同的数据源读取到数据,之后我们以各种装饰者装饰它,可以为其加上各种功能,比如,将读取到的每一个大写字符转换成小写字符.
那么我们就实现这个装饰器吧!
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-11 完成以上皆为个人所思所得,如有错误欢迎评论区指正。
欢迎转载,烦请署名并保留原文链接。
联系邮箱:[email protected]
更多学习笔记见个人博客------>呼延十