设计模式 (十) 装饰者模式

一、定义

装饰者模式是一种比较常见的模式,其定义如下:

动态地给对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更为灵活。如果不用装饰者模式,我们想要扩展一个对象的功能,我们可能会采用继承该对象的方式,然后重写里面的方法来实现扩展原有功能,但当对象变化频繁的时候,这种子类会有很多,装饰者模式有效避免了这种创建大量子类的现象,动态地扩展对象的功能。

装饰者模式通用类图如下:

 二、角色分析

装饰者模式有四个角色需要说明:

  • Component抽象构件 

抽象构建类,可以是抽象类或者接口,定义了被装饰者类的一些抽象方法等,即最基本的功能。

  • ConcreteComponent具体构件

具体构建类,实现或者继承了抽象构建类,实现了具体的方法,真正被装饰的其实是它

  • Decorator装饰角色

一般是一个抽象类,实现接口或者抽象方法,它里面不一定有抽象的方法,在它的属性里面必然有一个private变量指向Component抽象构件。

  • ConcreteDecorator具体装饰角色

真正在这里扩展原始对象的功能,针对不同的具体构建类,可以定义多个具体装饰角色,例如ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类。

三、装饰者模式案例分析

下面我们以一个日常生活中常见的例子来说明装饰者模式的使用。

冬天快到了,很多小伙伴都会喜欢打火锅,打火锅肯定要有一个汤底,然后我们可能还会点各种各样的配菜,在增加配菜的同时,我们需要动态计算吃这一顿的总价钱。

接下来我们就使用装饰者模式来把上面这种场景进行抽象。

首先,UML类图如下:

 我们首先定义抽象构件角色,主要提供两个方法:获取价格的方法getPrice()、获取描述信息的方法getDesc(),代码如下:

/**
 * 抽象构件: 火锅类
 */
public abstract class HotPotComponent {

    /**
     * 获取价格
     *
     * @return
     */
    protected abstract BigDecimal getPrice();

    /**
     * 获取描述信息
     *
     * @return
     */
    protected abstract String getDesc();
}

接下来定义一个具体构件角色:本例中就是火锅底料对象,真正装饰的也就是它。

/**
 * 具体构件类: 火锅底料
 */
public class HotPotSeasoningComponent extends HotPotComponent {
    @Override
    protected BigDecimal getPrice() {
        return BigDecimal.valueOf(20);
    }

    @Override
    protected String getDesc() {
        return "火锅底料";
    }
}

接着,定义一个抽象装饰者类,它继承自抽象构件角色,内存持有一个指向抽象构件角色的引用:

/**
 * 抽象装饰者类
 */
public abstract class AbstractDecorator extends HotPotComponent {
    private HotPotComponent hotPotComponent;

    public AbstractDecorator(HotPotComponent hotPotComponent) {
        this.hotPotComponent = hotPotComponent;
    }

    @Override
    protected BigDecimal getPrice() {
        return hotPotComponent.getPrice().add(getSideDishPrice());
    }

    @Override
    protected String getDesc() {
        return hotPotComponent.getDesc() + " + " + getSideDishDesc();
    }

    /**
     * 获取配菜的描述
     *
     * @return
     */
    protected abstract String getSideDishDesc();

    /**
     * 获取配菜的价格
     *
     * @return
     */
    protected abstract BigDecimal getSideDishPrice();

}

可以看到, 除了实现抽象构件的抽象方法之外,抽象装饰者角色内存还定义了配菜的一些方法。并且重写了抽象构件的getPrice()方法和getDesc()方法,动态的计算所有配菜的总价格。

有了抽象装饰者角色,就需要定义具体的装饰者角色进行增强了:

/**
 * 具体装饰者角色: 肥牛
 */
public class FatCattleDecorator extends AbstractDecorator {
    public FatCattleDecorator(HotPotComponent hotPotComponent) {
        super(hotPotComponent);
    }

    @Override
    protected String getSideDishDesc() {
        return "肥牛";
    }

    @Override
    protected BigDecimal getSideDishPrice() {
        return BigDecimal.valueOf(20);
    }
}

/**
 * 具体装饰者角色: 小白菜
 */
public class CabbageDecorator extends AbstractDecorator {
    public CabbageDecorator(HotPotComponent hotPotComponent) {
        super(hotPotComponent);
    }

    @Override
    protected String getSideDishDesc() {
        return "小白菜";
    }

    @Override
    protected BigDecimal getSideDishPrice() {
        return BigDecimal.valueOf(10);
    }

}

最后,我们定义一个场景类进行测试:

public class Client {
    public static void main(String[] args) {
        //层层包装
        HotPotComponent hotPotComponent = new HotPotSeasoningComponent();
        //一份肥牛
        hotPotComponent = new FatCattleDecorator(hotPotComponent);
        //一份肥牛
        hotPotComponent = new FatCattleDecorator(hotPotComponent);
        //一份小白菜
        hotPotComponent = new CabbageDecorator(hotPotComponent);
        System.out.println(hotPotComponent.getDesc() + " = " + hotPotComponent.getPrice() + "元");
    }
}

运行结果:

火锅底料 + 肥牛 + 肥牛 + 小白菜 = 70元

可见,成功扩展原有对象的功能,这样我们以后如果需要扩展火锅底料的功能,只需要增加一个具体的装饰者角色即可实现扩展,而不需要改动原有的代码,这符合开闭原则。

四、装饰者应用分析

装饰者模式在我们的IO流中体现的最为显著,下面我们看一下IO流中InputStream相关的UML类图:

对应前面的通用类图应该很容易看出各个角色分别是谁:

  • OutputStream和InputStream就对应于抽象构件角色(Component);
  • FileInputStream和FileOutputStream就对应具体构件角色(ConcreteComponent);
  • FilterOutputStream和FilterInputStream就对应着抽象装饰角色(Decorator);
  • BufferedOutputStream,DataOutputStream等等就对应着具体装饰角色; 

下面是关键源码:

//抽象类,抽象构建角色
public abstract class InputStream implements Closeable {
    //省略...
}

//具体构建角色,也是具体被装饰的类    
public class FileInputStream extends InputStream{
    //省略...
}

//装饰者基类,该类持有一个抽象构建角色InputStream的引用
public class FilterInputStream extends InputStream {
    /**
     * The input stream to be filtered.
     */
    protected volatile InputStream in;
    
    //省略...
}

//具体装饰者角色    
public class BufferedInputStream extends FilterInputStream {
    //省略...
}

五、总结

装饰者模式的优点:

  • 装饰者类和被装饰者类互不影响,独立扩展,他们之间没有耦合关系;
  • 装饰者模式是频繁继承的一个替换解决方案,可以重复包装,包装之后返回的还是抽象构建角色;
  • 装饰者模式可以动态地扩展一个实现类的功能;

装饰者模式的缺点:

  • 多层装饰之后会使系统更加复杂,后期不太方便维护和扩展,所以不建议嵌套太多层装饰,尽量减少装饰类的数量,降低系统的复杂度。

装饰者模式的使用场景:

  • 需要动态扩展一个类的功能,或给一个类在增加附加功能;
  • 需要频繁使用继承才能扩展功能时可以考虑使用装饰者模式,可以减少类的创建;
  • 需要为一批的兄弟类进行改装或加装功能,当前是首选装饰者模式;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值