美文网首页JavaAndroid开发经验谈Java 杂谈
动态修饰对象——装饰模式

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

作者: RunAlgorithm | 来源:发表于2019-05-13 23:33 被阅读10次

    1. 定义

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

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

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

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

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

    永远的希斯莱杰

    2. 设计

    主要角色:

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

    类图如下:

    装饰模式-类图

    抽象构件:

    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 等。
    InputStream

    输出字节流 OutputStream。

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

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

    3.2. Dubbo 对扩展点的修饰

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

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

    抽象构件 Protocol 定义如下:

    @SPI("dubbo")
    public interface Protocol {
        
        int getDefaultPort();
    
        @Adaptive
        <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    
        @Adaptive
        <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
    
        void destroy();
    }
    

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

    public class ProtocolFilterWrapper implements Protocol {
    
        private final Protocol protocol;
        
        ...
        
        public <T> Exporter<T> export(Invoker<T> 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 <T> Invoker<T> refer(Class<T> 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 <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
            Invoker<T> last = invoker;
            List<Filter> 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<T> next = last;
                    last = new Invoker<T>() {
    
                        public Class<T> 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 的使用过滤器操作。
    Dubbo 的 ProtocolFilterWrapper

    4. 特点

    装饰模式的几个要点:

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

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

    4.1. 优点

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

    4.2. 缺点

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

    相关文章

      网友评论

        本文标题:动态修饰对象——装饰模式

        本文链接:https://www.haomeiwen.com/subject/kmgdaqtx.html