装饰模式——动态修饰对象

文章目录

    • 1. 定义
    • 2. 设计
      • 2.1. 透明装饰者
      • 2.2. 半透明装饰者
    • 3. 应用
      • 3.1. Jave IO
      • 3.2. Dubbo 对扩展点的修饰
    • 4. 特点
      • 4.1. 优点
      • 4.2. 缺点

Demo 地址: https://github.com/ooblee/HelloDesignPattern

1. 定义

装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。

装饰模式可以从现实世界中找到影子。

比如 PS,对图像有锐化、高斯模糊、明暗对比、饱和度调整等功能,这些都视为对图像的装饰,然后这些功能可以进行组合也可以单独使用,应用在各种不同的图像上面,可以灵活丰富原有图像的展示。

比如化妆,需要不同的化妆品组合搭配,不同搭配有不同的效果,动态选择化妆品,对脸进行装饰。

比如学习的过程,一个学习的过程就是对个人知识库的装饰过程。

2. 设计

主要角色:

  • 抽象构件,定义构件的基本方法。
  • 具体构件,实现抽象构件的基本方法。装饰器会去装饰。
  • 抽象装饰类,继承抽象构件的基本方法,来实现和具体构件一样的行为。维护抽象构件的引用。
  • 具体装饰类,实现抽象装饰类的基本方法,会对构建增加新职责,增加新方法。

类图如下:

装饰模式——动态修饰对象_第1张图片

抽象构件:

public interface IComponent {

    void sayHello();
}

具体构件,用来被修饰的对象:

public class ConcreteComponent implements IComponent {

    public void sayHello() {
        System.out.println("hello.");
    }
}

抽象修饰者,也继承抽象构件,可以视为特殊的构件类:

public class DecoratorA extends Decorator {

    public DecoratorA(IComponent component) {
        super(component);
    }

    @Override
    public void sayHello() {
        super.sayHello();
        System.out.println("A");
    }
}

使用者组合调用,对具体构建进行一次修饰,或者多次修饰:

public class TestDecorator {

    public static void main(String[] args) {

        // 原始构件
        IComponent component = new ConcreteComponent();
        component.sayHello();

        // 使用 A 修饰
        System.out.println();
        component = new DecoratorA(new ConcreteComponent());
        component.sayHello();

        // 使用 B 修饰
        System.out.println();
        component = new DecoratorB(new ConcreteComponent());
        component.sayHello();

        // 使用 A 修饰然后又使用 B 修饰
        System.out.println();
        component = new DecoratorB(new DecoratorA(new ConcreteComponent()));
        component.sayHello();

    }
}

上面例子展示了装饰模式的基本结构以及使用的方式。

实际应用场景一般不会有这么理想化的模型。

对于装饰者新增的操作方法的使用方式,会有两种模型:

  • 透明(Transparent)装饰模式
  • 半透明(Semi-transparent)装饰模式

2.1. 透明装饰者

使用如下:

  • 客户端面向抽象编程。
  • 构件,声明的是抽象构建者。
  • 修饰者,声明的还是抽象构建者。

比如上面的那个简单例子。

// 构件
IComponent component = new ConcreteComponent();
component.sayHello();

// 使用 A 修饰
component = new DecoratorA(new ConcreteComponent());
component.sayHello();

这样的话修饰者和构件对于客户端就完全没有区别了,不用关心修饰者到底增加了什么方法,也不需要调用到修饰者增加的方法。

这里的修饰者还可以继续当成构件,抛出到下一个修饰者修饰。

对于客户端来说,透明修饰者模式,修饰者还是构件。

2.2. 半透明装饰者

使用如下:

  • 客户端需要显示调用装饰者新增的方法。
  • 构件,声明的是抽象构件者。
  • 修饰者,声明的是具体修饰者。没有抽象修饰者。

所以具体的构件者对客户端透明,但修饰者没有。

修饰者新增的方法是独立的,客户端可以调用该方法来完成对构件的修饰。

// 构件
IComponent component = new ConcreteComponent();
component.sayHello();

// 使用 A 修饰
DecoratorA decoratorA = new DecoratorA(componet);
decoratorA.operationA();

这种方式设计简单,不需要对装饰者和构件进行接口统一,所以修饰者和构件可以不在同一个继承体系下面。

缺点就是无法反复进行修饰。

对于客户端来说,半透明修饰者模式,修饰者可以不是构件。

3. 应用

使用者面像抽象构建编程,无需关心中途被修饰成什么样,修饰过程对使用者透明。

可以在不增加子类的情况下,不使用继承的方式,基于组合的方式,对一个对象的功能进行扩展。

除了对一个构件进行修饰,还可以把修饰完的构件再加入修饰器中反复修饰。

应用场景:

  • 动态给对象增加职责和功能
  • 对继承的优化
    • 在一些无法使用继承的类,比如声明为 final 类型的类需要扩展功能的。
    • 在已经存在大量扩展类的情况下,没增加一个扩展需要创建多个子类。

3.1. Jave IO

Java IO 框架是很经典的修饰者模式。

输入字节流 InputStream。

  • 抽像构件类为 InputStream。
  • 抽像修饰者为 FilterInputStream,维持被修饰的 InputStream 的引用。
  • 具体构件类有 FileInputStream、ByteArrayInputStream 等。
  • 具体修饰者有 BufferedInputStream、DataInputStream 等。

装饰模式——动态修饰对象_第2张图片

输出字节流 OutputStream。

  • 抽像构件类为 OutputStream。
  • 抽像修饰者为 FilterOutputStream,维持对被修饰的 OutputStream 的引用。
  • 具体构件类有 FileOutputStream、ByteArrayOutputStream 等。
  • 具体修饰者有 BufferedOutputStream、DataOutputStream 等。

装饰模式——动态修饰对象_第3张图片

可以发现这里使用的是修饰中模式中的透明修饰者,可以对字节流进行反复修饰。

3.2. Dubbo 对扩展点的修饰

Dubbo 在生成 Protocol 的 Invoker 前,会对 Protocol 进行修饰,增加日志、权限、异常处理等等。

对 Protocol 修饰使用的是 ProtocolFilterWrapper。对应修饰者模式中的修饰者。

抽象构件 Protocol 定义如下:

@SPI("dubbo")
public interface Protocol {
    
    int getDefaultPort();

    @Adaptive
     Exporter export(Invoker invoker) throws RpcException;

    @Adaptive
     Invoker refer(Class type, URL url) throws RpcException;

    void destroy();
}

抽象修饰者 ProtocolFilterWrapper 实现了 Protocol,并且持有 Protocol 的引用,然后 exportrefer 的方法中增强。

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;
    
    ...
    
	public  Exporter export(Invoker invoker) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
    }

    public  Invoker refer(Class type, URL url) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
    }
    
    ...
}

修饰者 ProtocolFilterWrapper 在修饰 Protocol 的过程中,还应用了责任链模式。

private static  Invoker buildInvokerChain(final Invoker invoker, String key, String group) {
        Invoker last = invoker;
        List filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
        if (filters.size() > 0) {
            for (int i = filters.size() - 1; i >= 0; i --) {
                final Filter filter = filters.get(i);
                final Invoker next = last;
                last = new Invoker() {

                    public Class getInterface() {
                        return invoker.getInterface();
                    }

                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    public Result invoke(Invocation invocation) throws RpcException {
                        return filter.invoke(next, invocation);
                    }

                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }
        return last;
    }

使用 SPI 工具 ExtensionLoader 从配置文件 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter 把过滤器列表全部加载出来。

文件如下:

cache=com.alibaba.dubbo.cache.filter.CacheFilter
validation=com.alibaba.dubbo.validation.filter.ValidationFilter
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
token=com.alibaba.dubbo.rpc.filter.TokenFilter
accesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilter
activelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
classloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFilter
context=com.alibaba.dubbo.rpc.filter.ContextFilter
consumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
executelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFilter
compatible=com.alibaba.dubbo.rpc.filter.CompatibleFilter
timeout=com.alibaba.dubbo.rpc.filter.TimeoutFilter
trace=com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter
future=com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter
monitor=com.alibaba.dubbo.monitor.support.MonitorFilter

然后使用责任链,把所有的 Filter 组装成链表。然后返回链表头部的 Invoker。

这样 Invoker 触发 invoke 的时候,这一系列过滤器就会被执行。

这个扩展点修饰如下:

  • 使用修饰者增强了 Protocol 的 export 和 refer 方法。
  • export 和 refer 在创建 Invoker 的地方,使用责任链模式组装 Filter 链。在 Invoker 调用 invoke 的使用过滤器操作。

装饰模式——动态修饰对象_第4张图片

4. 特点

装饰模式的几个要点:

  • 动态扩展
  • 不改变原来的类
  • 基于组合而不是继承

装饰者模式可以用来代替继承。不断装饰的过程就类似于继承对父类的扩展和重写。

4.1. 优点

  • 单一职责。每个装饰者关注某一个具体的功能。
  • 功能复用。而且面向抽象构件编程,可以在多个具体构建者中应用同一种功能。比如 BufferInputStream 可以对不同的字节流增加缓存的能力。
  • 解耦。具体构件者和具体装饰者相互独立。
  • 灵活。动态扩展,避免静态继承导致子类膨胀。
  • 易扩展。增加新的构件者和具体修饰者,无需修改之前的代码,符合开闭原则。

4.2. 缺点

  • 小对象多。因为不同的功能会建立单独一个修饰者实现,如果要组合一个对象可能会同时创建多个修饰者。
  • 排错复杂。动态组合功能,出问题需要排查每一个功能类。

你可能感兴趣的:(设计模式,设计模式大全)