概述
装饰者模式的实现其实就是 继承+聚合。
定义:在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
装饰(Decorator)模式中的角色:
- 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
在什么情况下我们会使用装饰者模式呢?
网上有一个非常经典的案例,比如咖啡店点餐或者小吃店点餐此类,这里拿小吃店点餐为例。
点餐势必要使用菜单,菜单上有菜单项,而每个菜单项都是由主菜+依附主菜的附加菜组成。
暂且不考虑附加菜是否能添加到每一个主菜这种关系,暂且理解为主菜和附加菜为平行关系,可以随意组合。
那么其实就是菜单项=主菜+附加菜。代码实现模式其实也非常的简单。
public class Item{
Master master;
List<Addition> additions;
}
好,现在就此打住,这和装饰者模式无关,这通常也被称作为组合模式,不过这也非常符合我们的日常开发习惯。
如果不用组合模式呢,按照我们oop思想的一切都是对象的角度来看,我们需要为每个 主菜+附加菜 的组合来定义一个类,形成一个 n*m 的格式,这不用多说都会形成一个类爆炸的形式。
因此,为了制止类大爆炸的情况,又不去额外的增加一个组合类,那么我们可以重新审视 主菜+附加菜 之间的关系。
我们可以发现,主菜,加不加上附加菜,其实都是主菜的,附加菜是无法依赖主菜存在的,所以其实附加菜只是对于主菜的一个附加,本质上这还是一个主菜。
也就是说,我们需要动态的为主菜添加功能,这也正是我们对于 装饰者模式 的定义。
实现大概如此,然后重写主菜中的每一个方法,为其添加自己的逻辑,并调用主菜原本的逻辑。
public class Addition extends Main{
private Main main;
}
实践案例实现
用刚才的饭店举例,不过使用快餐店的形式。
//快餐接口
public abstract class FastFood {
private float price;
private String desc;
public FastFood() {
}
public FastFood(float price, String desc) {
this.price = price;
this.desc = desc;
}
public void setPrice(float price) {
this.price = price;
}
public float getPrice() {
return price;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public abstract float cost(); //获取价格
}
//炒饭
public class FriedRice extends FastFood {
public FriedRice() {
super(10, "炒饭");
}
public float cost() {
return getPrice();
}
}
//炒面
public class FriedNoodles extends FastFood {
public FriedNoodles() {
super(12, "炒面");
}
public float cost() {
return getPrice();
}
}
//配料类
public abstract class Garnish extends FastFood {
private FastFood fastFood;
public FastFood getFastFood() {
return fastFood;
}
public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}
public Garnish(FastFood fastFood, float price, String desc) {
super(price,desc);
this.fastFood = fastFood;
}
}
//鸡蛋配料
public class Egg extends Garnish {
public Egg(FastFood fastFood) {
super(fastFood,1,"鸡蛋");
}
public float cost() {
return getPrice() + getFastFood().getPrice();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
//培根配料
public class Bacon extends Garnish {
public Bacon(FastFood fastFood) {
super(fastFood,2,"培根");
}
@Override
public float cost() {
return getPrice() + getFastFood().getPrice();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
//测试类
public class Client {
public static void main(String[] args) {
//点一份炒饭
FastFood food = new FriedRice();
//花费的价格
System.out.println(food.getDesc() + " " + food.cost() + "元");
System.out.println("========");
//点一份加鸡蛋的炒饭
FastFood food1 = new FriedRice();
food1 = new Egg(food1);
//花费的价格
System.out.println(food1.getDesc() + " " + food1.cost() + "元");
System.out.println("========");
//点一份加培根的炒面
FastFood food2 = new FriedNoodles();
food2 = new Bacon(food2);
//花费的价格
System.out.println(food2.getDesc() + " " + food2.cost() + "元");
}
}
源码
io
说到 FilterInputStream 大家可能不能意识到这是什么,但是说 BufferedInputStream 大家一定能知道。
BufferedInputStream 是io包下使用非常频繁的缓冲流,能加快io速度。
在java的io包下,像这样的为io类增加新功能,但是又不影响使用的类有很多:
- PushbackInputStream:允许将读取的字节退回到输入流中,以便稍后再次读取。它提供了 unread() 方法来实现这个功能。
- LineNumberInputStream:可以对输入流进行逐行编号,它提供了 getLineNumber() 方法来获取当前行号。
- ObjectInputStream:用于从输入流中读取对象。它提供了反序列化的功能,可以将字节流转换为对象。
- SequenceInputStream:可以将多个输入流组合成一个逻辑上的连续输入流。它按照它们被传递给构造函数的顺序依次读取这些输入流。
而这些类都是继承于FilterInputStream,我们点进FilterInputStream中去看,他继承了InputStream,又聚合了InputStream,在重写的方法中都是调用in中的函数,这便是经典的装饰者模式。