设计之禅——装饰者模式详解(与代理模式的区别以及与其他模式的组合)

时间:2022-07-24
本文章向大家介绍设计之禅——装饰者模式详解(与代理模式的区别以及与其他模式的组合),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言

相信很多初学者都对JavaAPI中的IO包感到头大,其中的类非常多,看着看着就晕了,笔者也是一样。不过,若是了解了装饰者模式那再看IO包的设计就很清晰明了了。

概述

装饰者模式动态的将责任附加到对象上。若要增加功能,装饰者提供了比继承更具有弹性的替代方案。

我们知道面向对象设计最基本的原则之一就是对扩展开放,对修改关闭,如果仅仅使用继承那么必然不会有很好的扩展性,尤其是我们想给对象增加功能时,装饰者模式也就因此而出现了,那它是如何做到的呢?先来看看它的类图:

装饰者模式包含了抽象组件、具体组件、抽象装饰者、具体装饰者四个角色。具体组件和抽象组件都实现自组件,以此保持类型的一致;具体装饰者持有具体组件的引用,也就是通过组合来实现动态的为对象附加责任。那么如何如何通过代码实现呢?

Coding

生活中到处可以看到装饰者的影子,这里以奶茶店购买奶茶为例,奶茶店提供了很多基本饮料,如奶茶、果汁、咖啡等等,这些就是等会儿我们看到的具体组件,而椰果、冰块、糖等等就是我们的装饰者了。接下来我们先实现一个最基础的装饰者,如果你看到这儿,先停下来思考几个问题:

  • 为什么具体组件和装饰者需要实现自抽象组件来保持类型一致?
  • 装饰者的优缺点?
  • 可以通过其他的什么模式来避免装饰者的不足?

Common

首先是抽象组件Beverage接口(能使用接口的地方就别使用抽象类,尤其是Java8后接口增加了默认的实现方法),它有一个价格方法:

public interface Beverage {

    double cost();

}
``
然后是具体组件MilkTea:
```java
public class MilkTea implements Beverage {

    @Override
    public double cost() {
        return 5;
    }

}

然后是装饰者一族:

// 使用抽象类是为了隐藏的无参构造,这需要根据具体的业务需求而定,没必要生搬硬套
public abstract class Decorator implements Beverage {

    protected Beverage beverage;

    public Decorator(Beverage beverage) {
        this.beverage = beverage;
    }

}

// 椰果
public class Coconut extends Decorator {

    public Coconut(Beverage beverage) {
        super(beverage);
    }

    @Override
    public double cost() {
    	// 这里就是装饰者的核心要点,动态的附加责任
        return beverage.cost() + 4.3;
    }
}

// 珍珠
public class Pearl extends Decorator {

    public Pearl(Beverage beverage) {
        super(beverage);
    }

    @Override
    public double cost() {
        return beverage.cost() + 2.2;
    }
}

测试:

public static void main(String[] args) {
        Beverage beverage = new MilkTea();
        Decorator decorator = new Pearl(beverage);
        System.out.println(decorator.cost());
        Decorator decorator1 = new Coconut(decorator);
        System.out.println(decorator1.cost());
}

这样一个简单的装饰者就实现完成了,如果你有认真思考,相信对于刚刚提的问题中的前两个应该已经有了答案了。其中使用装饰者的最大优点就是可以减少大类(经过装饰者装饰的类,如果都硬编码写到程序里,程序会非常的臃肿)的数量以及重复的代码,但同时也是它的缺点,这会增加非常多的小类(装饰者以及组件,相较于大类,可以灵活的自由组合产生需要的类),也就会增加系统的复杂度,那该如何解决这个问题呢?如果你看过我之前的文章或是熟悉其他模式那么不难想到可以通过工厂模式和生成器模式在一定程度上降低客户端创建对象的复杂度,注意,是一定程度,没有完美的模式。下面我通过工厂模式来说明,至于与生成器或是其他模式的组合就需要你自己去思考了。

WithFactory

沿用上面的类,在此基础上实现一个工厂:

public interface Factory {

    Beverage getBeverage();

}

public class CoConutFactory implements Factory {

    @Override
    public Beverage getBeverage() {
        return new Coconut(new MilkTea());
    }

}

可以看到客户端只需要创建椰果工厂,并掉用getBeverage()方法就能或取到加椰果的奶茶,对客户来讲也就不用繁复的去创建组件和装饰者,但是缺点也很明显,工厂只能提供热门的预设产品,而不可能提供给用户所有的组合,如果某个用户想要在奶茶中加入额外的牛奶或是糖,那么只能由用户自己再组合一下(其实细想一下MilkTea原本不就是奶茶店预设好的组合商品么)。但总的而言,装饰者模式提供了比继承更好的扩展性,熟悉其原理对于我们的项目扩展非常的有帮助。

IO包中的装饰者

下面是InputStream的体系类图,感兴趣的可以自行画出OutputStream的,与InputStream都是相对应的。

通过装饰者模式来看IO包的结构就很清晰明了了,即使Java内置的IO流不满足我们的需求,我们也能很轻易的扩展还不用破坏原本的结构。

与代理模式的区别

设计模式中对于很多初学者来说是很像的,如果不深入理解他们的设计理念就会感觉很疑惑。比如代理模式也可以增加对象的功能,那么它和装饰者模式的区别究竟是什么呢?这个在笔者之前的代理模式一文中也有提到过,他们之间最主要的区别就是装饰者模式是用来增强对象的行为和职责,而代理模式则是用于控制对象的访问,代理模式这里就不详细阐述了,感兴趣的可以去看笔者之前的文章《设计之禅——深入剖析代理模式》

总结

装饰者模式良好的遵循了对扩展开放,对修改关闭原则,使得我们的系统有更良好的扩展性,但同时也会产生比较多的小类,虽然可以通过和工厂模式以及生成器模式的组合来降低创建对象的复杂度,但这对于不了解该模式的人而言来说就会非常难以理解,平时应该多看多思考才能真正掌握。