目录
一.装饰者模式介绍
装饰者模式又称为包装模式,它主要是为了扩展对象的功能,通过持有对象的引用,把对象包装起来,可以在调用对象的方法之前或者之后增加新的功能,以达到给对象添加一些额外的职责,就像为对象添加了一些装饰。
另一个要点是,包装者可以有很多个,对象被包装了一层之后,依然可以继续再包装来添加新的职责。添加职责的目的其实就是为了扩展对象的功能,通过使用装饰者模式可以使得系统具有非常好的弹性。遵循了面向对象原则:对扩展开放,对修改关闭。如果有新的需求变更,对象功能扩展,只需要新增一个装饰者类,将对象包装起来即可扩展对象的功能,而不需要对旧有对象代码进行修改。
通过继承,子类进行方法重写同样也可以扩展对象的功能。但它的弹性比较差,一旦业务变更或新增业务功能时,就需要打开实现类的代码进行修改,这样不仅需要检查旧有代码,同时还要保证新增代码的正确性。类的继承关系是在编译期就已经确定好了,运行期间不能动态的更改,使用装饰者模式可以在运行期间动态的、不限量的给对象添加装饰者,扩展功能。装饰者模式是替代继承来扩展对象功能更好的方案。
二.术语
ConcreteCompent:
是需要扩展功能的对象或者叫组件对象。
Decorator:
是装饰者类共同实现的接口或抽象类,它和ContreateCompent具有相同的超类型,并且持有一个待扩展对象的引用。
ConcreteDecorator:
A等是具体的装饰者类,它们可以给引用的组件对象加上新的方法。新的方法通过在旧的方法的前面或者后面做一些计算来添加。
注意:concreteComponent、Decorator都会实现或继承Component
三.角色
- Client 类:装饰模式的调用者
- **Component**:主体===》饮料(Drink)
- **concreteComponent**:被装饰者===》咖啡(Coffee)
- xxxConcreteComponent:具体的被装饰者(ChinaCoffee)
美式咖啡、欧式咖啡、中式咖啡
- **Decorator**:装饰器===》调料
- xxxDecorator:子装饰器
糖类、奶酪、牛油、芥末
四. 案例
单体咖啡与调味组合的饮料计价项目
这个项目最容易想到的设计方案就是采用继承的设计方案,设计思路如下:
Drink===》饮品
Juice===》果汁
.......
Coffee===》咖啡
ChinaCoffee===》中式咖啡
ASeasoningChinaCoffee===》被A调料修饰的中式咖啡
BSeasoningChinaCoffee===》被B调料修饰的中式咖啡
......
XxxCoffee===》其它咖啡
ASeasoningXxxCoffee===》被A调料修饰的其它咖啡
BSeasoningXxxCoffee===》被B调料修饰的其它咖啡
......
上面每个类中都有getPrice计价的功能,从结果上来看,确实可以完成项目需求,但是整个设计体系过于臃肿,不便于后期的扩展与维护;
五.代码示例
1.未使用设计模式之前:
package com.chaiyang.demo;
/**
* 饮料包括单体咖啡+调料
*/
public abstract class Drink {
protected double price;
protected int n;
protected DrinkSeasoning seasoning;
public abstract double getPrice();
}
/**
* 单体咖啡
*/
abstract class Coffee extends Drink {
}
/**
* 单体果汁
*/
abstract class Juice extends Drink {
}
class ChinaCoffee extends Coffee{
ChinaCoffee(double price,int n){
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price*this.n+this.seasoning.getPrice();
}
}
package com.chaiyang.demo;
/**
* 调料的抽象接口
*/
public interface DrinkSeasoning {
public abstract double getPrice();
}
/**
* A类调料
*/
class ADrinkSeasoning implements DrinkSeasoning{
protected double price;
protected int n;
ADrinkSeasoning(double price,int n){
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price*this.n;
}
}
/**
* B类调料
*/
class BDrinkSeasoning implements DrinkSeasoning{
private double price;
protected int n;
BDrinkSeasoning(double price,int n){
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price*this.n;
}
}
package com.chaiyang.demo;
public class Client {
public static void main(String[] args) {
ChinaCoffee chinaCoffee = new ChinaCoffee(6,1);
ADrinkSeasoning aDrinkSeasoning = new ADrinkSeasoning(2,2);
chinaCoffee.seasoning = aDrinkSeasoning;
System.out.println("中式咖啡1份+A调料2份,最终价格为:"+chinaCoffee.getPrice());
// 思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢?
// 思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?
}
}
1.运行效果图:
2.上面的代码存在问题:
思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢?
seasoning只能代表一种调料思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?
原有的设计没办法应对这种场景思考3:Drink饮品单体饮料种类多,调料种类也多,会带来什么问题?
Drink抽象类中聚合List<DrinkSeasoning>才可以解决上述的前两个问题;但是,在原有订单追加调料,相当于给原有对象进行装饰,这类的问题更加适合用装饰模式来解决;
使用设计模式之后:
package com.chaiyang.demo.demo1;
/**
* 饮料包括单体咖啡+调料
*/
public abstract class Drink {
protected double price;
protected int n;
public abstract double getPrice();
}
/**
* 单体咖啡
*/
abstract class Coffee extends Drink {
}
/**
* 单体果汁
*/
abstract class Juice extends Drink {
}
class ChinaCoffee extends Coffee {
ChinaCoffee(double price, int n) {
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price * this.n;
}
}
package com.chaiyang.demo.demo1;
public class DecoratorDrink extends Drink {
private Drink drink;
public DecoratorDrink(Drink drink, double price, int n) {
this.drink = drink;
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price * this.n + drink.getPrice();
}
}
class ADecoratorDrink extends DecoratorDrink {
public ADecoratorDrink(Drink drink, double price, int n) {
super(drink, price, n);
}
}
class BDecoratorDrink extends DecoratorDrink {
public BDecoratorDrink(Drink drink, double price, int n) {
super(drink, price, n);
}
}
public class Client {
public static void main(String[] args) {
ChinaCoffee chinaCoffee = new ChinaCoffee(6,1);
// 假定A类调料2元一份,B类调料3元一份
Drink order = new ADecoratorDrink(chinaCoffee, 2, 2);
System.out.println("中式咖啡1份+A调料2份,最终价格为:"+order.getPrice());
// 思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢?
order = new ADecoratorDrink(order,2,1);
System.out.println("式咖啡1份+A调料3份,最终价格为:"+order.getPrice());
order = new BDecoratorDrink(order,3,2);
System.out.println("式咖啡1份+A调料3份+B调料2份,最终价格为:"+order.getPrice());
// 思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?
order = new BDecoratorDrink(order,3,2);
System.out.println("式咖啡1份+A调料3份+B调料4份,最终价格为:"+order.getPrice());
}
}
六.总结
装饰者模式又名包装模式。装饰者模式以对客户端透明的方式扩展对象的功能,是继承的一个替代方案。
装饰模式有透明和半透明两种,这两种的区别就在于装饰角色的接口与抽象构件角色的接口是否完全一致。
1)透明的装饰模式也就是理想的装饰模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。
2)装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,称为“半透明”的装饰模式。
装饰者模式:增强功能、不改变接口。
半透明装饰者模式:增强功能、改变接口。
适配器模式:不增加功能、改变接口。
装饰者模式的优点:
1)目的在于扩展对象的功能。装饰者模式提供比继承更好的灵活性。装饰是动态的,运行时可以修改的;继承是静态的,编译期便已确定好。
2)通过使用不同的装饰类及对它们的排列组合,可以创造出许多不同行为的组合。装饰者模式的缺点:
1)利用装饰者模式,常常造成设计中有大量的小类,数量太多,会造成使用此API的人带来困扰,不容易理解。
2)采用装饰者在实例化组件时,将增加代码复杂度。一旦使用装饰者,不仅要实例化组件,同时要将组件实例包装进装饰者。
3)调试时不易排查错误。适用场景:
1)需要扩展一个类的功能, 或给一个类增加附加功能。
2)需要动态地给一个对象增加功能, 这些功能可以再动态地撤销。
3)需要为一批的兄弟类进行改装或加装功能, 当然是首选装饰模式