1.概念
1.1定义
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方法。
也就是说,装饰者模式是对原有功能类增加一些新的功能(行为)。但是为原有功能类增加新的功能行为,继承或组合也能做,为什么要使用装饰者模式呢?这是因为要对原有功能类动态地同时增加多个功能行为(包括相同行为或不同行为),单独使用继承或组合实现将产生大量的子类(“类爆炸”),而使用装饰者模式则可以在保持良好的扩展性的同时避免“类爆炸”情况的出现。
1.2特点
- 装饰者和被装饰者继承同一个基类。因为装饰者必须能够取代被装饰者,这里利用继承达到“类型匹配”,而不是利用继承获取“行为”
- 装饰者拥有(has a)一个与被装饰者相同的基类类型属性
- 可以用一个或多个装饰者类包装同一个对象
- 装饰者可以在所委托被装饰者的行为之前或行为之后或行为之前与之后加上自己的行为,以达到特定的目的
为什么装饰者类和被装饰者需要继承同一个基础类呢?
主要有两个方面的原因。第一,装饰者必须能够取代被装饰者;第二,如果需要多个装饰对象对被装饰对象进行装饰(假设A是被装饰者,B和C用来装饰A),如果不使用继承,则先用B装饰A再用C装饰B时,需要B拥有A的对象,C拥有B的对象;而先用C装饰A再用B装饰C时,需要C拥有A的对象,B拥有C的对象,这使得装饰的顺序依赖于类的实现。而如果使用了继承,则只需要B和C同时拥有A的对象,然后在使用的地方用调用的顺序来完成装饰的顺序,这样不用破坏原有的类,有良好的扩展性。
为什么装饰者需要拥有(has a)一个和被装饰者相同的基础类类型作为自己属性呢?
主要是考虑多个不同的被装饰者的情况,如果使用继承,则装饰者必须继承自被装饰者,此时只能装饰一个类,而使用组合,则装饰者可以同时装饰多个类。这里也算是应用了策略模式吧。
1.3类图
在类图中有四种角色需要说明
- Component:Component是一个接口或者是抽象类,即定义我们最核心的对象,也就是最原始的对象
- ConcreateComponent:是最原始最基本的接口或者抽象类的实现,被装饰的对象
- Decorator:一般是一个抽象类,实现接口或者抽象方法,它里面不一定有抽象的方法,但是它的属性中必然有一个private变量指向Component抽象构件
- ConcreteDecoratorA和ConcreteDecoratorB:两个具体的装饰类,在里面需要写所想装饰的东西。
装饰类通用的实现
(1)、Component类
abstract class Component{
public abstract void operate();
}
(2)、复制代码具体的实现类ConcreateComponent
class ConcreateComponent extends Component{
@Override
public void operate() {
System.out.println("do something");
}
}
(3)、复制代码抽象的装饰类Decorator
abstract class Decorator extends Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operate() {
this. component. operate();
}
}
(4)、复制代码具体的装饰类ConcreteDecoratorA和ConcreteDecoratorB
class ConcreteDecoratorA extends Decorator{
public ConcreteDecoratorA(Component component) {
super(component);
}
private void methodA(){
System.out.println("MethodA装饰");
}
public void operate(){
this.methodA();
super.operate();
}
}
class ConcreteDecoratorB extends Decorator{
public ConcreteDecoratorB(Component component) {
super(component);
}
private void methodB(){
System.out.println("MethodB装饰");
}
public void operate(){
this. methodB();
super.operate();
}
}
此处需要主要原始方法和装饰方法的执行顺序在具体的装饰类中时固定的,如果想要不同的顺序可以通过重载实现多种执行顺序。
2.案例实现1
对考试成绩进行装饰,在上学的时候,通常考试后,需要家长在成绩单上进行签字,如果成绩单上只有自己的成绩【语文:78】【数学:67】【英语:69】,想让家长签字是不太容易的。需要对成绩单进行装饰,在自己的成绩的基础上加上班内的最高分【语文:88】【数学:90】【英语:87】以及自己在班内的排名15名。
//Component抽象构件
public abstract class SchoolReport {
public abstract void report();
public abstract void sign(String name);
}
//ConcreteComponent具体构件 被装饰角色
public class FouthGradeSchoolReport extends SchoolReport {
@Override
public void report() {
System.out.println("成绩单。。。。");
System.out.println("您孩子的成绩为:【语文:78】【数学:67】【英语:69】");
}
@Override
public void sign(String name) {
System.out.println("家长签名:"+name);
}
}
//Decorator装饰角色
public abstract class Decorator extends SchoolReport{
private SchoolReport sr;
public Decorator(SchoolReport sr){
this.sr = sr;
}
public void report(){
this.sr.report();
}
public void sign(String name){
this.sr.sign(name);
}
}
//具体装饰角色
public class HighScoreDecorator extends Decorator {
public HighScoreDecorator(SchoolReport re){
super(re);
}
private void reportHighScore(){
System.out.println("汇报最高成绩");
System.out.println("这次班里的最高分:【语文:88】【数学:90】【英语:87】");
}
@Override
public void report() {
this.reportHighScore();
super.report();
}
}
//具体装饰角色
public class SortDecorator extends Decorator {
public SortDecorator(SchoolReport _sr) {
super(_sr);
}
private void reportSort(){
System.out.println("这次排名:15名!");
}
@Override
public void report(){
super.report();
this.reportSort();
}
}
//场景类
public class Father {
public static void main(String[] args) {
SchoolReport schoolReport;
schoolReport = new FouthGradeSchoolReport();
schoolReport = new HighScoreDecorator(schoolReport);
schoolReport = new SortDecorator(schoolReport);
schoolReport.report();
schoolReport.sign("老三");
}
}
运行结果:
这次班里的最高分:【语文:88】【数学:90】【英语:87】
您孩子的成绩为:【语文:78】【数学:67】【英语:69】
这次排名:15名!
家长签名:老三
2.案例实现2
星巴克咖啡订单项目(咖啡馆):
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
- 调料:Milk、Soy(豆浆)、Chocolate
- 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
- 使用OO的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖啡+调料组合。
2.1方案1-解决星巴克咖啡订单项目
类图:
问题分析:
- Drink 是一个抽象类,表示饮料
- des就是对咖啡的描述, 比如咖啡的名字
- cost() 方法就是计算费用,Drink 类中做成一个抽象方法.
- Decaf 就是单品咖啡, 继承Drink, 并实现cost
- Espress && Milk 就是单品咖啡+调料, 这个组合很多
- 问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸
2.2方案2-解决星巴克咖啡订单项目
前面分析到方案1因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多。从而提高项目的维护性
类图:
问题分析:
- 方案2可以控制类的数量,不至于造成很多的类
- 在增加或者删除调料种类时,代码的维护量很大
- 考虑到用户可以添加多份调料时,可以将hasMilk 返回一个对应int
- 考虑使用 装饰者 模式
2.3装饰者模式-解决星巴克咖啡订单项目
类图:
说明
- Drink 类就是前面说的抽象类, Component
- LongBlack 就单品咖啡
- Decorator 是一个装饰类,含有一个被装饰的对象(Drink obj)
- Decorator 的cost 方法 进行一个费用的叠加计算,递归的计算价格
代码实现:
Drink 类
public abstract class Drink {
public String des;
private Float price;
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
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
LongBlack
public class LongBlack extends Coffee{
public LongBlack(){
setDes("LongBlack咖啡");
setPrice(10f);
}
}
Decorator
public class Decorator extends Drink{
private Drink drink;
public Decorator(Drink drink){ //组合
this.drink=drink;
}
@Override
public float cost() {
return super.getPrice()+drink.cost();
}
}
Milk
public class Milk extends Decorator {
public Milk(Drink drink){
super(drink);
setDes("牛奶");
setPrice(2.0f);
}
}
Chocolate
public class Chocolate extends Decorator{
public Chocolate(Drink drink){
super(drink);
setDes("巧克力");
setPrice(3.5f);
}
}
测试类CoffeeBar
public class CoffeeBar {
public static void main(String[] args) {
/*
* 一份份巧克力+一份牛奶
*/
//1.点一份DeCaf
Drink order=new LongBlack();
System.out.println(order.getDes()+":"+order.getPrice());
//2.加一份巧克力
order=new Chocolate(order);
System.out.println(order.getDes()+":"+order.getPrice());
//3.加一份牛奶
order=new Milk(order);
System.out.println(order.getDes()+":"+order.getPrice());
System.out.println("费用共计:"+order.cost());
}
}
运行结果:
无因咖啡:10.0
巧克力:3.5
牛奶:2.0
费用共计:15.5
3.装饰者模式的优缺点及使用场景
优点
- 装饰类和被装饰类可以独立发展,不会相互耦合,换句话说,Component类无需知道Decorator的类,Decorator类是从外部来扩展Component类的功能。
- 装饰模式是继承关系的一个替代方案,我们可以看到在装饰类中Decorator无论装饰了多少层,返回的对象还是Component
- 装饰模式可以动态的扩展一个实现类的功能
缺点:
多层的装饰是比较复杂的—尽量减少装饰类的数量,以便降低系统的复杂度
装饰类的使用场景
- 需要扩展一个类的功能,或给一个类增加附加功能
- 需要动态的给一个对象增加功能,这些功能可以再动态的撤销
- 需要为一批的兄弟类进行改装或加装功能
最佳实践
- 装饰模式是对继承的有力补充,增加了其灵活性,解决了类膨胀的问题
- 装饰模式可以动态地增加功能
- 装饰模式的扩展性非常好,对于业务的变更,通过装饰模式来重封装一个类,而不是通过继承来完成