引子

 
 
 
 

 

现实世界的装饰器模式

大家应该都吃过手抓饼,本文装饰器模式以手抓饼为模型展开简介
"老板,来一个手抓饼,  加个培根,  加个鸡蛋,多少钱?"
这句话会不会很耳熟,或者自己可能都说过呢?
 
我们看看这句话到底表达了哪些含义呢?
你应该可以看得到这两个基本角色
1.手抓饼                                 核心角色
2.配菜(鸡蛋/培根/香肠...)          装饰器角色
 
你既然想要吃手抓饼,自然你是奔着手抓饼去的,对吧
所以,你肯定会要一个手抓饼,至少是原味的
然后可能根据你的口味或者喜好添加更多的配菜
这个行为很自然,也很正常.
 
如果是在代码的世界里面,你怎么描述:  顾客 购买 手抓饼     这一行为呢?  
顾客Customer   顾客有方法buy  然后有一个手抓饼HandPancake,看起来是这样子的
那么问题来了
如何表示 加了鸡蛋的手抓饼,或者加了鸡蛋和培根的手抓饼呢?
 
一种很可能方式是把他们都当成手抓饼的不同种类,也就是使用继承或者说实现类的形式
那么我们有多少种手抓饼呢?
原味手抓饼/加鸡蛋手抓饼/加鸡蛋加培根手抓饼/加鸡蛋加烤肠手抓饼/加鸡蛋加培根加烤肠手抓饼手抓饼/.......
很显然,这就是数学中的组合,最终的个数跟我们到底有多少种配菜有关系
如果按照这种思维方式,我们将会有无数个手抓饼类,而且如果以后多了一种配菜,类的个数将会呈现爆炸式的增长
这是你想要的结果么?
 
在现实世界里面,你会很自然的说 "老板,来一个手抓饼,  加个培根,  加个鸡蛋,多少钱?""
那么为什么在程序世界里面,你却很可能说"老板,给我来一个加了鸡蛋加了培根的那种手抓饼" 呢?
 
 

手抓饼代码示例

手抓饼接口和具体的一家店铺提供的手抓饼

package decorator;
/**
* Created by noteless on 2018/9/6.
* Description:手抓饼接口 描述抽象的手抓饼
*/
public interface HandPancake {
/**
* 提供手抓饼
*/
String offerHandPancake();
/**计算手抓饼的价格
* @return
*/
Integer calcCost();
} package decorator;
/**
* Created by noteless on 2018/9/6.
* Description: Noteless 家的手抓饼
*/
public class NotelessHandPancake implements HandPancake {
/**
* 提供noteless 家的手抓饼一份
*/
@Override
public String offerHandPancake() {
return " noteless 家的手抓饼";
}
/**计算 noteless 家 一份手抓饼的价格
* @return
*/
@Override
public Integer calcCost() {
return 3;
}
}

配菜抽象类(装饰器)

package decorator;
/**
* Created by noteless on 2018/9/6.
* Description:装饰器类实现了手抓饼接口,具有了手抓饼的类型
*/
public abstract class Decorator implements HandPancake{
private HandPancake handPancake;
Decorator(HandPancake handPancake){
this.handPancake = handPancake;
}
/**提供手抓饼
* @return
*/
@Override
public String offerHandPancake() {
return handPancake.offerHandPancake();
} /**提供手抓饼的价格
* @return
*/
@Override
public Integer calcCost() {
return handPancake.calcCost();
}
}

具体的配菜(具体的装饰)

package decorator;
/**
* Created by noteless on 2018/9/6.
* Description:培根
*/
public class Bacon extends Decorator {
Bacon(HandPancake handPancake){
super(handPancake);
} @Override
public String offerHandPancake() {
return super.offerHandPancake()+" 加培根";
}
@Override
public Integer calcCost() {
return super.calcCost()+4;
}
} package decorator;
/**
* Created by noteless on 2018/9/6.
* Description:鸡蛋
*/
public class Egg extends Decorator {
Egg(HandPancake handPancake){
super(handPancake);
}
@Override
public String offerHandPancake() {
return super.offerHandPancake()+"加鸡蛋";
}
@Override
public Integer calcCost() {
return super.calcCost()+2;
}
} package decorator;
/**
* Created by noteless on 2018/9/6.
* Description:烤肠
*/
public class Sausage extends Decorator {
Sausage(HandPancake handPancake){
super(handPancake);
}
@Override
public String offerHandPancake() {
return super.offerHandPancake()+" 加香肠";
}
@Override
public Integer calcCost() {
return super.calcCost()+3;
}
} package decorator;
/**
* Created by noteless on 2018/9/6.
* Description:青菜
*/
public class Vegetable extends Decorator {
Vegetable(HandPancake handPancake){
super(handPancake);
}
@Override
public String offerHandPancake() {
return super.offerHandPancake()+" 加青菜";
}
@Override
public Integer calcCost() {
return super.calcCost()+1;
} }

顾客

package decorator;
/**
* Created by noteless on 2018/9/6.
* Description:顾客具有名字,然后购买手抓饼
*/
public class Customer {
private String name;
Customer(String name){
this.name = name;
} public void buy(HandPancake handPancake){
  System.out.println(name+"购买了 : "+handPancake.offerHandPancake()+
  " 一份, 花了 : "+handPancake.calcCost()+"块钱~");
  System.out.println();
}
}

测试类

package decorator;

/**
* Created by noteless on 2018/9/6.
* Description:
* 手抓饼3块
* Sausage 烤肠 3块
* Bacon 培根 4块
* Egg 鸡蛋2块
* Vegetable 青菜 1块
*/ public class Test {
public static void main(String ...strings){ //有一个顾客张三,他想吃手抓饼了,来了一个原味的
Customer customerA = new Customer("张三");
customerA.buy(new NotelessHandPancake()); //有一个顾客李四,他想吃手抓饼了,他加了一根烤肠
Customer customerB = new Customer("李四");
customerB.buy(new Sausage(new NotelessHandPancake())); //有一个顾客王五,他想吃手抓饼了,他加了一根烤肠 又加了培根
Customer customerC = new Customer("王五");
customerC.buy(new Bacon(new Sausage(new NotelessHandPancake()))); //有一个顾客王五的兄弟,他想吃手抓饼了,他加了培根 又加了烤肠
Customer customerC1 = new Customer("王五的兄弟");
customerC1.buy(new Sausage(new Bacon(new NotelessHandPancake()))); //有一个顾客赵六,他想吃手抓饼了,他加了一根烤肠 又加了2份培根
Customer customerD = new Customer("赵六");
customerD.buy(new Bacon(new Bacon(new Sausage(new NotelessHandPancake()))));
//有一个顾客 王二麻子,他想吃手抓饼了,特别喜欢吃青菜 来了三分青菜
Customer customerE = new Customer("王二麻子");
customerE.buy(new Vegetable(new Vegetable(new Vegetable(new NotelessHandPancake()))));
//有一个顾客 有钱人 王大富 来了一个全套的手抓饼
Customer customerF = new Customer("王大富");
customerF.buy(new Egg(new Vegetable(new Bacon(new Sausage(new NotelessHandPancake())))));
}
}
我们有一个顾客Customer类,他拥有buy方法,可以购买手抓饼
手抓饼接口为 HandPancake  具体的手抓饼为NotelessHandPancake
然后提供了一个配菜类,这个配菜类的行为和手抓饼是一致的,在提供手抓饼的同时还能够增加一些额外的
然后还有四个具体的配菜 培根 香肠 鸡蛋 青菜
 
运行测试类,会算账的亲们,看看单价是否还对的上?

UML图

懒得画了,IDEA自动生成的
 

手抓饼装饰器模式中的根本

上面的代码还是比较清晰的,如果你没办法仔细看进去的话,我们换一种思维方式来思考手抓饼的装饰器模式
 
你可以这么理解:
你过去手抓饼的摊位那边,你说老板来一个手抓饼,加培根,加鸡蛋
 
摊主那边是这样子的:
老板负责直接做手抓饼
旁边站着漂亮的老板娘,手里拿着手抓饼的袋子,负责帮你装袋,你总不能直接用手拿饼,对吧
 
接下来我们说下过程:
老板马上就开始做手抓饼了,做好了之后,老板把手抓饼交给了旁边站着的老板娘
老板娘在给装袋并且交给你之前
把鸡蛋和培根放到了你的手抓饼里面
然后又放到了包装袋子里面
接着递给了你
 
你说到底是老板娘手里包装好的手抓饼是手抓饼  还是老板做好的热气腾腾的是手抓饼呢?
 
其实,老板做好的热气腾腾的手抓饼,正是我们上面提供出来的具体的手抓饼
老板娘手里拿着的手抓饼包装袋来包装手抓饼,也是手抓饼,只不过是包装了下,这个就是装饰器的概念
 
所以装饰器模式还有一个名字  包装器模式(Wrapper)
 
 
解决问题的根本思路是使用组合替代了继承
上面我们也进行了分析,继承会出现类的个数的爆炸式增长
组合,不仅仅动态扩展了类的功能,而且还很大程度上减少了类的个数
不过显然,如果你的装饰类过多,虽说比继承好很多,但是问题还是一样的,都会类过多
 
根本:  是你还有你
 
我们上面的类的结构中,装饰器包含一个手抓饼对象作为属性,他也实现了手抓饼接口
所以我们说,是你还有你
每次自己返回结果之前,都还会调用自己含有的对象的方法
 
看下调用流程, 你说它的形式跟递归调用有什么区别?
 
 

面向对象中的适配器模式详解

意图

动态的给一个对象添加额外的职责,简单说,动态的扩展职责
就增加功能来说,装饰器模式比生成子类要更加灵活
所以装饰器模式主要解决继承子类爆炸增长的问题

 

装饰器模式中的角色

Component 抽象构建 装饰器模式中必然有一个最基本最原始的->
接口/抽象类
来充当抽象构建
抽象的手抓饼    HandPancake
ConcreteComponent 具体构建 
是抽象构建的一个具体实现
你要装饰的就是它
具体某家店铺生产的手抓饼   NotelessHandPancake
Decorator 装饰抽象类 一般是一个抽象类
实现抽象构建
并且必然有一个private变量指向Component 抽象构建
配菜抽象类(装饰器)   Decorator
ConcreteDecorator 具体的装饰类 必须要有具体的装饰角色
否则装饰模式就毫无意义了
具体的配菜(具体的装饰)    Bacon Egg  Vegetable Sausage
 
仔细体味下<是你 还有你>
Decorator 是Component 还有Component
 
OOP中的一个重要设计原则
类应该对扩展开放,对修改关闭
所谓修改就是指继承,一旦继承,那么将会对部分源代码具有修改的能力,比如覆盖方法,所以你尽量不要做这件事情
扩展就是指的组合,组合不会改变任何已有代码,动态得扩展功能
 

装饰器模式优点

装饰类和被装饰类可以独立发展,而不会相互耦合
 
Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,
而Decorator也不用知道具体的构件
装饰模式是继承关系的一个替代方案
我们看装饰类Decorator,不管装饰多少层,他始终是一个Component,实现的还是is-a的关系,所以他是继承的一种良好替代方案
如果设计得当,装饰器类的嵌套顺序可以任意,比如

一定要注意前提,那就是你的装饰不依赖顺序

装饰器模式缺点

装饰器模式虽然从数量级上减少了类的数量,但是为了要装饰,仍旧会增加很多的小类
这些具体的装饰类的逻辑将不会非常的清晰,不够直观,容易令人迷惑
装饰器模式虽然减少了类的爆炸,但是在使用的时候,你就可能需要更多的对象来表示继承关系中的一个对象
多层的装饰是比较复杂,比如查找问题时,被层层嵌套,不容易发现问题所在
 

装饰器模式使用场景

当你想要给一个类增加功能,然而,却并不想修改原来类的代码时,可以考虑装饰器模式
如果你想要动态的给一个类增加功能,并且这个功能你还希望可以动态的撤销,就好像直接拿掉了一层装饰物

装饰器模式的简化变形

装饰器模式是对继承的一种强有力的补充与替代方案,装饰器模式具有良好的扩展性
再次强调,设计模式是一种思维模式,没有固定公式
如果需要的话,可以进行简化
如果省略抽象构建,装饰器直接装饰一个类的话,
那么可以装饰器直接继承这个类

如果只有一个具体的装饰器类,那么可以省略掉 Decorator
ConcreteDecorator 充当了ConcreteDecorator 和 Decorator的角色
 
 
 

设计模式是作为解决问题或者设计类层级结构时的一种思维的存在,而不是公式一样的存在!
 

 
 

装饰器模式 Decorator 结构型 设计模式 (十)的更多相关文章

  1. Java设计模式07:常用设计模式之装饰器模式(结构型模式)

    1. Java之装饰器模式(Decorator Pattern) (1)概述:     装饰模式在Java种使用也很广泛,比如我们在重新定义按钮.对话框等时候,实际上已经在使用装饰模式了.在不必改变原 ...

  2. 设计模式(八)装饰器模式Decorator(结构型)

    设计模式(八)装饰器模式Decorator(结构型) 1. 概述 若你从事过面向对象开发,实现给一个类或对象增加行为,使用继承机制,这是所有面向对象语言的一个基本特性.如果已经存在的一个类缺少某些方法 ...

  3. 桥接模式 桥梁模式 bridge 结构型 设计模式(十二)

      桥接模式Bridge   Bridge 意为桥梁,桥接模式的作用就像桥梁一样,用于把两件事物连接起来   意图 将抽象部分与他的实现部分进行分离,使得他们都可以独立的发展.  意图解析 依赖倒置原 ...

  4. 享元模式 FlyWeight 结构型 设计模式(十五)

    享元模式(FlyWeight)  “享”取“共享”之意,“元”取“单元”之意. 意图 运用共享技术,有效的支持大量细粒度的对象. 意图解析 面向对象的程序设计中,一切皆是对象,这也就意味着系统的运行将 ...

  5. 【PHP设计模式 09_ZhuangShiQi.php】装饰器模式 (decorator)

    <?php /** * [装饰器模式 (decorator)] * 有时候发布一篇文章需要经过很多人手,层层处理 */ header("Content-type: text/html; ...

  6. 装饰器模式-Decorator(Java实现)

    装饰器模式-Decorator(Java实现) 装饰器模式允许向一个现有的对象添加新的功能, 同时又不改变其结构. 其中 "现有对象"在本文中是StringDisplay类. 添加 ...

  7. 组合模式 合成模式 COMPOSITE 结构型 设计模式(十一)

    组合模式(合成模式 COMPOSITE) 意图 将对象组合成树形结构以表示“部分-整体”的层次结构. Composite使得用户对单个对象和组合对象的使用具有一致性.   树形结构介绍 为了便于理解, ...

  8. 12、Decorator 装饰器 模式 装饰起来美美哒 结构型设计模式

    1.Decorator模式 装饰模式又名包装(Wrapper)模式.装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 装饰器模式(Decorator Pattern)允许向一个现 ...

  9. 设计模式学习心得<装饰器模式 Decorator>

    装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰类,用来包装 ...

随机推荐

  1. 打包java程序生成exe

    打包java程序生成exe 目标 我们知道c++的控制台程序编译运行以后就会生成一个exe可执行文件,在电脑上可以直接双击运行,但是java是跨平台的语言,编译运行以后的字节码文件.class是和平台 ...

  2. Java_接口与抽象类

    接口: 接口,英文interface,在java中,泛指供别人调用的方法或函数.接口是对行为的一种抽象. 语法: [public] interface InterfaceName{} 注意: 1)接口 ...

  3. [LeetCode] Maximize Distance to Closest Person 离最近的人的最大距离

    In a row of seats, 1 represents a person sitting in that seat, and 0 represents that the seat is emp ...

  4. 干掉safedog命令

    sc delete safedogguardcenter    shutdown -r -t 00 两条命令搞定

  5. Linux常用服务器搭建

    1.Linux常用服务器构建-ftp服务器 ftp服务器 FTP 是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”. 用于Internet上的控制文件 ...

  6. 在 ns-3.25中添加 plc(电力线载波) 模块

    上一篇安装好了 ns-3.25,这里继续往里添加 plc 模块.整理之前现推荐一个网站,大家可以参考. 英属哥伦比亚大学(University of British Columbia,UBC)提供NS ...

  7. ThinkInJava之内部类

    一:内部类概述 将一个类的定义放在另一个类的内部,这就是内部类.内部类是Java一种非常有用的特征,因为他允许你把一些逻辑相关的数据组织在一起,并控制它的可见性. 二:内部类的创建 我们都知道类的创建 ...

  8. 分布式_zookeeper

    分布式协调服务-zookeeper 分布式环境的特点 1.分布性 2.并发性 程序运行过程中,并发性操作是很常见的.比如同一个分布式系统中的多个节点,同时访问一个共享资源.数据库.分布式存储 3.无序 ...

  9. P5241 序列

    分析 题目要求在一共加入若干条边是的不同B序列的个数:方便统计,我们不妨让一个B序列对应一种特定的操作来构造,具体如下 从1开始按从小到大加入点,始终维护一个顺序为加点次序的链:每次加入一条有向边 不 ...

  10. IntelliJ的Scala配置

    打开IDE: file->New->Project->Maven->Next 名字随便命名,到后面可以改的: 存放代码项目的位置,名字还是随便命名,可以改的,但是路径要自定义好 ...