以前谈到过继承会破坏封装,因此采用组合的方式更好,那么装饰者模式就是组合的实现模式
一、模式产生初衷
  孙悟空有七十二般变化,他的每一种变化都给他带来一种附加的本领。他变成鱼儿时,就可以到水里游泳;他变成鸟儿时,就可以在天上飞行。而不管大圣编程什么,在如来佛祖的眼里它永远是那只猴子
装饰模式以对用户透明的方式动态地给对象附加上更多的责任。也就是说用户并不会觉得对象在装饰前和装饰后有什么不同。装饰者模式可以在不创造更多子类的前提下,将对象的功能加以拓展。

  • 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
  • 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。
  • 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
  • 具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。
二、典型例子
1.鸭子模型
鸭子分为(1)真鸭子和(2)橡皮鸭子
真鸭子和橡皮鸭子有相同的外观,另外我们想要真鸭子具有会飞的技能(方法),橡皮鸭子也会飞但是和真鸭子飞的不一样
这时候我们可能会想到,共有方法写到父类,特有方法写到子类,虽然这样可行,但是它的缺点也是很明显的
(1)多个相同功能代码在多个子类中出现
(2)当类定义完后,我们很难在给它们加功能
(3)很难知道鸭子的所有行为
能够在不修改目标类也不使用继承情况下,动态的扩展一个类的功能 。( 目标类中的方法是所有子类共有的,装饰者内的方法是子类特有的 )它是通过创建一个包装对象,也就是装饰者来达到增强目标类的目的。
要求:
1.装饰者类和目标类都要 实现相同的接口 ,或继承自相同抽象类(增强的方法在接口中定义)
2.装饰者类中要 有目标类的引用作为成员变量 ,而具体赋值一般通过带参构造器来完成
//接口
public interface Duck {void call();
}
//被装饰的具体鸭子
public class ConcreteDuck implements Duck{@Overridepublic void call() {System.out.println("guagua");}
}
//装饰者
public class RubberDuck implements Duck {private ConcreteDuck cDuck;public RubberDuck(ConcreteDuck cDuck) {this.cDuck = cDuck;}@Overridepublic void call() {cDuck.call();System.out.println("我是一只玩具鸭");     }
}
2.高级装饰者(装饰者链)每个装饰者只进行一种装饰
  孙悟空有七十二般变化,他的每一种变化都给他带来一种附加的本领。他变成鱼儿时,就可以到水里游泳;他变成鸟儿时,就可以在天上飞行。
  本例中,Component的角色便由鼎鼎大名的齐天大圣扮演;ConcreteComponent的角色属于大圣的本尊,就是猢狲本人;Decorator的角色由大圣的七十二变扮演。而ConcreteDecorator的角色便是鱼儿、鸟儿等七十二般变化。

“齐天大圣”接口定义了一个move()方法,这是所有的具体构件类和装饰类必须实现的
//大圣的尊号
public interface TheGreatestSage { public void move();
}
具体构件角色“大圣本尊”猢狲类
public class Monkey implements TheGreatestSage {@Overridepublic void move() {//代码System.out.println("Monkey Move");}}
抽象装饰角色“七十二变”
public class Change implements TheGreatestSage {private TheGreatestSage sage;public Change(TheGreatestSage sage){this.sage = sage;}@Overridepublic void move() {// 代码sage.move();}}
具体装饰角色“鱼儿”
public class Fish extends Change {public Fish(TheGreatestSage sage) {super(sage);}@Overridepublic void move() {System.out.println("Fish Move");}
}
具体装饰角色“鸟儿”
public class Bird extends Change {public Bird(TheGreatestSage sage) {super(sage);}@Overridepublic void move() {// 代码System.out.println("Bird Move");}
}
测试类
public class Client {public static void main(String[] args) {TheGreatestSage sage = new Monkey();// 第一种写法TheGreatestSage bird = new Bird(sage);TheGreatestSage fish = new Fish(bird);// 第二种写法//TheGreatestSage fish = new Fish(new Bird(sage));fish.move(); }}
上面的例子中,系统把大圣从一只猢狲装饰成了一只鸟儿(把鸟儿的功能加到了猢狲身上),然后又把鸟儿装饰成了一条鱼儿(把鱼儿的功能加到了猢狲+鸟儿身上,得到了猢狲+鸟儿+鱼儿)。

三、装饰模式使用场景
1.装饰模式的优点
   (1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
  (2)通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
2.装饰模式的缺点
   由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
3.装饰模式的简化
   大多数情况下,装饰模式的实现都要比上面给出的示意性例子要简单。
  如果只有一个ConcreteComponent类,那么可以考虑去掉抽象的Component类(接口),把Decorator作为一个ConcreteComponent子类。如下图所示:(这时ConcreteComponent担任两个角色)

  如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。甚至在只有两个ConcreteDecorator类的情况下,都可以这样做。如下图所示:

4. 透明性的要求
   装饰模式对客户端的透明性要求程序不要声明一个ConcreteComponent类型的变量,而应当声明一个Component类型的变量。
  用孙悟空的例子来说,必须永远把孙悟空的所有变化都当成孙悟空来对待,而如果把老孙变成的鱼儿当成鱼儿,而不是老孙,那就被老孙骗了,而这时不应当发生的。下面的做法是对的:
TheGreatestSage sage = new Monkey();
TheGreatestSage bird = new Bird(sage);
 而下面的做法是不对的:
Monkey sage = new Monkey();
Bird bird = new Bird(sage);
5. 半透明的装饰模式
   然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能。 在增强性能的时候,往往需要建立新的公开的方法 。即便是在孙大圣的系统里,也需要新的方法。比如齐天大圣类并没有飞行的能力,而鸟儿有。这就意味着鸟儿应当有一个新的fly()方法。再比如,齐天大圣类并没有游泳的能力,而鱼儿有,这就意味着在鱼儿类里应当有一个新的swim()方法。
  这就导致了大多数的装饰模式的实现都是“半透明”的,而不是完全透明的。换言之,允许装饰模式改变接口,增加新的方法。这意味着客户端可以声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法
TheGreatestSage sage = new Monkey();
Bird bird = new Bird(sage);
bird.fly();
半透明的装饰模式是介于装饰模式和适配器模式之间的。适配器模式的用意是改变所考虑的类的接口,也可以通过改写一个或几个方法,或增加新的方法来增强或改变所考虑的类的功能。大多数的装饰模式实际上是半透明的装饰模式,这样的装饰模式也称做半装饰、半适配器模式。
6.与其它模式的关系
(1)适配器模式
适配器的目的是改变所考虑的对象接口而不改变功能,实现不同接口
装饰者的目的是保持接口,从而增强所考虑对象的性能,实现相同接口
(2)装饰模式与策略模式
装饰模式将一个东西的表皮换掉,而保持它的本质。策略模式恰恰相反,它在保持接口不变的情况下,使具体算法可以互换。装饰模式的实现要求Component类尽量的“轻”,而策略模式要求抽象策略类尽量的“重”。
(3)装饰模式与合成模式
装饰模式常常用在合成模式的扩展上。使用继承关系合成模式的行为很困难。如果仅仅对抽象构件类还是合成类或者是树叶类使用继承办法,都会导致多态性被破坏。唯一能够保持多态性的办法,便是对所有的三种角色都使用继承关系,而这显然不是很好的办法。装饰模式是继承的替代方案,它可以动态的为合成模式增加新的职责。
四、装饰者实际应用
1.有一个电子销售系统需要打印出顾客所购买的商品的发票。一张发票可以分为三个部分: 发票头部(Header):上面有顾客的名字,销售的日期 发票主部:销售的货物清单,包括商品名字、购买数量、单价、小计 发票的尾部(Footer):商品总金额 分析:因为发票的头部和尾部可以有很多种格式,因此系统的设计必须给出足够的灵活性,使得一个新的头部和尾部格式都能较为容易得插入到系统中;同时本系统的客户端必须可以随意地选择某一种头部格式和尾部格式的组合并与主部格式结合起来。 使用装饰模式,系统发票的头部和尾部分别可由具体装饰类HeaderDecorator和具体装饰类FooterDecorator来代表。有多少种头部和尾部,就可以有多少种相应的具体装饰类。这样,这些头部和尾部的组合就可以给出大量的选择。

    继承的主要问题:当一个类需要缓冲区的时候就需要创建一个子类解决缓冲问题,当这个类子类需要缓冲区就要建立一个子类的子类来实现缓冲区,这样代码复用性不强,我们就可以将缓冲的技术放到一个类之中,然后采用多态的技术实现多个类的缓冲。

十、结构型模式——装饰者模式相关推荐

  1. Java设计模式之结构型:装饰器模式

    一.什么是装饰器模式: 当需要对类的功能进行拓展时,一般可以使用继承,但如果需要拓展的功能种类很繁多,那势必会生成很多子类,增加系统的复杂性,并且使用继承实现功能拓展时,我们必须能够预见这些拓展功能, ...

  2. Java设计模式之结构型:享元模式

    一.什么是享元模式: 享元模式通过共享技术有效地支持细粒度.状态变化小的对象复用,当系统中存在有多个相同的对象,那么只共享一份,不必每个都去实例化一个对象,极大地减少系统中对象的数量.比如说一个文本系 ...

  3. 结构型设计模式之组合模式

    结构型设计模式之组合模式 组合模式 应用场景 优缺点 主要角色 组合模式结构 分类 透明组合模式 创建抽象根节点 创建树枝节点 创建叶子节点 客户端调用 安全组合模式 创建抽象根节点 创建树枝节点 创 ...

  4. 结构型模式-装饰器模式

    1.概述 快餐店有炒面.炒饭这些快餐,可以额外附加鸡蛋.火腿.培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦. 使用继承的方式存在的问题: 扩展性不好 ...

  5. 设计模式 结构型模式 -- 装饰者模式(概述 快餐店案例 模式优点 使用场景 源码解析 BufferedWriter 和代理模式的区别)

    1. 装饰者模式 1.1 概述 我们先来看一个快餐店的例子: 快餐店有炒面.炒饭这些快餐,可以额外附加鸡蛋.火腿.培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得 ...

  6. 创建型、结构型、行为型模式(2)

    来源:http://blog.csdn.net/wulingmin21/article/details/6757111 创建型模式 Singleton模式解决的是实体对象个数的问题. 除了Single ...

  7. 创建型、结构型、行为型模式(1)

    来源:http://blog.csdn.net/wulingmin21/article/details/6753363 目的 创建型模式 Creational Pattern 结构型模式 Struct ...

  8. 设计模式(结构型)之享元模式(Flyweight Pattern)

    PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbob ...

  9. 设计模式——结构型之用桥梁模式(Bridge Pattern)将“抽象”与“实现”解耦(五)

    引言 相信对于现实生活中这样的情况都不陌生,比如说开关与它具体控制的电器,开关的类型多种多样,而电器也是千变万化,两者之间相对独立变化却又耦合在一起,再比如说奶茶店的奶茶,有不同规格大小.不同口味.不 ...

最新文章

  1. Ubuntu开机自启动与sh脚本
  2. linux 加载u盘、光盘、软盘 mount使用指南
  3. 【oracle】sqlnet.ora 访问控制策略
  4. ASP.NET中IP地址,当前用户的方法属性大整合- -
  5. 整理了一份 Docker系统知识,从安装到熟练操作看这篇就够了 | 原力计划
  6. C语言课后习题(13)
  7. java 动态编译_老生常谈Java动态编译(必看篇)
  8. Ansible 系统概述与部署(1)
  9. 森林病虫防治系统 (七)
  10. C++模版类List实现
  11. Java下载安装与环境配置
  12. html旅游网站设计与实现——绿色古典旅游景区 HTML+CSS+JavaScript
  13. AI2021下载 Illtrator2021安装教程
  14. 51单片机项目设计:WiFi视频小车制作教程、正点原子wifi摄像头模块应用、手机wifi控制
  15. 最强 IOS系统改定位
  16. 人工智能深度学习Caffe框架介绍,优秀的深度学习架构 1
  17. PHP架构师“精简”进阶路线规划
  18. android中如何如何让dailog横屏显示
  19. 微信卡券开发HelloWord
  20. Material Design 总结

热门文章

  1. 106码号办理需要具备的条件和需要的材料
  2. ERROR: child process failed, exited with error number 51
  3. cf819C Jatayu‘s Balanced Bracket Sequence
  4. sql注入基础原理(超详细)
  5. phpcms v9和discuz X3.1实现同步登陆退出论坛(已实现)
  6. 测试用例_等价类划分方法
  7. 魔方(8)123魔方、223魔方、233魔方、香蕉魔方
  8. 计算机一级考试B十类理论题,计算机一级考试试题
  9. 2019年EI收录的会议(计算机/网络通信方向)
  10. [源码和文档分享]基于JAVA的葫芦娃 — 最终之战