Attach additional responsibilities to an object dynamically keeping the
same interface.Decorators provide a flexible alternative to subclassing for
extending functionality.
(动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。)
上班路上,路过一个卖煎饼果子的摊位,摊位前围着一些买煎饼的人。卖煎饼的大妈手法娴熟,左手舀一勺面糊到鏊子上,右手拿刮板唰唰绕两圈,一张厚薄均匀的煎饼就成型了,大妈左手抓一颗鸡蛋在鏊子边上一磕,举到煎饼正中,手指一掰,整团蛋黄就落到了煎饼上,一气呵成!伴随着热气,阵阵香味迎面飘来…,大妈将做好的煎饼打包,递给还在玩手机的小伙子,小伙也没想到大妈的速度这么快,看了看大妈递过来的煎饼,小伙对大妈说:你是不是少给我加了个蛋?大妈一听急了:我月入3万,怎么可能少你一个鸡蛋!
路人我,瞬间扎心!!!
那么,让我们用装饰模式也来摆个月入3W的煎饼摊吧。
首先,我们定义一个食材类。
/**
* 食材
* 抽象组件对象
*/
public interface Food {
/**
* 描述
*/
String getDescription();
/**
* 价格
*/
int getPrice();
}
定义一个煎饼类,此处就是我们的被装饰对象
/**
* 煎饼果子
*/
public class Pancake implements Food {
@Override
public String getDescription() {
return "煎饼果子";
}
/**
* 纯煎饼果子8元
*/
@Override
public int getPrice() {
return 8;
}
}
定义装饰器父类
/**
* 所有装饰器的父类,需要跟被装饰的对象实现同样的接口
*/
public class FoodDecorator implements Food{
private Food food;
public FoodDecorator(Food food) {
this.food = food;
}
@Override
public String getDescription() {
return this.food.getDescription();
}
@Override
public int getPrice() {
return this.food.getPrice();
}
}
定义具体装饰类:加鸡蛋的装饰类
/**
* 装饰器对象,用来加鸡蛋
*/
public class EggDecorator extends FoodDecorator{
public EggDecorator(Food food) {
super(food);
}
@Override
public String getDescription() {
return super.getDescription() + " 加一个鸡蛋";
}
/**
* 加一个鸡蛋需要2元
*/
@Override
public int getPrice() {
return super.getPrice() + 2;
}
}
定义具体装饰类:加辣条的装饰类
/**
* 装饰器对象,用来加辣条,卫龙牌的
*/
public class SpicyGlutenDecorator extends FoodDecorator{
public SpicyGlutenDecorator(Food food) {
super(food);
}
@Override
public String getDescription() {
return super.getDescription() + " 加一包辣条";
}
/**
* 加一包辣条1元
*/
@Override
public int getPrice() {
return super.getPrice() + 1;
}
}
定义具体装饰类:加火腿的装饰类
/**
* 装饰器对象,用来加火腿
*/
public class SausageDecorator extends FoodDecorator{
public SausageDecorator(Food food) {
super(food);
}
@Override
public String getDescription() {
return super.getDescription() + " 加一根火腿";
}
/**
* 加一根火腿3元
*/
@Override
public int getPrice() {
return super.getPrice() + 3;
}
}
下面,我们先来做一套煎饼果子。
public static void main(String[] args) {
Food pancake = new Pancake();
// 加一个鸡蛋
pancake = new EggDecorator(pancake);
// 加一包辣条
pancake = new SpicyGlutenDecorator(pancake);
// 加一根火腿
pancake = new SausageDecorator(pancake);
// 再加一个鸡蛋吧
pancake = new EggDecorator(pancake);
System.out.println("套餐内容:" + pancake.getDescription());
System.out.println("套餐价格:" + pancake.getPrice());
}
输出结果为:
套餐内容:煎饼果子 加一个鸡蛋 加一包辣条 加一根火腿 加一个鸡蛋
套餐价格:16
嗯嗯,那有人可能有这样的疑问,为什么不直接定义一个既加鸡蛋、加辣条又加火腿的煎饼果子类呢,这样直接new就可以,不是更直接么。
但是,假如我们只要加鸡蛋的?或者只要加辣条的?再或者要一个加鸡蛋和辣条,但是不加火腿的呢?这种类似的组合会很多,要是我们都创建出来的话,是不是需要创建的类的数量太多了啊。
并且,如果我们不仅有煎饼果子,我们还想推出烤冷面,通过装饰模式,这些装饰类就可以直接复用到烤冷面上了。
定义烤冷面类,也是我们的被装饰对象
/**
* 烤冷面
*/
public class RoastColdNoodles implements Food {
@Override
public String getDescription() {
return "哈尔滨烤冷面";
}
/**
* 纯烤冷面10元
*/
@Override
public int getPrice() {
return 10;
}
}
再来做一份烤冷面:
public static void main(String[] args) {
Food roastColdNoodles = new RoastColdNoodles();
// 加一个鸡蛋
roastColdNoodles = new EggDecorator(roastColdNoodles);
// 加一包辣条
roastColdNoodles = new SpicyGlutenDecorator(roastColdNoodles);
System.out.println("套餐内容:" + roastColdNoodles.getDescription());
System.out.println("套餐价格:" + roastColdNoodles.getPrice());
}
输出结果为:
套餐内容:哈尔滨烤冷面 加一个鸡蛋 加一包辣条
套餐价格:13
同上,如果我们再加一些装饰类,或者再推出一些其它产品,都可以很方便的进行搭配组合。
装饰模式在Java中最典型的应用,就是I/O流。
先来演示一下:
public static void main(String[] args) throws IOException {
OutputStream outputStream = new FileOutputStream("E:\\MyEncrypt.txt");
outputStream = new BufferedOutputStream(outputStream);
outputStream = new DataOutputStream(outputStream);
//输出内容
outputStream.write("abc123".getBytes());
outputStream.close();
}
执行完代码后,文件中就会输出下边的内容。
abc123
简单的画下它们之间的层级图:
是不是跟做一套煎饼的那个例子很像啊。
OutputStream 是最原始的抽象组件对象,
FileOutputStream 是被装饰类,
FilterOutputStream 是装饰类的父类
BufferedOutputStream 和 DataOutputStream 是装饰类。
同样的,输入流也类似,这里就不再演示了。
既然IO流是采用装饰模式实现的,那如果我们想自己实现一个装饰类功能,该怎么实现呢?比较简单,例如我们想实现一个简单的加密功能,就是将字节数组中的每个字节往后移动两位,看代码:
/**
* 实现一个输出流的装饰类
* 将字节往后移动两位
*/
public class EncryptOutputStream extends FilterOutputStream {
public EncryptOutputStream(OutputStream out) {
super(out);
}
/**
* 将字节往后移动两位
* @param b
* @throws IOException
*/
@Override
public void write(int b) throws IOException {
super.write(b+2);
}
}
再来演示一下:
public static void main(String[] args) throws IOException {
OutputStream outputStream = new FileOutputStream("E:\\MyEncrypt.txt");
outputStream = new BufferedOutputStream(outputStream);
outputStream = new DataOutputStream(outputStream);
// 组合我们的加密装饰类(也就是将字节往后移动两位的功能组件)
outputStream = new EncryptOutputStream(outputStream);
//输出内容
outputStream.write("abc123".getBytes());
outputStream.close();
}
执行完后,文件中输出如下:
cde345
优点:
缺点: