大千世界,茫茫人海,相识就是一种缘分。若此篇文章对您有帮助,点个赞或点个关注呗!
前言
在了解每一种设计模式之前,我们都应该大体了解设计模式的具体分类以及不同设计模式的重要等级。设计模式的整体归类,已在第一篇Java设计模式中做出归类总结。点击查阅
✍装饰者模式定义
装饰者模式(Decorator Pattern):动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp),它是一种对象结构型模式。
装饰者模式以客户端透明的方式动态给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么区别。装饰者模式可以在不需要创建更多子类的情况下,将对象的功能加以扩展。这就是装饰者模式动机所在。
✍装饰者模式包含如下角色:
- Component:抽象主体 定义一个抽象类以规范准备接收附加功能的对象;
- ConcreteComponent:具体主体实现抽象主体接口,通过装饰者为其添加一些特定的功能;
- Decorator:抽象装饰类 继承抽象主体,并包含具体主体的实例,可以通过其子类扩展具体主体的功能;
- ConcreteDecorator:具体装饰类 实现抽象装饰类的相关方法,并给具体主体对象添加附加的功能。
✍ 具体代码示例:
不使用装饰者模式案例:
以购买路虎车为例:
package com.bnl.decoratorPattern;
/**
* @Author:bnli
* @Date:2020/3/8 16:50
*/
public class LuhuCar {
public String getDesc() {
return "购买路虎原车一辆";
}
public int cost() {
return 600000;
}
}
原车改装轮毂:
package com.bnl.decoratorPattern;
/**
* @Author:bnli
* @Date:2020/3/8 16:52
*/
public class RefitHub extends LuhuCar {
public String getDesc() {
return super.getDesc() + "------改装原车轮毂";
}
public int cost() {
return super.cost() + 10000;
}
}
原车改装疝气大灯:
package com.bnl.decoratorPattern;
/**
* @Author:bnli
* @Date:2020/3/8 16:57
*/
public class RefitHeadlight extends RefitHub {
public String getDesc() {
return super.getDesc() + "------改装原车疝气大灯";
}
public int cost() {
return super.cost() + 5000;
}
}
测试类:
package com.bnl.decoratorPattern;
/**
* @Author:bnli
* @Date:2020/3/8 16:59
*/
public class TestCount {
public static void main(String[] args) {
LuhuCar luhu = new LuhuCar();
System.out.println(luhu.getDesc() + ",售价为" + luhu.cost());
System.out.println("------------------------");
RefitHub refitHub = new RefitHub();
System.out.println(refitHub.getDesc() + ",售价为" + refitHub.cost());
System.out.println("------------------------");
RefitHeadlight lingnt = new RefitHeadlight();
System.out.println(lingnt.getDesc() + ",售价为" + lingnt.cost());
}
}
测试结果:
购买路虎原车一辆,售价为600000
------------------------
购买路虎原车一辆--改装原车轮毂,售价为610000
------------------------
购买路虎原车一辆--改装原车轮毂--改装原车疝气大灯,售价为615000
Process finished with exit code 0
✍转成UML图:
上述案例总结:
针对上述业务场景,如果需要继续添加需求,则会产生大量的类,实现最终的业务需求。为此,我们引入装饰者模式解决此类的弊端。
使用装饰者模式案例:
创建Drink (Component:抽象主体)
package com.bnl.DrinkDecorator;
/**
* @Author:bnli
* @Date:2020/3/8 21:01
*/
public abstract class Drink {
//饮品描述信息
public String des;
//饮品价格
private float price = 0.0f;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
//计算费用的抽象方法,子类来实现
public abstract float cost();
}
创建Coffee类
package com.bnl.DrinkDecorator;
/**
* @Author:bnli
* @Date:2020/3/8 21:04
*/
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
创建意大利咖啡( ConcreteComponent: 具体主体)
package com.bnl.DrinkDecorator;
/**
* @Author:bnli
* @Date:2020/3/8 21:05
*/
public class Espresso extends Coffee {
public Espresso() {
setDes(" 意大利咖啡 ");
setPrice(6.0f);
}
}
创建无因咖啡 ( ConcreteComponent: 具体主体)
package com.bnl.DrinkDecorator;
/**
* @Author:bnli
* @Date:2020/3/8 21:05
*/
public class DeCaf extends Coffee {
public DeCaf() {
setDes(" 无因咖啡 ");
setPrice(1.0f);
}
}
创建装饰者的抽象类(Decorator: 抽象装饰类)
package com.bnl.DrinkDecorator;
/**
* @Author:bnli
* @Date:2020/3/8 21:07
*/
public class Decorator extends Drink {
private Drink drink;
public Decorator(Drink drink) {
this.drink = drink;
}
@Override
public float cost() {
return super.getPrice() + drink.cost();
}
@Override
public String getDes() {
// drink.getDes() 输出被装饰者的信息
return des + " " + getPrice() + " && " + drink.getDes();
}
}
添加牛奶的装饰者(ConcreteDecorator: 具体装饰类)
package com.bnl.DrinkDecorator;
/**
* @Author:bnli
* @Date:2020/3/8 21:13
*/
public class Milk extends Decorator {
public Milk(Drink drink) {
super(drink);
setDes("牛奶");
setPrice(10.0f);
}
}
添加的装饰者(ConcreteDecorator: 具体装饰类)
package com.bnl.DrinkDecorator;
/**
* @Author:bnli
* @Date:2020/3/8 21:11
*/
//具体的Decorator, 添加需要的佐料
public class Chocolate extends Decorator {
public Chocolate(Drink drink) {
super(drink);
setDes(" 巧克力 ");
setPrice(3.0f); // 佐料的价格
}
}
测试类:
package com.bnl.DrinkDecorator;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Author:bnli
* @Date:2020/3/8 21:14
*/
public class TestCoffee {
public static void main(String[] args) {
//Test1、装饰者模式下的订单:2份牛奶+1份巧克力的意大利咖啡
//1、点一份意大利咖啡
Drink espresso = new Espresso();
System.out.println("费用:" + espresso.cost() + "元");
System.out.println("商品描述:" + espresso.getDes());
System.out.println("下单时间:"+new Date());
System.out.println("--------------------------------------");
//2. espresso加入一份牛奶
espresso = new Milk(espresso);
System.out.println("意大利咖啡 加入一份牛奶 费用 =" + espresso.cost());
System.out.println("意大利咖啡 加入一份牛奶 描述 = " + espresso.getDes());
System.out.println("--------------------------------------");
//3. espresso加入一份牛奶
espresso = new Milk(espresso);
System.out.println("意大利咖啡 加入两份牛奶 费用 =" + espresso.cost());
System.out.println("意大利咖啡 加入两份牛奶 描述 = " + espresso.getDes());
System.out.println("--------------------------------------");
//4. espresso加入一份巧克力
espresso = new Chocolate(espresso);
System.out.println("意大利咖啡 加入两份牛奶 加入一份巧克力 费用 =" + espresso.cost());
System.out.println("意大利咖啡 加入两份牛奶 加入一份巧克力 描述 = " + espresso.getDes());
}
}
测试结果:
费用:6.0 元
商品描述:意大利咖啡
下单时间:Sun Mar 08 21:33:15 CST 2020
--------------------------------------
意大利咖啡 加入一份牛奶 费用 =16.0
意大利咖啡 加入一份牛奶 描述 = 牛奶 10.0 && 意大利咖啡
--------------------------------------
意大利咖啡 加入两份牛奶 费用 =26.0
意大利咖啡 加入两份牛奶 描述 = 牛奶 10.0 && 牛奶 10.0 && 意大利咖啡
--------------------------------------
意大利咖啡 加入两份牛奶 加入一份巧克力 费用 =29.0
意大利咖啡 加入两份牛奶 加入一份巧克力 描述 = 巧克力 3.0 && 牛奶 10.0 && 牛奶 10.0 && 意大利咖啡
Process finished with exit code 0
✍转成UML图:
✍分析总结:
装饰者模式相对继承,装饰者模式的主要优势在于 不会破坏类的封装性,而且继承是一种耦合度较大的静态关系 ,无法在程序运行时动态扩展。在软件开发阶段 ,关联关系虽然不会比继承关系减少编码量 ,但是到了软件维护阶段 ,由于关联关系使系统有较好的松耦合性 ,因此使得系统更加容易维护。当然 ,关联关系的缺点是比继承关系要创建更多的对象 。使用装饰模式实现扩展比继承更加灵活 ,它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下 ,将对象的功能加以扩展。
✍拓展使用场景:
多重加密系统
比如我们在调用第三方的接口,需要传递相对密级的参数,需要对数据进行加密。通常我们可以对数据进行加密的最简单的加密算法通过对字母进行移位来实现,同时还提供了稍复杂的逆向输出加密,还提供了更为高级的求模加密。用户先使用最简单的加密算法对字符串进行加密,如果觉得还不够可以对加密之后的结果使用其他加密算法进行二次加密,当然也可以进行第三次加密。现使用装饰模式设计该多重加密系统。
✍装饰者模式的优缺点:
装饰模式的优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
装饰模式的缺点:
- 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度;
- 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
✍ 在以下情况下可以使用装饰模式:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类)。