声明: 本文
内容属于《Head First 设计模式》阅读笔记
,文中涉及到的知识案例等直接或间接来源于该书。《Head First 设计模式》
通过有趣的图表+文字的形式,让人自然学习设计模式,非常棒
,推荐阅读
。
装饰者模式概念:
动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
要点说明:
序号 | 说明 |
---|---|
① | 装饰者与被装饰者需要具有同一个超类(即:这两者的某个祖先类要相同)。 |
② | 程序需要的也只是超类,而不具体到哪一个子类,这样一来该超类的任何子类都能被程序认可。 |
③ | 装饰者中应持有被装饰者实例,这样一来,装饰者里面就能调用到被装饰者的相关方法,进而进行装饰。 |
④ | 装饰者中持有被装饰者时,声明被装饰者的类型时应声明为超类的类型(而不声明为某个具体的被装饰者类)。这样一来,(因为装饰者、被装饰者都是这个超类的子类,所以)装饰者不仅可以装饰被装饰者,装饰者还可以装饰装饰者。 |
⑤ | 装饰者应重写(所有装饰后)会受到影响的方法。 注:重写的方法来自超类。 |
图示(示例)说明:
案例(辅助理解):
提示: 装饰者模式使用灵活,下面只是一个简单的例子,在不同的情境下,可能有不同的表现形式。
情景(需求)介绍:
现有一家星巴兹咖啡馆,里面有各种咖啡(焦炒咖啡、低浓咖啡、浓缩咖啡、首选咖啡)以及各种调味料(牛奶、摩卡、豆浆、奶泡)。现在的需求是:客人在点咖啡时还可以点任意(任意种类、任意数量的)调料。
从装饰者模式出发:
- 各种咖啡,各种调料的共同超类,为饮料beverage。
- 各种咖啡为饮料的直接实现,为被装饰者。
- 各种调料是饮料的附加添加品,为装饰者。
- 可以将装饰者的共有逻辑进行抽取,抽取为一个抽象类。
于是得到以下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