装饰模式
装饰模式动态的将责任附加到对象上。就增加功能来说,装饰模式比生成子类更为灵活。装饰模式在GOF95中分类为结构模式。
- 装饰者和被装饰对象有相同的超类型。这里使用继承的目的是类型匹配,而不是为了通过继承获得行为
- 可以用一个或多个装饰者包装一个对象(多层包装),对象可以在运行时被动态装饰
- 由于装饰器与被装饰对象就有相同的超类型,所有在任何需要原始对象的场合,都可以使用装饰对象代替之(里氏替换原则)
- 装饰器可以在被装饰对象的行为之前或(和)之后,加上自己的行为,甚至完全替换被装饰对象的行为,以达到特定目的。这是装饰模式的关键点,这些新的行为是通过组合得到的
装饰模式具有以下优点:
- 比继承灵活:在运行时动态装饰,且可以多层包装
- 更容易复用:通过将功能分散到简单的、功能相对单一的装饰器中,通过层层包装复用
装饰模式具有以下缺点:
- 被装饰的内层对象,其真实类型被隐藏,因此客户端难以对某种具体类型做特定的操作
- 装饰模式常常导致设计中出现大量的小类,对客户端程序员造成困扰
- 不同装饰器之间不能有关联性
考虑一个煎饼果子摊位,根据食材的不同进行收费:绿豆面饼皮、杂粮饼皮、玉米饼皮、荞麦饼皮,这些是主料,食客只能选择一种;鸡蛋、鸭蛋、火腿肠、培根、馃子、馃篦、雪菜、葱花、蒜泥、榨菜……等等这些是辅料,可以任意组合,制作成各式各样的煎饼果子。这样的需求如果使用继承来实现,将导致“类爆炸”的发生,而装饰模式却可以很轻松的满足需要:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
public class TianjinHamburgerApplication { //食材接口 static interface Material{ //计算价格 int getCost(); } //面皮 abstract static class Sheet implements Material{ } //绿豆面皮 static class GreenBeanSheet extends Sheet{ public int getCost(){ return 2; } } //玉米面皮 static class CornSheet extends Sheet{ public int getCost(){ return 1; } } //辅材,可以任意搭配,以满足不同口味需求 static abstract class Side implements Material{ private Material material; public Side(Material material){ this.material = material; } public int getCost() { return material.getCost(); } } //鸡蛋 static class Egg extends Side{ private int num; public Egg( int num, Material material ){ super(material); this.num = num; } @Override public int getCost() { return super.getCost() + this.num * 1; } } //油条 static class FriedStick extends Side{ public FriedStick( Material material ) { super( material ); } public int getCost() { return super.getCost() + 2; } } //雪菜 static class PickledMustard extends Side{ public PickledMustard( Material material ) { super( material ); } public int getCost() { return super.getCost() + 1; } } public static void main( String[] args ) { //给我来个煎饼果子 Material tianjinHamburger = new GreenBeanSheet(); //来根油条 tianjinHamburger = new FriedStick( tianjinHamburger ); //加十个鸡蛋 tianjinHamburger = new Egg( 10,tianjinHamburger ); //加点雪菜啊 tianjinHamburger = new PickledMustard( tianjinHamburger ); //结账 System.out.println(tianjinHamburger.getCost()); } } |
在Java I/O框架中,装饰模式被广泛的使用,以提供缓冲、回退、按行处理等功能,下面是Java I/O框架输入流的部分类图:
其中FilterInputStream是一个抽象的装饰器,它具有InputStream接口,它的子类用来装饰基础的输入流,提供各种功能。除了InputStream以外,OutputStream、Writer、Reader采用了类似的设计。
AOP是一种编程范式,它从另外一个角度考虑程序结构以完善OO。在传统的OO开发中,都是从“纵向”角度分析系统,因此系统架构图都是自上而下,层层依赖。系统的每个业务模块常常分为表现层、逻辑层、数据层。在设计过程中会发现,不同的模块会存在一些公用的功能,例如日志管理、事物管理、安全性检查等。这时候,就应当改为“横向”角度来分析系统,考虑如何把这些公用的功能独立出来实现,以便模块化、重用。AOP框架提供一些原语(切面、切入点、通知等),允许将那些业务无关、但是被业务模块共同调用的逻辑或责任封装起来,减少系统的重复代码,降低模块耦合度。更重要的是,业务模块不知道AOP通用模块的存在,这意味着业务模块和通用模块可以独立演化。
装饰模式与AOP在思想上有共同之处,都可以透明的为业务功能对象增加功能,但是AOP的实现方式更加灵活、可配置,且AOP将主动调用变成了被动织入。
Leave a Reply