装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
情景
在厦门的小伙伴们一定对沙茶面很熟悉,作为厦门的一个特色小吃,大街小巷到处能看到挂有“沙茶面”招牌的面馆,味道鲜美,可以加各种配料,是外地游客必吃的一种小吃。
假设面馆现在要做一个沙茶面订单系统,能计算出每一碗面的价格。而面馆有两种面,一种沙茶面,一种清汤面,两种面有不同的定价,一开始我们可能会这样设计的,定义两种面:
每一碗面顾客可以加配料,有瘦肉、猪肝、鱿鱼等,每一种配料都有不同的价格,清汤面和沙茶面加了不同的料更是有多种价格,接下去怎么处理呢?可能你会想到用成员变量加继承方式,如下:
这样每一碗面都可以设置自己的配料,在计算价格调用cost()时就可以根据Nooddle对象是否含有对应的配料,有的话再加上配料的价钱就能计算出这碗面的价格;
缺点
这样处理有点问题,假如现在面馆又增加了一种配料,比如老板今天新进了花蛤、肉丸,这时候Nooddle基类还得加上这两种配料的成员,每加一种配料,基类就要改动一次,这样明显违背了设计模式:对扩展开发,对修改关闭的重要原则。
方案
这时候我们就可以使用装饰器模式了,它能使我们加每一种调料时可以不改变原有的面的属性,极大地提高了灵活性和可扩展性,下面介绍一下通过装饰模式处理的步骤:
1. 创建Nooddle基类
抽象面条类有mDescription标注是哪一种面条,mPrice为定价,每一种面的价格和名称都在子类重写
public abstract class Nooddle {
public String mDescription = "Unknow Nooddle";
public float mPrice = 0.0f;
public String getDescription(){
return mDescription;
}
public float cost(){
return mPrice;
}
}
2. 实现两种面:沙茶面和清汤面
假设沙茶面每碗不含任何配料定价15块,清汤面每碗不含任何配料定价10块
public class SatayNooddle extends Nooddle {
@Override
public String getDescription() {
return "SatayNooddle";
}
@Override
public float cost() {
return 15.0f;
}
}
public class LightSoupNooddle extends Nooddle {
@Override
public String getDescription() {
return "LightSoupNooddle";
}
@Override
public float cost() {
return 10.0f;
}
}
3. 创建抽象装饰类
这个类持有一个Nooddle对象,而自己本身也是Nooddle类型,这样能保持动作一致性,都有cost()方法,而且cost可以在调用成员变量的cost()方法基础上再加上自己额外的处理,即能计算被装饰者的价格外还能加上自己的价格
abstract class DecorateNooddle extends Nooddle {
public Nooddle mNooddle;
public DecorateNooddle(Nooddle nooddle){
mNooddle = nooddle;
}
@Override
public String getDescription() {
return mNooddle.getDescription() + mDescription;
}
@Override
public float cost() {
return mNooddle.cost() + mPrice;
}
}
4. 实现两种配料:瘦肉和猪肝,它们属于装饰者
public class LeanBurdening extends DecorateNooddle {
public LeanBurdening(Nooddle nooddle) {
super(nooddle);
}
@Override
public String getDescription() {
mDescription = " + Lean";
return super.getDescription();
}
@Override
public float cost() {
mPrice = 1.5f;
return super.cost();
}
}
public class LiverBurdening extends DecorateNooddle {
public LiverBurdening(Nooddle nooddle) {
super(nooddle);
}
@Override
public String getDescription() {
mDescription = " + Liver";
return super.getDescription();
}
@Override
public float cost() {
mPrice = 2.0f;
return super.cost();
}
}
这样准备工作就做好了,这里总结一下装饰模式的工作流程,假设一个顾客点了一碗沙茶面,加了瘦肉和猪肝配料,这是就有如下处理:
1. 创建一个沙茶面对象;
2. 创建一个瘦肉配料对象,这个配料装饰了沙茶面,即沙茶面对象是自己的成员变量,要计算价格时就可以把自己的价格机上沙茶面的价格;
3. 创建一个猪肝对象,同第2点;
4. 由于装饰对象两种配料和被装饰对象沙茶面都是Nooddle类型,所以计算价格时只需要计算最外围的装饰对象的价格就能得出这碗面的总价格了。
用如下代码处理:
public class NooddleStoreDemo {
public static void main(String[] args){
Nooddle satayNooddle = new SatayNooddle();
satayNooddle = new LiverBurdening(satayNooddle);
satayNooddle = new LeanBurdening(satayNooddle);
System.out.println(satayNooddle.getDescription() + ", price: " + satayNooddle.cost());
Nooddle lightSoupNooddle = new LightSoupNooddle();
lightSoupNooddle = new LiverBurdening(lightSoupNooddle);
lightSoupNooddle = new LeanBurdening(lightSoupNooddle);
System.out.println(lightSoupNooddle.getDescription() + ", price: " + lightSoupNooddle.cost());
}
}
看下输出的结果:
SatayNooddle + Liver + Lean, price: 18.5
LightSoupNooddle + Liver + Lean, price: 13.5
总结
一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。这时就可以动态地给一个对象添加一些额外的职责,这样装饰类和被装饰类能独立发展,不相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
示例代码戳这里。