设计模式之装饰器模式(十六)
在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象,这就是装饰器模式。
一、设计模式的角色
-
Component
定义一个对象接口,可以给这些对象动态地添加职责。 -
ConcreteComponent
定义了一个具体的对象,也可以给这个对象添加一些职责。 -
Decorator
装饰抽象类,继承了Component
,扩展了Component
类的功能,对于Component
无需知道Decorator
。 -
ConcreteDecorator
具体实现的装饰对象,给Component
添加职责的功能。
二、适配器模式与装饰器模式的区别
装饰器与适配器都有一个别名叫做包装模式(Wrapper
)(适配器模式包装是为了接口兼容性,装饰器模式包装是为了动态的添加方法或者是行为),它们都是起到包装一个类或对象的作用,但是使用时,它们的目的不一样。
适配器只需要将原接口转化为客户希望的另一个接口,就是适配器模式。转化无非2点:
继承原类或者实现原接口
持有原接口的对象,再实现目标接口。
在装饰器模式中,必须要有被装饰的类和装饰的类。装饰模式的一定是从外部传入的,并且可以没有顺序,按照实际的需求随意调换执行的顺序。 装饰模式注重的是功能的拓展。
三、这是一个很短很短的故事
小明和小白和小农去吃手抓饼,小明点了一个普通手抓饼、小白点了一个加蛋的手抓饼,小农点了一个奢华版的手抓饼(加蛋加牛肉)接下来我们用代码来实现吧。
- 蛋糕类
public interface Cake {
// 名字
public String nameDetails();
// 价格
public double price();
}
- 具体实现类(手抓饼类、加蛋手抓饼、加牛肉手抓饼、加蛋加牛肉手抓饼)
public class CakeImpl implements Cake {
@Override
public String nameDetails() {
return "普通版本手抓饼";
}
@Override
public double price() {
return 5.0;
}
}
public class EggCakeImpl implements Cake {
@Override
public String nameDetails() {
return "加蛋手抓饼";
}
@Override
public double price() {
return 6.0;
}
}
public class BeefCakeImpl implements Cake {
@Override
public String nameDetails() {
return "加牛肉手抓饼";
}
@Override
public double price() {
return 7.0;
}
}
public class BeefAndEggCakeImpl implements Cake {
@Override
public String nameDetails() {
return "加蛋加牛肉的手抓饼";
}
@Override
public double price() {
return 8.0;
}
}
- 测试类
public static void main(String [] args) {
Cake cake = new CakeImpl();
System.out.println("小明点了一份:"+cake.nameDetails()+"价格:"+cake.price());
Cake cake1 = new EggCakeImpl();
System.out.println("小白点了一份:"+cake1.nameDetails()+"价格:"+cake1.price());
Cake cake2 = new BeefAndEggCakeImpl();
System.out.println("小农点了一份:"+cake2.nameDetails()+"价格:"+cake2.price());
}
- 测试结果
小明点了一份:普通版本手抓饼价格:5.0
小白点了一份:加蛋手抓饼价格:6.0
小农点了一份:加蛋加牛肉的手抓饼价格:8.0
通过业务我们可以想到会有多种形式的手抓饼的类型,手抓饼我们可以看成对象,对象有多种组合,比如手抓饼可能有十几种原材料,如果有多种组合方式,我们就需要多种类。目前3种原材料就需要多种类进行实现,这样做是可以实现的,但是不符合设计模式编程,接下来我们用装饰器模式进行实现。
抽象对象类
public interface Cake {
// 名字
public String nameDetails();
// 价格
public double price();
}
基础对象类
public class CakeImpl implements Cake {
@Override
public String nameDetails() {
return "手抓饼";
}
@Override
public double price() {
return 5.0;
}
}
装饰者抽象类
public abstract class CakeDecorator implements Cake {
Cake cake;
public CakeDecorator (Cake cake) {
this.cake = cake;
}
@Override
public String nameDetails() {
return cake.nameDetails();
}
@Override
public double price() {
return cake.price();
}
}
具体装饰者类
public class EggCakeImpl extends CakeDecorator {
public EggCakeImpl(Cake cake) {
super(cake);
}
@Override
public String nameDetails() {
return "鸡蛋"+cake.nameDetails();
}
@Override
public double price() {
return cake.price()+1.5;
}
}
public class BeefCakeImpl extends CakeDecorator {
public BeefCakeImpl(Cake cake) {
super(cake);
}
@Override
public String nameDetails() {
return "牛肉"+cake.nameDetails();
}
@Override
public double price() {
return cake.price()+2.0;
}
}
测试类
public static void main(String [] args) {
Cake cake = new CakeImpl();
System.out.println("小明点了一份:"+cake.nameDetails() +"价格:"+cake.price());
Cake cake1 = new EggCakeImpl(cake);
System.out.println("小白点了一份:"+cake1.nameDetails() +"价格:"+cake1.price());
// 标识了制作顺序与流程,再生产中流水线制作程序时,可能会比较严格的把控,制作流程。
// 装饰者模式不仅仅完成操作细节的流程,提供很好的调用方式。
Cake cake3 = new BeefCakeImpl(cake1);
System.out.println("小农点了一份:"+cake3.nameDetails() +"价格:"+cake3.price());
}
测试结果
小明点了一份:手抓饼价格:5.0
小白点了一份:鸡蛋手抓饼价格:6.0
小农点了一份:牛肉鸡蛋手抓饼价格:8.0
四、适配器模式的优缺点
- 优点
装饰者模式可以提供比继承更多的灵活性。
可以通过一种动态的方式来扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为。
通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
- 缺点
会产生很多的小对象,增加了系统的复杂性 。
这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
五、 应用场景
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。 当不能采用继承的方式对系统进行扩展或者继承。
六、jdk中的应用
java.io.BufferedInputStream(InputStream)
java.io.DataInputStream(InputStream)
java.io.BufferedOutputStream(OutputStream)
七、源码在这里
本文的代码GitHub地址:
https://github.com/xiaonongOne/decorator-test