引子

 
 
 
 

 

现实世界的装饰器模式

大家应该都吃过手抓饼,本文装饰器模式以手抓饼为模型展开简介
"老板,来一个手抓饼,  加个培根,  加个鸡蛋,多少钱?"
这句话会不会很耳熟,或者自己可能都说过呢?
 
我们看看这句话到底表达了哪些含义呢?
你应该可以看得到这两个基本角色
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. json字符串的拼接

    关于json字符串的解析与拼接,第一次接触,留下个笔记了.......解析,是改的代码,拼接是纯的,解析就不说了,笔记一下拼接了 关于解析主要分三部分,一个是第一层处理,一个是第二层处理,一个是进行& ...

  2. go 结构体

    结构体声明 type Employee struct { ID int Name string Address string DoB time.Time Position string Salary ...

  3. es6的基本数据详解

    一.Set 基本用法:   1)ES6提供了新的数据机构-Set. 它类似于数组,但是成员的值都是唯一的,没有重复的值.Set本身是一个构造函数,用来生成Set数据结构. 先来看一段最简单的代码: 1 ...

  4. django+javascrpt+python实现私有云盘代码

    丁丁:由于篇幅有限,这里暂时只展示python后端代码,前端js代码后面上传,有需要的也可以留言私信我. 1.view.py 使用用户.部门.公司等相关账号的创建,已经个人,部门账号的冻结,删除,相关 ...

  5. su;su -;sudo;sudo -i;sudo su;sudo su - 之间的区别

    今天我们来聊聊su;su -;sudo;sudo -i;sudo su;sudo su -他们之间的区别. su :su 在不加任何参数,默认为切换到root用户,但没有转到root用户家目录下,也就 ...

  6. Oracle 的开窗函数 rank,dense_rank,row_number

    1.开窗函数和分组函数的区别 分组函数是指按照某列或者某些列分组后进行某种计算,比如计数,求和等聚合函数进行计算. 开窗函数是指基于某列或某些列让数据有序,数据行数和原始数据数相同,依然能曾现个体数据 ...

  7. CS231n 第一次作业KNN中本地CIFAR10数据集的载入

    一.问题描述 网上绝大多数作业参考都是在jupyter下运行的,数据集载入过程一般如下: from cs231n.data_utils import load_CIFAR10 #导入数据集,并打印出数 ...

  8. [Swift]LeetCode963. 最小面积矩形 II | Minimum Area Rectangle II

    Given a set of points in the xy-plane, determine the minimum area of any rectangle formed from these ...

  9. 分布式事务之如何基于RocketMQ的事务消息特性实现分布式系统的最终一致性?

    导读 在之前的文章中我们介绍了如何基于RocketMQ搭建生产级消息集群,以及2PC.3PC和TCC等与分布式事务相关的基本概念(没有读过的读者详见

  10. Python必备库

    Python必备库 --default-timeout=100避免网络延迟错误:-U给管理员权限. Python基础库 pip --default-timeout=100 install -U pyg ...