装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有类的一个包装。装饰器在代码程序中适用于以下场景:
- 用于扩展 一个类的功能或者给一个类添加附加职责
- 动态的给一个对象添加功能,这些功能可以再动态的撤销
为什么要用装饰器模式
装饰器模式能很好的解决过多的继承所带来的问题,下面我们通过一个例子来看看装饰器模式的作用。生活中我们很多人都很喜欢喝奶茶,只喝奶茶又感觉有点太单调,会加一些配料,比如珍珠、脆啵啵、芋圆等,下面我们通过代码来模拟一下。
最原始不放任何调料的特调奶茶
/**
* @author: Winston
* @createTime: 2021/7/1
*/
public class MilkTea {
public String info() {
return "特调奶茶";
}
public double price() {
return 8D;
}
}
由于我想吃点珍珠,于是我通过继承的方式给奶茶增加珍珠
public class MilkTearWithPearl extends MilkTea {
@Override
public String info() {
return super.info() + "加珍珠";
}
@Override
public double price() {
// 奶茶加上珍珠的价格
return super.price() + 2D;
}
}
我还想要一杯珍珠加上脆啵啵的奶茶
public class MilkTearWithPearlAndBoo extends MilkTea {
@Override
public String info() {
return super.info() + "加珍珠加脆啵啵";
}
@Override
public double price() {
// 加珍珠加脆啵啵后的价格
return super.price() + 2D + 3D;
}
}
我同事想要一杯加珍珠加脆啵啵加西米露的奶茶
public class MilkTearWithPearlAndBooAndSago extends MilkTea {
@Override
public String info() {
return super.info() + "加珍珠加脆啵啵加西米露";
}
@Override
public double price() {
// 加珍珠加脆啵啵加西米露后的价格
return super.price() + 2D + 3D + 2D;
}
}
测试类
public class MilkTeaTest {
public static void main(String[] args) {
MilkTea milkTea = new MilkTea();
System.out.println(milkTea.info() + ",总价格:"+milkTea.price());
System.out.println("====================================================");
MilkTearWithPearl milkTearWithPearl = new MilkTearWithPearl();
System.out.println(milkTearWithPearl.info() + ",总价格:"+milkTearWithPearl.price());
System.out.println("====================================================");
MilkTearWithPearlAndBoo milkTearWithPearlAndBoo = new MilkTearWithPearlAndBoo();
System.out.println(milkTearWithPearlAndBoo.info() + ",总价格:"+milkTearWithPearlAndBoo.price());
System.out.println("====================================================");
MilkTearWithPearlAndBooAndSago milkTearWithPearlAndBooAndSago = new MilkTearWithPearlAndBooAndSago();
System.out.println(milkTearWithPearlAndBooAndSago.info() + ",总价格:"+milkTearWithPearlAndBooAndSago.price());
System.out.println("====================================================");
}
}
特调奶茶,总价格:8.0
====================================================
特调奶茶加珍珠,总价格:10.0
====================================================
特调奶茶加珍珠加脆啵啵,总价格:13.0
====================================================
特调奶茶加珍珠加脆啵啵加西米露,总价格:15.0
====================================================
从上面的例子我们可以看出只要加的小料不同那么我就要新创建一个类来继承奶茶类MikTea,如果只有三种料珍珠、芋圆、脆啵啵不重复添加,根据排列组合就有6种方式,更何况有可能有的人会要双份珍珠,这么一来我们的类就特别特别多,非常的冗余。有没有什么方式能够进行改造呢?下面我们就用装饰器模式将上面的案例进行改造,让大家体会一下用了它有何不同。
装饰器模式改造案例
首先先创造一个奶茶的抽象类MilkTea,所有的奶茶都必须有介绍信息和价格信息
public abstract class MilkTea {
/**
* 奶茶中的信息
*/
protected abstract String info();
/**
* 奶茶总价格,这个方法需要在具体实现类中实现
*
* @return
*/
protected abstract double price();
}
创造一个基础款奶茶,要继承基类MilkTea
public class BaseMilkTea extends MilkTea{
private String info = "本店招牌特调奶茶";
@Override
protected String info() {
return this.info;
}
@Override
protected double price() {
return 12D;
}
}
再创建一个巧克力奶茶类,同样要继承基类MilkTea
public class ChocolateMilkTea extends MilkTea {
private String info = "巧克力奶茶";
@Override
protected String info() {
return this.info;
}
@Override
protected double price() {
return 15D;
}
}
创建一个奶茶的装饰类MilkTeaDecorate,同样这个类也继承MilkTea,这里大家可能会有疑问,既然MilkTeaDecorate这个抽象类的方法和MilkTea抽象类的方法一样为什么还要单独写一个类。这里的话是为了能够区分出哪个是装饰者,哪个是被装饰者。
public abstract class MilkTeaDecorate extends MilkTea {
/**
* 要添加小料的奶茶,通过构造函数传入奶茶信息
*/
public MilkTea milkTea;
public MilkTeaDecorate(MilkTea milkTea) {
this.milkTea = milkTea;
}
/**
* 所有的调料装饰者都必须重新实现info()方法
* 这样才能够得到所选奶茶的整体描述
*
* @return
*/
@Override
protected String info() {
return this.info();
}
/**
* 所有的调料装饰者都必须重新实现price()方法
* 这样才能够得到所选奶茶的整体价格
*
* @return
*/
@Override
protected double price() {
return this.price();
}
}
编写珍珠装饰类
public class PearlDecorate extends MilkTeaDecorate {
public PearlDecorate(MilkTea milkTea) {
super(milkTea);
}
@Override
protected double price() {
return this.milkTea.price() + 2D;
}
@Override
protected String info() {
return this.milkTea.info() + "加一份珍珠";
}
}
脆啵啵装饰类
public class BooDecorate extends MilkTeaDecorate {
public BooDecorate(MilkTea milkTea) {
super(milkTea);
}
@Override
protected double price() {
return this.milkTea.price() + 3D;
}
@Override
protected String info() {
return this.milkTea.info() + "加一份波波";
}
}
西米露装饰类
public class SagoDecorate extends MilkTeaDecorate {
public SagoDecorate(MilkTea milkTea) {
super(milkTea);
}
@Override
protected double price() {
return this.milkTea.price() + 3D;
}
@Override
protected String info() {
return this.milkTea.info() + "加一份西米露";
}
}
测试类:
public class MilkTeaTest {
public static void main(String[] args) {
//1.一份原始的特调奶茶
System.out.println("=======================================");
MilkTea milkTea;
milkTea = new BaseMilkTea();
System.out.println(milkTea.info() + "总价价格:" + milkTea.price());
// 想要加一份珍珠
milkTea = new PearlDecorate(milkTea);
System.out.println(milkTea.info() + "总价价格:" + milkTea.price());
// 加一份脆啵啵
milkTea = new BooDecorate(milkTea);
System.out.println(milkTea.info() + "总价价格:" + milkTea.price());
//1.一份巧克力奶茶
System.out.println("=======================================");
MilkTea milkTea2;
milkTea2 = new ChocolateMilkTea();
System.out.println(milkTea2.info() + "总价价格:" + milkTea2.price());
// 想要加一份珍珠
milkTea2 = new PearlDecorate(milkTea2);
System.out.println(milkTea2.info() + "总价价格:" + milkTea2.price());
// 加一份脆啵啵
milkTea2 = new BooDecorate(milkTea2);
System.out.println(milkTea2.info() + "总价价格:" + milkTea2.price());
// 再加一份珍珠
milkTea2 = new PearlDecorate(milkTea2);
System.out.println(milkTea2.info() + "总价价格:" + milkTea2.price());
System.out.println("=======================================");
}
}
结果:
=======================================
本店招牌特调奶茶总价价格:12.0
本店招牌特调奶茶加一份珍珠总价价格:14.0
本店招牌特调奶茶加一份珍珠加一份波波总价价格:17.0
=======================================
巧克力奶茶总价价格:15.0
巧克力奶茶加一份珍珠总价价格:17.0
巧克力奶茶加一份珍珠加一份波波总价价格:20.0
巧克力奶茶加一份珍珠加一份波波加一份珍珠总价价格:22.0
=======================================
案例小结
通过上面这个案例,我们可以看出装饰者(装饰器)模式的特点。
- 装饰者类拥有被装饰者类的对象,一般是当做构造函数传入的。
- 在装饰者类当中调用被装饰者类的方法,封装成新的功能方法。
- 装饰者模式主要是利用多态,将子类对象作为参数互相传递(主要是为了传递实现的函数),达到互相装饰的效果,从而减少代码重复率,优化代码结构。
从上面的例子我们又可以看出跟静态代理有所相似,静态代理的被代理类和代理类都实现了同一接口,都是对类功能进行加强。这边装饰者模式和静态代理有何区别的?装饰者模式和静态代理的最大区别就是职责不同。代理模式(Proxy Pattern),为其它对象提供一种代理以控制对这个对象的访问。装饰模式(Decorator Pattern),动态地给一个对象添加一些额外的职责。换句话说:代理模式的目标是控制对被代理对象的访问,而装饰模式是给原对象增加额外功能。虽然代理模式也可以实现对被代理对象功能的增强,但其核心是隐藏对被代理类的访问。