装饰者模式就是这么简单

788 阅读5分钟

定义

1、装饰模式又称为包装模式,是结构型设计模式的一种。
2、装饰者模式的理念是在对客户端透明的方式下动态的给对象附加一些额外的功能,使得对象的功能更加完善。
3、装饰模式区别于继承,是继承关系的替代。装饰模式将客户端的调用委派给被装饰类。

UML结构

上图中存在四个角色

  • 抽象构件角色(Component):通常是一个接口或者抽象类,定义需要装饰的方法,一般是该角色的主要功能。
  • 具体构件角色(ConcretComponent):抽象构件角色的具体实现,也就是我们要装饰的具体对象。
  • 抽象装饰角色(Decorator):持有组件(Component)的实例引用,该类的职责是为了装饰具体组件对象,可以在需要装饰的方法的开始或者结束加上一些额外的功能。
  • 具体装饰角色(ConcreteDecorator):抽象装饰角色的不同实现,表示增强装饰方法,加上不同的功能。

实战

案例

在我们生活的周边,会有很多的便利店,这些便利店主要功能就是卖东西,可能刚开始只是卖零食和饮料这些食品,后来便利店规模扩大了,开始卖一些纸巾,洗发水等一些生活用品。不仅如此,店铺的业务也在扩展,不仅卖东西,还可以寄存类似中通,韵达的快递。用装饰者模式怎么表示呢?

创建抽象构件角色

按照UML模型,我们需要创建一个抽象构件接口或抽象类

/**
 * 抽象构建角色:定义一个功能方法
 */
public interface Component {

    void function();
}

创建具体构件角色

然后我们创建我们的构件角色,就是便利店,便利店的基本功能就是买零食和饮料这些食品。

/**
 * 具体构件角色,此处是一个店铺
 */
public class ShopComponent implements Component{

    /**
     * 实现function()方法,店铺的主要功能就是买东西,此处假设这家店铺是家便利店,
     * 卖的东西都是零食饮料之类的
     */
    @Override
    public void function() {
        System.out.println("基本功能:卖零食和饮料");
    }
}

创建装饰者

这里我们创建了我们创建了装饰者的基类,它不一定是抽象类或者接口。它持有一个构件角色Component的对象引用,然后实现其基本方法。然后在它的子类中对基本方法进行装饰。

/**
 * 抽象装饰角色
 */
public class ShopDecorator implements Component{

    //持有一个Component类型的对象引用
    private Component component;

    ShopDecorator(Component component) {
        this.component = component;
    }

    @Override
    public void function() {
        //客户端的调用委派给具体的子类
       component.function();
    }
}

创建具体装饰者

首先,我们创建了一个装饰者,装饰基本方法function(),表示便利店规模扩大了,开始卖一些生活用品

/**
 * 具体装饰角色,继承抽象装饰角色
 */
public class ShopSizeDecorator extends ShopDecorator {

    ShopSizeDecorator(Component component) {
        super(component);
    }

    /**
     * 在基本功能之后进行扩展
     */
    @Override
    public void function() {
        super.function();
        System.out.print("店铺规模扩大了:");
        sellLifeThings();
    }

    private void sellLifeThings() {
        System.out.println("开始卖一些生活用品");
    }
}

然后,又创建了一个装饰者,还是装饰基本方法function(),表示便利店业务范围扩大了,可以寄存快递了。

/**
 * 具体装饰角色,继承抽象装饰角色
 */
public class ShopBusinessDecorator extends ShopDecorator{

    ShopBusinessDecorator(Component component) {
        super(component);
    }

    /**
     * 在基本功能之后进行扩展
     */
    @Override
    public void function() {
        super.function();
        System.out.print("店铺业务范围扩大了:");
        acceptShipping();
    }

    private void acceptShipping() {
        System.out.println("可以寄存快递了");
    }
}

测试类

注意双层装饰的地方,是不是很熟悉呢?(java的IO流是不是很像)

public class ShopDecoratorTest {

    public static void main(String [] args) {
        //装饰前,店铺的基本功能就是卖一些零食和饮料
        Component component = new ShopComponent();
        System.out.println("装饰前:");
        component.function();

        //第一层装饰,店铺的规模扩大了,开始卖一些生活用品
        Component component1 = new ShopSizeDecorator(component);
        System.out.println("第一层装饰:");
        component1.function();

        //双层装饰,店铺的业务范围也扩大了,有了寄存快递的业务
        Component component2 = new ShopBusinessDecorator(new ShopSizeDecorator(component));
        System.out.println("双层装饰:");
        component2.function();
    }
}

运行结果:

理理java IO的装饰者

public static void main(String [] args) throws Exception{
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(new File("path")));

        Base64InputStream base64InputStream = new Base64InputStream(bufferedInputStream);
    }

第一层装饰,加了缓冲区功能,第二层装饰,进行了简单的加密处理。为啥能装饰呢?关键是BufferedInputStream(具体装饰者)的父类FilterInputStream(装饰者)中持有InputStream(抽象构件角色)的对象引用,而且实现了InputStream中基本功能,那么在BufferedInputStream中我也实现相同的功能,想加点什么功能就可以加点什么功能,简单吧!

补充:装饰器模式和代理模式的区别

相同点

装饰器模式中,装饰者和被装饰者都实现同一个接口。代理模式中,代理类和被代理类也都实现同一个接口,两者都是对类的方法进行扩展。

不同点

  • 让别人帮你做你并不关心的事叫代理模式。为让自己的能力增强,拓展在自己基础上的功能,叫装饰器模式。
  • 装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后,你还是你,只是能力变强了而已。代理模式强调要让别人帮你做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者其内部不想暴露出来。
  • 装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案。代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用。