秒懂设计模式之装饰者模式(Decorator Pattern)

前言

人在IT江湖飘,不懂设计模式咋装X?

今天媳妇突然跑过来问我:你看我好看吗?我抬头一看,这货画了个特殊的眉毛,整个人和平时都不太一样了,果然化妆大法好,这也同时勾起了我对一个相关的设计模式,装饰者模式,的回忆:

定义

装饰模式是在不必改变原类和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象

使用场景

  1. 需要在运行时动态的给一个对象增加额外的职责时候
  2. 需要给一个现有的类增加职责,但是又不想通过继承的方式来实现的时候(应该优先使用组合而非继承),或者通过继承的方式不现实的时候(可能由于排列组合产生类爆炸的问题)。

如何实现

具体业务场景

在上一篇 桥接模式 中提到林蛋大公司接了星巴克的咖啡系统项目。林蛋大在王二狗的指导下使用桥接模式实现了星巴克要求的各种下单功能:大杯原味、大杯加糖、大杯加奶;中杯原味、中杯加糖、中杯加奶;小杯原味、小杯加糖、小杯加奶。刚舒服了没两天,项目经理就来找蛋大了:蛋大啊,现在用户的口味太刁,好多都要同时加奶,加糖,有的还要加蜂蜜,咱们目前这个实现不支持,你去改一下。对了,有些用户要求先加糖再加奶,而一些用户要求先加奶然后再加糖,顺序不能乱!蛋大:我了个去。。。。。。。正当林蛋大一筹莫展的时候,王二狗兴高采烈的走了过来,看样子和牛翠花的进展不错。蛋大:狗哥,我这个有个棘手的问题请教你一下。。。

王二狗开始分析需求:假设我们有一个原味咖啡的类 OriginalCoffee, 目前的需求就是要动态的给这个类的一些实例增加一些额外功能,此处就是动态的对某些咖啡制作过程增加新的流程,例如加奶,加糖,而有的咖啡却保持原味不变。

这种需求要是通过继承的方式就不太好实现,因为咖啡制作过程是动态变化的。例如有的需要原味咖啡,有的需要加奶咖啡,有的需要加糖咖啡,而有的需要先加奶再加糖咖啡,而有的需要先加糖再加奶的咖啡,。。。这是一个排列组合问题,如果使用类继承的方式实现,必须预先将所有可能组合都想清楚,然后生成相应的子类,随着咖啡口味的增多,以及添加顺序的改变,几乎是不可扩展和维护的。

经过需求分析,二狗锁定了装饰者模式来实现此需求。原味咖啡是本质,而加奶,加糖都是在装饰这个本质的东西,再怎么加东西咖啡还是咖啡。

下图是装饰者模式的UML图
在这里插入图片描述
首先我们有一个ICoffee接口,里面有一个制作咖啡的接口方法makeCoffee()。要进行装饰的类 OriginalCoffee 和装饰者基类CoffeeDecorator(一般为抽象类)实现了此接口。CoffeeDecorator类里面持有一个ICoffee引用,我们第一步会把要装饰那个原始对象赋值给这个引用,那样在装饰者类中才可以调用到那个被装饰的对象的方法。MilkDecoratorSugarDecorator 都继承至CoffeeDecorator, 都是具体的装饰者类。

具体实现

第一步:先声明一个原始对象的接口

public interface ICoffee {
    void makeCoffee();
}

第二步:构建我们的原始对象,此处为原味咖啡对象,它实现了ICoffee接口。

public class OriginalCoffee implements ICoffee {
    @Override
    public void makeCoffee() {
        System.out.print("原味咖啡 ");
    }
}

第三步:构建装饰者抽象基类,它要实现与原始对象相同的接口ICoffee,其内部持有一个ICoffee类型的引用,用来接收被装饰的对象

public abstract class CoffeeDecorator implements ICoffee {
    private  ICoffee coffee;
    public CoffeeDecorator(ICoffee coffee){
        this.coffee=coffee;
    }

    @Override
    public void makeCoffee() {
        coffee.makeCoffee();
    }
}

第四步:构建各种装饰者类,他们都继承至装饰者基类 CoffeeDecorator。此处生成了两个,一个是加奶的装饰者,另一个是加糖的装饰者。

public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(ICoffee coffee) {
        super(coffee);
    }
    @Override
    public void makeCoffee() {
        super.makeCoffee();
        addMilk();
    }
    private void addMilk(){
           System.out.print("加奶 ");
     }    
}
public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(ICoffee coffee) {
        super(coffee);
    }
    @Override
    public void makeCoffee() {
        super.makeCoffee();
        addSugar();
    }
    private void addSugar(){
           System.out.print("加糖");
     } 
}

第五步:客户端使用

    public static void main(String[] args) {
        //原味咖啡
        ICoffee coffee=new OriginalCoffee();
        coffee.makeCoffee();
        System.out.println("");

        //加奶的咖啡
        coffee=new MilkDecorator(coffee);
        coffee.makeCoffee();
        System.out.println("");

        //先加奶后加糖的咖啡
        coffee=new SugarDecorator(coffee);
        coffee.makeCoffee();
    }

输出:

原味咖啡 
原味咖啡 加奶 
原味咖啡 加奶 加糖 

可以从客户端调用代码看出,装饰者模式的精髓在于动态的给对象增减功能

当你你需要原味咖啡时那就生成原味咖啡的对象,而当你需要加奶咖啡时,仅仅需要将原味咖啡对象传递到加奶装饰者中去装饰一下就好了。如果你加了奶还想加糖,那就把加了奶的咖啡对象丢到加糖装饰者类中去装饰一下,一个先加奶后加糖的咖啡对象就出来了。

优缺点

优点

可以提供比继承更加灵活的方式去扩展对象的功能,通过排列组合,可以对某个类的一些对象做动态的功能扩展,而不需要装饰的对象却可以保持原样。

缺点

仍然是设计模式的通用缺点:类的个数会增加,会产生很多装饰者类,相应的就增加了复杂度。

装饰者模式与代理模式的区别

一般认为代理模式侧重于使用代理类增强被代理对象的访问,而装饰者模式侧重于使用装饰者类来对被装饰对象的功能进行增减。
除了上面的区别,个人实践中还发现,装饰者模式主要是提供一组装饰者类,然后形成一个装饰者栈,来动态的对某一个对象不断加强,而代理一般不会使用多级代理,详情请见秒懂Java代理与动态代理模式

总结

设计模式值得你刻意练习。

You May Also Like

About the Author: shusheng007

发表评论

邮箱地址不会被公开。