什么是装饰者设计模式 DecoratorPattern
- 在不改变对象的功能结构情况下动态的给该对象添加职责,关注与动态的,动态是可撤销的,提供不同的装饰类,通过不同的行为组合,实现职责添加,比继承方式更加灵活
- 装饰者设计模式中角色的区分: 被装饰者,装饰者,根据需求,装饰者与被装饰者,可能同时存在多个,提供被装饰者公共抽象,装饰者公共抽象,被装饰者与装饰者共同实现装饰者抽象,将被装饰者注入到装饰者中?
- 生活中装饰者设计模式的案例: 点饮料系统,客户购买饮料,可能购买咖啡,可能购买果汁,拿咖啡举例,咖啡又分为白咖啡,黑咖啡,可以单点,也可以向咖啡中添加不同种类,不同数量,不同价格的调味品,例如向咖啡中添加糖,牛奶,豆浆等
- 在点咖啡时,咖啡就是被装饰者,添加的调味品就是装饰者,根据咖啡,果汁等提供装饰者公共抽象,调味品也要不同种类,提供装饰者公共抽象,客户在点饮料时,可以添加调味品,也可以不添加调味品,此为动态的,可撤销的
代码示例
通过装饰者模式实现上面的点咖啡案例
- 创建被装饰者公共抽象(也就是饮料系统,饮料系统可能是咖啡,果汁,酒…)
//整个饮料超类(被装饰者,与装饰者中都存在了"类型","价格"两个维度
//故被装饰者,装饰者都实现该接口)
abstract class Drink2{
//饮料类型
private String drinkType;
//饮料价格
private int price;
//通过构造器对赋值
public Drink2(String drinkType, int price){
this.drinkType = drinkType;
this.price = price;
}
//返回当前点的饮料总价格(在饮料中直接返回当前饮料单价即可,
//在调味品中需要返回单品+调味品,所以写成抽象的,子类根据需求自己重写)
public abstract int cost();
//不同的饮料,调味品描述不同,有实际子类重写
abstract String desc();
//提供get, set 方法
public void setDrinkType(String drinkType) {
this.drinkType = drinkType;
}
public int getPrice() {
return price;
}
public String getDrinkType(){
return drinkType;
}
public void setPrice(int price) {
this.price = price;
}
}
- 创建被装饰者咖啡,分为黑咖啡,白咖啡
//咖啡的父类,装饰者用同样存在价格,类型,继承Drink2抽象类
abstract class Coffee extends Drink2{
public Coffee(String drinkType, int price){
super(drinkType, price);
}
//咖啡是单个饮品,计算中价格直接返回单价即可
@Override
public int cost() {
return super.getPrice();
}
//描述,由实际子类重写
public abstract String desc();
}
//黑咖啡,继承咖啡类,并重写抽象方法,抽象方法可以看为是属于该类产品的逻辑代码
class BrackCoffee2 extends Coffee{
public BrackCoffee2(String drinkType, int price){
super(drinkType, price);
}
@Override
public String desc() {
return "制作"+this.getDrinkType();
}
}
//白咖啡,继承咖啡类,并抽象抽象方法
class WhiteCoffee2 extends Coffee{
public WhiteCoffee2(String drinkType, int price){
super(drinkType, price);
}
@Override
public String desc() {
return "制作"+this.getDrinkType();
}
}
- 创建公共的装饰者抽象,装饰者中注入一个被装饰者,在后续的操作中,在装饰者中获取被装饰者,注意cost()计算总价格的方式
//公共的装饰者此处有糖, 牛奶, 豆浆多种
//装饰者中同样有着 单价, 类型两个属性,继承Drink2即可
abstract class Decorator extends Drink2{
//装饰者中注入一个被装饰者
private Drink2 drink2;
public Decorator(String drinkType, int price, Drink2 drink2){
super(drinkType,price);
this.drink2 = drink2;
}
//装饰者自己的价格 + 饮料的价格
//(调用的是cost()方法,该方法返回的是上次整个饮料+装饰者自己的价格)
@Override
public int cost() {
return super.getPrice()+ drink2.cost();
}
public void drinkInfo(){
System.out.println(desc());
}
}
- 具体装饰者,可以向咖啡中添加"糖",“牛奶”,“豆浆” 这些都是实际的装饰者
//糖 继承Decorator,并重写抽象方法
class Sugar2 extends Decorator{
public Sugar2(String type, int price,Drink2 drink2){
super(type, price, drink2);
}
@Override
public String desc() {
return "添加一份糖逻辑代码执行,制作调味品糖";
}
}
//牛奶
class Milk2 extends Decorator{
public Milk2(String type, int price,Drink2 drink2){
super(type, price, drink2);
}
@Override
public String desc() {
return "添加一份牛奶逻辑代码执行,制作调味品牛奶";
}
}
//豆浆
class Soybean2 extends Decorator{
public Soybean2(String type, int price, Drink2 drink2){
super(type, price, drink2);
}
@Override
public String desc() {
return "添加一份豆浆逻辑代码执行,制作调味品豆浆";
}
}
- 调用测试
public class Test2 {
public static void main(String[] args) {
//new BrackCoffee2()创建的是单个饮品,黑咖啡
Drink2 drink2 = new BrackCoffee2("黑咖啡",15);
//drink2调用的cost()方法中只是返回的单个咖啡的价格
System.out.println(drink2.desc()+drink2.cost());
//加一分牛奶
//在new Milk2()时,将饮品添赋值给组合到装饰者中的被装饰者上
drink2 = new Milk2("牛奶",5, drink2);
//此时调用的const()方法时当前调味品的单价+当前饮料的单价
System.out.println(drink2.desc()+"共计"+drink2.cost());
//再加一分豆浆
//在new Soybean2()时,将存有了一份牛奶,一份黑咖啡的Drink2存入了新的Soybean2中
drink2 = new Soybean2("豆浆", 1, drink2);
//调用const()方法,实际是获取的当前调味品的单价+递归调用的也是const()
//获取上层调味品单价...+饮品单价
System.out.println(drink2.desc()+"共计"+drink2.cost());
}
}
- 流程图(不是UML)
JDK 中装饰者设计模式使用案例
查看java中的io包下发现io流就是使用装饰者设计模式实现的,例如父接口InputStream相当于此处共同实现的Drink接口, InputStream下的的实现子类: FileInputStream, StringBufferInputStream,ByteArrayInputStream相当于此处的Coffee被装饰者,在继续查看,在使用FileInputStream时,可以使用管道流将其包裹,例如使用,BufferInputStream(new FileInputStream("路径)),DataInputStream(new FileInputStream("路径))等相当于此处的具体调味品糖,牛奶等,而这两个流的父类FilterInputStream相当于此处的Decorator 装饰者的公共抽象,在FilterInputStream中注入了一个InputStream装饰者属性
以上案例的另外一种实现方式
(不知道这是个啥,但是是实际工作中经常用到的一种,实现方式)
- 思考这种模式与装饰者设计模式带来的不同(与装饰者刚好相反,饮料中注入集合调味品,调味品中添加数量属性,感觉这种更关注的是结果,方便获取调味品的数量,装饰者模式更关注的是执行流程,创建一个调味品,执行一次逻辑)
- 饮品超类
abstract class Drink{
//饮料中包含调味品,一份饮料可能包含多份调味品所以是List集合
private List<Additive> additiveList;
//饮品价格
private int coffeePrice;
//饮品类型
private String drinkType;
public Drink(String drinkType, int coffeePrice){
this.drinkType = drinkType;
this.coffeePrice = coffeePrice;
}
//输出本次点饮料的详细信息
public void coffeeInfo(List<Additive> additiveList){
//如果additiveList不为空,说明饮料中添加了调味品
//输出调味品的种类,单价,份数,调味品总计,饮料总计
if(null !=additiveList && additiveList.size()>0){
int sum = 0;
for(Additive additive : additiveList){
System.out.println(additive.getAdditiveType()
+additive.count+"份,每份:"+additive.price+"元");
sum += additive.sumPrice();
}
System.out.print("购买一份"+drinkType+",单价:"+this.coffeePrice
+",添加调味品花费:"+sum
+"元,总计花费:" +(coffeePrice+sum));
}else {
System.out.print("购买一份"+drinkType+",单价:"
+this.coffeePrice+"元,总计花费:" +coffeePrice);
}
}
//提供get, set 方法
public void setAdditiveList(List<Additive> additiveList) {
this.additiveList = additiveList;
}
//......
}
- 调味品父类
abstract class Additive{
//添加饮料的数量
protected int count;
//添加饮料的单价
protected int price;
//调味品类型
protected String additiveType;
public Additive(String additiveType, int price, int count){
this.additiveType = additiveType;
this.price = price;
this.count = count;
}
abstract String desc();
public int sumPrice(){
return this.count*this.price;
}
public String getAdditiveType() {
return additiveType;
}
- 调用测试
public class Test {
public static void main(String[] args) {
//创建向饮料中添加的调味品,可能添加多种,多份调味品
//Additive是调味品的抽象父接口,nwe Milk是调味品的具体子类牛奶
//调味品中有"类型","单价","数量"三个属性,
Additive mAdditive = new Milk("牛奶",5,2);
//new Soybena是调味品糖,
Additive sAdditive = new Soybean("糖",2,1);
List<Additive> additiveList = new ArrayList<>();
additiveList.add(mAdditive);
additiveList.add(sAdditive);
//创建饮品,设置饮品类型,单价
Drink brackCoffee = new BrackCoffee("黑咖啡", 15);
//向饮品中添加调味品
brackCoffee.setAdditiveList(additiveList);
//调用执最终的逻辑代码
brackCoffee.coffeeInfo(additiveList);
}
}
业务与设计模式落地案例
- 在线商城的订单购买流程中,常常有各种优惠活动,例如满减、打折、赠送礼品等。可以使用装饰者模式来实现这些活动的计算。定义一个基础订单类Order,然后针对不同的优惠活动定义不同的装饰者类,例如DiscountDecorator类用于计算打折优惠,GiftDecorator类用于计算赠送礼品等。最后,通过设计一个装饰者链,按照优先级依次对订单进行装饰,即可实现订单优惠活动的自由组合
- 创建基础订单类Order,并定义计算订单价格的方法calculatePrice()
import lombok.Data;
@Data
public class Order {
private double price;
public void calculatePrice() {
// 计算原始订单价格
}
}
- 创建抽象装饰类OrderDecorator,并继承基础订单类Order,通过组合方式引用基础订单类实例,同时定义相应的装饰方法,例如计算打折优惠的calculateDiscount()方法
public abstract class OrderDecorator extends Order {
protected Order order;
public OrderDecorator(Order order) {
this.order = order;
}
@Override
public void calculatePrice() {
order.calculatePrice();
}
}
- 创建具体装饰类DiscountDecorator和GiftDecorator,分别继承抽象装饰类OrderDecorator,实现相应的装饰方法,计算打折优惠和赠送礼品等操作,并在构造函数中调用父类构造函数,传入基础订单类实例,进行装饰
public class DiscountDecorator extends OrderDecorator {
public DiscountDecorator(Order order) {
super(order);
}
@Override
public void calculatePrice() {
super.calculatePrice();
// 计算打折优惠
setPrice(getPrice() * 0.9);
}
}
public class GiftDecorator extends OrderDecorator {
public GiftDecorator(Order order) {
super(order);
}
@Override
public void calculatePrice() {
super.calculatePrice();
// 计算赠送礼品
}
}
- 创建装饰者链,按照优先级依次对订单进行装饰。我们可以使用Spring中的@Configuration和@Bean注解来实现,例如下面的例子中定义了一个orderDecorator,它将DiscountDecorator和GiftDecorator进行组合,并按照优先级先计算打折再计算赠送礼品
@Configuration
public class OrderConfig {
@Bean
public Order basicOrder() {
Order order = new Order();
order.setPrice(100);
return order;
}
@Bean
public DiscountDecorator discountDecorator(Order basicOrder) {
return new DiscountDecorator(basicOrder);
}
@Bean
public GiftDecorator giftDecorator(DiscountDecorator discountDecorator) {
return new GiftDecorator(discountDecorator);
}
@Bean
public OrderDecorator orderDecorator(GiftDecorator giftDecorator) {
return new OrderDecorator(giftDecorator);
}
}
优化1
- 上方实现装饰者设计模式时将业务对象直接注入到框架中会造成业务跟框架之间的耦合过大,使用Spring的AOP机制通过定义切面和切点,动态地在切点处对业务对象进行装饰
- 定义一个基础订单类Order,并定义calculatePrice()方法,该方法将用于计算原始订单价格
public class Order {
private double price;
public void calculatePrice() {
// 计算原始订单价格
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
- 定义切面类OrderDecoratorAspect,该类用于实现装饰行为。在该类中定义一个before方法,该方法使用Around通知类型,在切点处对业务对象进行装饰,例如添加打折、赠礼物等操作
@Aspect
@Component
public class OrderDecoratorAspect {
@Around("execution(* com.example.demo.Order.calculatePrice(..))")
public void before(ProceedingJoinPoint joinPoint) throws Throwable {
Order order = (Order) joinPoint.getTarget();
// 进行装饰操作,例如添加打折、赠礼物等操作
double price = order.getPrice();
order.setPrice(price * 0.9);
// 继续执行原始方法
joinPoint.proceed();
}
}
优化2
- 上方使用AOP方式也存在与框架耦合的问题,可以使用java8的Lambda表达式和函数式接口,将行为传递给方法或者类,从而实现动态的行为组合
- 定义基础的订单类Order,并定义calculatePrice()方法,该方法将用于计算原始订单价格
public class Order {
private double price;
public void calculatePrice() {
// 计算原始订单价格
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
- 定义一个装饰器接口Decorator,该接口中定义一个decorate()方法,用于在业务对象上添加额外的装饰行为。由于使用函数式编程,这个接口可以是一个函数式接口,例如:
@FunctionalInterface
public interface Decorator {
void decorate(Order order);
}
- 在Spring Boot中,可以使用函数式编程来实现装饰者模式。具体来说,在Service层中定义一个processOrder()方法,该方法接收一个Order对象和一系列Decorator对象,并依次对Order对象进行装饰
@Service
public class OrderService {
public void processOrder(Order order, Decorator... decorators) {
for (Decorator decorator : decorators) {
decorator.decorate(order);
}
order.calculatePrice();
}
}
- 在Controller层中调用OrderService的processOrder()方法,将需要添加的Decorator对象传递给该方法。例如
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/order")
public void createOrder() {
Order order = new Order();
order.setPrice(100);
orderService.processOrder(order,
o -> o.setPrice(o.getPrice() * 0.9), // 添加打折装饰器
o -> {/* 添加赠送礼品装饰器 */}); // 添加赠礼物装饰器
}
}