天天看点

Java设计模式——装饰者模式【Decorator Pattern】

一、引言

23种设计模式大概分为三大类:

5种(创建型模式):工厂方法模式、抽象工厂模式、单例模式、原型模式、建造者模式。

7种(结构型模式):适配器模式、桥接模式、代理模式、外观模式、装饰者模式、组合模式、享元模式。

11种(行为型模式):策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

行为型又可以通过类与类之间的关系进行划分 :

Java设计模式——装饰者模式【Decorator Pattern】

装饰者模式基本介绍:

  • 装饰者模式(Decorator):动态地将新功能附加到对象上,在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)

二、装饰者模式

1.装饰者模式原理类图

Java设计模式——装饰者模式【Decorator Pattern】
  • 装饰者模式就像打包一个快递

    主体:比如:陶瓷、衣服 (Component) //被装饰者

    包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator) //装饰者

  • Component: 主体
  • ConcreteComponent 和 Decorator
    • ConcreteComponent:具体的主体
    • Decorator:装饰者,比如各调料,装饰者里聚合了一个Component,也就是说,ConcreteComponent是可以放到装饰者里面的(装饰者里面可以包含被装饰者)
  • 在如图的 Component 与 ConcreteComponent 之间,如果 ConcreteComponent 类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象成一个类。

三、具体需求

1.星巴克咖啡

  • 星巴克咖啡馆
  • 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
  • 调料:Milk、Soy(豆浆)、Chocolate
要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便。

要求计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖啡+调料组合。

2. 传统方案

思路一分析 - 类图

Java设计模式——装饰者模式【Decorator Pattern】

类图分析:

  • Drink 是一个抽象类,表示饮料
    • description 就是对咖啡的描述,比如咖啡的名字
    • cost()方法就是计算费用,Drink 类中做成一个抽象方法
  • Decaf 就是单品咖啡,继承 Drink,并实现了 cost()
  • Espress && Milk 就是单品咖啡+调料, 这个组合很多
方案一问题分析:这样设计会有很多类,当需要增加一个单品咖啡,或者一个新的调料时,类的数量就会倍增,就会出现类爆炸

思路二分析

前面思路一因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多,从而提高项目的维护性

Java设计模式——装饰者模式【Decorator Pattern】

说明:milk,soy,chocolate 可以设计为 Boolean,表示是否要添加相应的调料

方案二问题分析:
  • 方案 2 可以控制类的数量,不至于造成很多的类
  • 在增加或者删除调料种类时,代码的维护量很大
  • 考虑到用户可以添加多份调料,可以将 hasMilk 返回一个对应 int
  • 改进:考虑使用 装饰者 模式

3. 装饰者模式方案

使用装饰者模式改进传统方式,让程序具有更好的扩展性

思路分析 - 类图

Java设计模式——装饰者模式【Decorator Pattern】

装饰者模式下的订单:2份巧克力 + 一份牛奶的LongBlack

Java设计模式——装饰者模式【Decorator Pattern】

说明:

  • Milk包含了LongBlack
  • 一份Chocolate包含了(Milk+LongBlack)
  • 一份Chocolate包含了(Chocolate+Milk+LongBlack)
  • 这样,不管是什么形式的单品咖啡+调料组合,通过递归方式都可以方便地组合和维护

具体实现

// 接口 Drink.java
public abstract class Drink {
	public String des;//描述
	private float price = 0.0f;
	// get / set方法略
	
	//计算费用的抽象方法:由子类实现
	public abstract float cost();
}

// Coffee.java
public class Coffee extends Drink{
	@Override
	public float cost() {
		return super.getPrice();
	}
}

// Espresso.java/LongBlack.java/ShortBlack.java
public class Espresso extends Coffee{
	public Espresso() {
		setDes("意大利咖啡");
		setPrice(6.0f);
	}
}

// Decorator.java  继承Drink又组合Drink
public class Decorator extends Drink{
	private Drink drink;
	
	public Decorator(Drink drink) {
		this.drink = drink;
	}
    
	@Override
	public float cost() {
		//自己(调料)的价格+咖啡的价格
		return super.getPrice() + drink.getPrice();
	}
    
	@Override
	public String getDes() {
		//父类的信息+被装饰者的信息
		return super.des+" "+super.getPrice()+" && "+drink.getDes();
	}
}
// Chocolate.java/Milk.java/Soy.java
//具体的Decorator,这里就是调味品
public class Chocolate extends Decorator {
	public Chocolate(Drink drink) {//必须有构造器
		super(drink);
		setDes("巧克力");
		setPrice(3.5f);
	}
}
// CoffeeBar.java 使用
public class CoffeeBar {
	public static void main(String[] args) {
		//用装饰者模式下单:2份Chocolate+1份milk+LongBlack
		//1.先点一份LongBlack
		Drink coffee = new LongBlack();
		System.out.println("单品:"+coffee.getDes()+" 价格:"+coffee.cost());
		//2.加入一份牛奶
		coffee = new Milk(coffee);
		System.out.println("加入一份牛奶后:"+coffee.getDes()+" 价格:"+coffee.cost());
		
		//3.加入一份巧克力
		coffee = new Chocolate(coffee);
		System.out.println("加入一份牛奶+巧克力后:"+coffee.getDes()+" 价格:"+coffee.cost());
		
		//4.再加入一份巧克力
		coffee = new Chocolate(coffee);
		System.out.println("加入一份牛奶+2份巧克力后:"+coffee.getDes()+" 价格:"+coffee.cost());
	}
}