装饰者模式

声明 本文内容属于《Head First 设计模式》阅读笔记,文中涉及到的知识案例等直接或间接来源于该书。《Head First 设计模式》通过有趣的图表+文字的形式,让人自然学习设计模式,非常棒推荐阅读


装饰者模式概念

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

要点说明

序号说明
装饰者与被装饰者需要具有同一个超类(即:这两者的某个祖先类要相同)。
程序需要的也只是超类,而不具体到哪一个子类,这样一来该超类的任何子类都能被程序认可。
装饰者中应持有被装饰者实例,这样一来,装饰者里面就能调用到被装饰者的相关方法,进而进行装饰。
装饰者中持有被装饰者时,声明被装饰者的类型时应声明为超类的类型(而不声明为某个具体的被装饰者类)。这样一来,(因为装饰者、被装饰者都是这个超类的子类,所以)装饰者不仅可以装饰被装饰者,装饰者还可以装饰装饰者。
装饰者应重写(所有装饰后)会受到影响的方法。
注:重写的方法来自超类。

图示(示例)说明

在这里插入图片描述


案例(辅助理解)

提示 装饰者模式使用灵活,下面只是一个简单的例子,在不同的情境下,可能有不同的表现形式。

情景(需求)介绍

       现有一家星巴兹咖啡馆,里面有各种咖啡(焦炒咖啡、低浓咖啡、浓缩咖啡、首选咖啡)以及各种调味料(牛奶、摩卡、豆浆、奶泡)。现在的需求是:客人在点咖啡时还可以点任意(任意种类、任意数量的)调料

从装饰者模式出发

  1. 各种咖啡,各种调料的共同超类,为饮料beverage。
  2. 各种咖啡为饮料的直接实现,为被装饰者。
  3. 各种调料是饮料的附加添加品,为装饰者。
  4. 可以将装饰者的共有逻辑进行抽取,抽取为一个抽象类。

于是得到以下UML类图:在这里插入图片描述提示:上图中没有显示方法项,装饰者应重写装饰后受影响的方法(重写的方法来自超类)。

上述装饰者模式示例中的几个核心类及代表类

  • AbstractBeverage:抽象超类。

    import lombok.Getter;
    import lombok.Setter;
    
    /**
     * 饮料(的抽象定义)
     *
     * 注: 在java中的装饰者模式,超类既可采用【抽象类】,亦可采用【接口】。
     *     不过一般都采用 抽象类。
     *
     * @author JustryDeng
     * @date 2019/12/3 11:45
     */
    public abstract class AbstractBeverage {
    
        /** 描述 */
        @Setter
        @Getter
        private String description;
    
        /**
         * 获取饮料的价格
         *
         * @return  饮料的价格
         */
        public abstract double cost();
    }
    

    注:超类既可采用抽象类,亦可采用接口。不过一般都采用抽象类。

  • AbstractCondimentDecorator:装饰者抽象类。

    import com.szlaozicl.designpattern.decorator.AbstractBeverage;
    
    /**
     * (装饰饮料的)调料装饰者(的抽象定义)
     *
     * @author JustryDeng
     * @date 2019/12/3 11:50
     */
    @SuppressWarnings("all")
    public abstract class AbstractCondimentDecorator extends AbstractBeverage {
    
        /** 被装饰者 */
        protected AbstractBeverage decoratedObj;
    
        /**
         * 构造器
         *
         * @param decoratedObj
         *            被装饰者
         * @date 2019/12/4 12:30
         */
        protected AbstractCondimentDecorator(AbstractBeverage decoratedObj) {
            this.decoratedObj = decoratedObj;
        }
    
        /**
         * 装饰者应重写 饮料的描述
         * 注: 之所以需要重写饮料的描述, 是因为原来的描述没有对调料的介绍。
         * 特别注意: 装饰者 应重写 装饰前后相关的方法。 如,这里的描述、价格。
         *
         * @return 饮料的描述
         */
        @Override
        public abstract String getDescription();
    }
    

    注:对于所有装饰者共有的特征,可进行抽取,抽取为抽象类或非抽象类均可。

  • Espresso:被装饰者代表类。

    import com.szlaozicl.designpattern.decorator.AbstractBeverage;
    
    /**
     * 具体饮料 - 浓缩咖啡
     *
     * @author JustryDeng
     * @date 2019/12/3 12:03
     */
    public class Espresso extends AbstractBeverage {
    
        /** 无参构造 */
        public Espresso() {
            // 当前饮料的描述
            setDescription("浓缩咖啡Espresso");
        }
    
        @Override
        public double cost() {
            return 1.99;
        }
    }
    
  • MilkCondimentDecorator:装饰者代表类。

    import com.szlaozicl.designpattern.decorator.AbstractBeverage;
    
    /**
     * 具体的调料(装饰者) - 牛奶Milk
     *
     * @author JustryDeng
     * @date 2019/12/3 12:33
     */
    public class MilkCondimentDecorator extends AbstractCondimentDecorator {
    
        /** 构造 */
        public MilkCondimentDecorator(AbstractBeverage decoratedObj) {
            super(decoratedObj);
        }
    
        @Override
        public String getDescription() {
            // 被装饰者 原来的description
            String oldDescription = decoratedObj.getDescription();
            // 对原来的description进行装饰
            return oldDescription + " + 牛奶Milk";
        }
    
        @Override
        public double cost() {
            // 被装饰者 原来的 价格
            double oldCost = decoratedObj.cost();
            // 对原来的价格进行装饰
            return oldCost + 0.1;
        }
    }
    

测试一下

  • 测试类:

    import com.szlaozicl.designpattern.decorator.beverage.DarkRoast;
    import com.szlaozicl.designpattern.decorator.beverage.Decat;
    import com.szlaozicl.designpattern.decorator.beverage.Espresso;
    import com.szlaozicl.designpattern.decorator.decorator.MilkCondimentDecorator;
    import com.szlaozicl.designpattern.decorator.decorator.MochaCondimentDecorator;
    import com.szlaozicl.designpattern.decorator.decorator.SoyCondimentDecorator;
    import com.szlaozicl.designpattern.decorator.decorator.WhipCondimentDecorator;
    
    /**
     * 装饰者模式 --- 测试
     *
     * @author JustryDeng
     * @date 2019/12/3 12:54
     */
    public class Test {
    
        /** 函数入口 */
        public static void main(String[] args) {
            System.out.println(" ------------- 测试一");
            testOne();
    
            System.out.println("\n ------------- 测试二");
            testTwo();
    
            System.out.println("\n ------------- 测试三");
            testThree();
        }
    
        /** 测试一 */
        private static void testOne() {
            AbstractBeverage beverage = new Espresso();
            System.err.println("装饰前:");
            System.out.println("描述:" + beverage.getDescription());
            System.out.println("价格:" + beverage.cost());
    
            // 给Espresso装饰 牛奶Milk
            beverage = new MilkCondimentDecorator(beverage);
            System.err.println("装饰后:");
            System.out.println("描述:" + beverage.getDescription());
            System.out.println("价格:" + beverage.cost());
        }
    
        /** 测试二 */
        private static void testTwo() {
            AbstractBeverage beverage = new DarkRoast();
            System.err.println("装饰前:");
            System.out.println("描述:" + beverage.getDescription());
            System.out.println("价格:" + beverage.cost());
    
            // 给DarkRoast装饰 牛奶Milk
            beverage = new MilkCondimentDecorator(beverage);
            /*
             * 继续装饰 豆浆Soy
             *
             * 注:虽然直接装饰的对象是MilkCondimentDecorator,但是由
             *    于MilkCondimentDecorator其实是对DarkRoast的装饰,
             *    所以实际上装饰的还是DarkRoast
             */
            beverage = new SoyCondimentDecorator(beverage);
            System.err.println("装饰后:");
            System.out.println("描述:" + beverage.getDescription());
            System.out.println("价格:" + beverage.cost());
        }
    
        /** 测试三 */
        private static void testThree() {
            AbstractBeverage beverage = new Decat();
            System.err.println("装饰前:");
            System.out.println("描述:" + beverage.getDescription());
            System.out.println("价格:" + beverage.cost());
    
            // 给DarkRoast装饰 奶泡Whip
            beverage = new WhipCondimentDecorator(beverage);
            // 继续装饰 摩卡Mocha
            beverage = new MochaCondimentDecorator(beverage);
            // 继续装饰 奶泡Whip
            beverage = new WhipCondimentDecorator(beverage);
            System.err.println("装饰后:");
            System.out.println("描述:" + beverage.getDescription());
            System.out.println("价格:" + beverage.cost());
        }
    }
    

运行测试类main方法,控制台输出:
在这里插入图片描述


拓展 - 自定义InputStream子类的装饰者

        java.io包下大量运用了装饰者模式(或在装饰者模式基础上进行了变形),如InputStream、OutputStream、Reader、Writer等。

  • 以InputStream为例,其UML类图部分如图:
    在这里插入图片描述

  • 自定义一个大写转换为小写的装饰者,并编写测试类:

    import java.io.ByteArrayInputStream;
    import java.io.FilterInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    
    /**
     * 对java.io包下的I/O进行自定义装饰者 测试
     * <p>
     * 说明: java.io包下的I/O就大量用到了 装饰者模式,
     * 这里自己实现一个 InputStream的装饰者 以作练习。
     *
     * @author JustryDeng
     * @date 2019/12/3 12:54
     */
    @SuppressWarnings("all")
    public class IOTest {
    
        /** 函数入口 */
        public static void main(String[] args) throws IOException {
            InputStream in = new LowerCaseInputStream(new ByteArrayInputStream("ABCDEFG".getBytes()));
            int c;
            while ((c = in.read()) > 0) {
                // 输出结果为: abcdefg
                System.out.print((char) c);
            }
        }
    
    }
    
    /**
     * 自定义InputStream子类的装饰者
     *
     * @author JustryDeng
     * @date 2019/12/3 13:17
     */
    @SuppressWarnings("all")
    class LowerCaseInputStream extends FilterInputStream {
    
        public LowerCaseInputStream(InputStream in) {
            super(in);
        }
    
        /**
         * 从此输入流中读取下一个数据字节。返回一个 0 到 255 范围内的 int 字节值。
         * 如果因为已经到达流末尾而没有字节可用,则返回 -1。
         */
        @Override
        public int read() throws IOException {
            int c = super.read();
            if (c == -1) {
                return c;
            }
            return Character.toLowerCase((char) c);
        }
    
    
        /**
         * 从此输入流中将 len 个字节的数据读入到这个 给定的byte数组中。
         */
        @Override
        public int read(byte[] b, int offset, int len) throws IOException {
            int result = super.read(b, offset, len);
            // 注: len为允许一次读取的最大字节数, 而result为该次实际读取的字节数, 所以得以result为准
            // 注: 数据结果存储在b中,只需要将b里面的大写字母转换为小写即可
            for (int i = offset; i < offset + result; i++) {
                b[i] = (byte) Character.toLowerCase((char) b[i]);
            }
            return result;
        }
    }
    
  • 控制台输出:
    在这里插入图片描述


拓展 - 一个不伦不类的cglib代理,实现装饰者模式

提示 同样的,使用jdk动态代理也能比较优雅的实现包装(P.S.代理模式不就是一种特殊的装饰者模式嘛)。

import com.szlaozicl.designpattern.author.JustryDeng;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * 以一个不伦不类的cglib代理实现包装
 *
 * @author {@link JustryDeng}
 * @since 2020/8/4 22:10:36
 */
@SuppressWarnings({"unchecked", "unused"})
public class MonsterProxyWrapper<T> implements MethodInterceptor {
    
    protected final T target;
    
    protected MonsterProxyWrapper(T target) {
        this.target = target;
    }
    
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        preHandle(proxy, method, args, methodProxy);
        Object result = method.invoke(target, args);
        result = postHandle(proxy, method, args, methodProxy, result);
        return result;
    }
    
    /**
     * 前处理
     *
     * @see this#intercept
     */
    protected void preHandle(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {
        // [optional] sub-class ext
    }
    
    /**
     * 后处理
     *
     * @see this#intercept
     * @param result
     *            调用结果
     */
    protected Object postHandle(Object proxy, Method method, Object[] args, MethodProxy methodProxy, Object result) {
        // [optional] sub-class ext
        return result;
    }
    
    /**
     * 获取代理对象
     *
     * P.S. 内部会调用到被代理对象的无参构造
     */
    public T getProxyInstance() {
        Enhancer en = new Enhancer();
        en.setSuperclass(target.getClass());
        en.setCallback(this);
        return (T)en.create();
    }
    
    /**
     * 获取代理对象
     *
     * 提示: 这里en.create(argumentTypes, arguments)构造出来的代理类只是一个空壳子,所以只要能够造出来对象,
     *       argumentTypes和arguments随便传都可以
     */
    public T getProxyInstance(Class<Object>[] argumentTypes, Object[] arguments) {
        Objects.requireNonNull(argumentTypes, "argumentTypes cannot been null");
        Objects.requireNonNull(arguments, "arguments cannot been null");
        Enhancer en = new Enhancer();
        en.setSuperclass(target.getClass());
        en.setCallback(this);
        return (T)en.create(argumentTypes, arguments);
    }
}

提示装饰者模式可结合工厂模式/建造者模式进行优化。

装饰者模式学习完毕 !


^_^ 如有不当之处,欢迎指正

^_^ 参考资料
        《Head First 设计模式》
Eric Freeman & Elisabeth Freeman with Kathy Sierra & Bert Bates著,O’Reilly Taiwan公司译,UMLChina改编

^_^ 测试代码托管链接
         https://github.com/JustryDeng…DesignPattern

^_^ 本文已经被收录进《程序员成长笔记》 ,笔者JustryDeng

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值