概念


“用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能”

“动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活”

何时用


需要扩展一个类的功能,或给一个类增加附加责任

需要动态的给一个对象增加功能,且可以动态地撤销它

需要增加一些基本功能的排列组合而产生的大量的功能,而使得继承变得非常困难的时候

实现构件


抽象构件(Component)

表示“被”装饰的本体的抽象定义,这个定义通常是一个接口(Interface),定义了若干方法(能力),这些方法可以用来在被具体装饰角色(ConcreteDecorator)实现时改变原有构件本体的方法(能力),如原来本体伤害输出是 10,装饰角色把它再增加 10 而不会影响本体的原有逻辑(代码)。

具体构件(ConcreteComponent)

表示实现了抽象构件(Component)的对象,即将要接受附加能力的对象,本文中比喻的“本体”。

抽象装饰(Decorator)

持有一个抽象构件的实例(即具体构件),并定义(实现)与抽象构件接口一致的接口。抽象装饰的(部分)作用应该是应用了依赖倒置原则,在结构上使具体装饰(ConcreteDecorator)不要直接依赖于抽象构件,因为二者作用性质也不同,直接依赖灰常奇怪,就好像都是50岁的男子也不能把隔壁老王当成爸爸一样。

具体装饰(ConcreteDecorator)

实现了抽象装饰的定义,负责给构件对象添加附加能力的对象。具体装饰通过实现抽象装饰定义的接口,拥有了和具体构件一样的“能力”(方法/函数/属性),再通过抽象装饰定义中所持有的抽象构件的实例而获得对该实例“相同”能力的结果,并在结果上进行一些装饰。

实现步骤


  • 定义抽象构件,提供抽象接口
  • 定义具体构件并实现抽象构件,构造后的具体构件即理解为“本体”,被装饰的对象
  • 定义抽象装饰,它要做两件事,实现抽象构件和保存一个抽象构件对象
  • 定义具体装饰,具体装饰要实现抽象装饰,并在实现的接口方法中对构件进行具体装饰操作
  • 之后,要增加“本体”就创建具体构件,要增加装饰物,就创建具体装饰
  • 使用时,把本体“传递进”装饰对象,在装饰对象(同样继承自抽象构件)的方法里去使用本体的方法和结果,加工它,并输出进行了“调整”的结果

原理与代码


用 Golang 描述代码结构(代码模仿自 github,但不好意思忘记来自哪位作者了)

package component

/*
抽象构件(Component)接口
*/
type Beverage interface {
// 计算价格
Cost() int
// 返回描述
Me() string
}

上面定义了抽象构件接口“饮料”接口,它包含了两个方法,输出价格和描述自己,饮料接口作为最底层接口是所有饮料都必须要实现的(能力)。

package component

type Tea struct {
Beverage // 作用?
name string
price int
} func (self *Tea) Me() string {
return self.name
} func (self *Tea) Cost() int {
return self.price
}

有了饮料这个概念,就在此基础之上创建第一款具体的产品:“茶”。茶是饮料,因此它要继承饮料的特性(实现接口)。如何表达茶实现了饮料接口,使得上层调用茶时可以访问茶的接口呢?按照 Golang 的语法特性先定义一个 Tea 结构(类),先有了茶。

Golang 中实现接口无需声明,实现该接口所有方法即为(自动)实现接口,因此 Tea 类要通过实现 MeCost 两个具体方法来实现对接口的实现(这话说的)。与普通方法不同的是在 funcMe 中间要增加 (self *Tea) ,这种语法糖的作用简单说就是当前这个 Me 方法被 Tea 这个类包含(实现)了,以后可以 Tea.Me() 这么用了。

在两个方法里需要实现具体的逻辑,要输出对自身的描述和价格,那值从哪儿来,于是给 Tea 定义了两个私有字段 nameprice,以便在构造类实例时对其赋值。

Tea 中还包含了一个 Beverage,意思是通过组合的方式让类有了 Beverage 对象,但个人理解在本例中没有起到实质作用,因为 Tea 已经是 Beverage 的具体实现了,除非再创建出茶下面的红茶、绿茶继承自茶,它可用被用做标记上层结构是谁,否则在本例中只有茶一种饮品,或创建咖啡这种与茶是平级关系的构件,那这个内部的 Beverage 就没有作用了。

package component

type Moli struct {
*Tea
} func NewMoli() *Moli {
return &Moli{&Tea{name: "茉莉", price: 48}}
} func (self *Moli) Me() string{
return self.name
} func (self *Moli) Cost() int {
return self.price
}
package component

type Puer struct {
*Tea
} func NewPuer() *Puer {
return &Puer{&Tea{name: "普洱", price: 38}}
} func (self *Puer) Me() string{
return self.name
} func (self *Puer) Cost() int {
return self.price
}

上面创建两种具体的茶,茉莉和普洱。可以看到两种茶实际是一种结构,为了表达装饰模式的特性这样写更为清晰。类中只包含了一个对象,就是指向茶的指针,也就是“指向某个茶的指针”。普洱类就像个壳,名字叫普洱,壳里边只有一种(个)对象就是茶。

NewPuer的语法可以帮助我们方便的实例化一个普洱,它的返回值是指针,内在的逻辑是返回一个袋子,这种袋子叫 Puer,它里面(只)有一种(个)东西名叫普洱价格是38元的茶。茉莉逻辑与此相同。

到此,完成了抽象构件和具体构件的设计和创建,实际可以喝茶了,沏上两杯试一下

package main

func main() {

	moli := component.NewMoli()
puer := component.NewPuer() fmt.Printf("第 %v 杯是 %s 售价 %v 元\n", 1, moli.Me(), moli.Cost())
fmt.Printf("第 %v 杯是 %s 售价 %v 元\n", 2, puer.Me(),puer.Cost()) fmt.Printf("好喝吗,欢迎再来 ^_^ ")
}

上面代码会输出两杯茶的信息

第 1 杯是 茉莉 售价 48 元
第 2 杯是 普洱 售价 38 元

下面该装饰了,我要创建一些辅料,比如糖和冰,并希望能自由的放进想放的饮料中而不会和某种饮料硬性绑定,最终实现的逻辑是点一杯加糖的茉莉而不是点一杯茉莉自己再买一包糖倒里边。按照原理先定义出一个抽象装饰,它要同样实现抽象构件 Beverage 接口,并(最好)还能保持对构件的引用,因为要有“本体”才能装饰,不然对谁做装饰呢。

package decorator

import "golang-design-pattern/decorator/component"

type Condiment struct {
*component.Tea //作用?
beverage component.Beverage
name string
price int
} func (self *Condiment) Me() string {
return self.name
} func (self *Condiment) Cost() int {
return self.price
}

上面是抽象装饰 Condiment,与 Tea 一样实现了两个具体的方法,并拥有两个方法要使用到的私有字段。beverage component.Beverage 让它能够保存一个符合抽象构件接口要求的对象,即只要是满足 Beverage 接口定义的对象我就能保存着以后用。

package decorator

import "golang-design-pattern/decorator/component"

type Sugar struct {
*Condiment
} func NewSugar(beverage component.Beverage) *Sugar {
return &Sugar{ &Condiment{beverage:beverage, name:"糖", price:3 }}
} func (self *Sugar) Me() string{
return self.beverage.Me() + " 加点 " + self.name
} func (self *Sugar) Cost() int {
return self.beverage.Cost() + self.price
}
package decorator

import "golang-design-pattern/decorator/component"

type Ice struct {
*Condiment
} func NewIce(beverage component.Beverage) *Ice {
return &Ice{ &Condiment{beverage: beverage, name: "冰", price: 3 }}
} func (self *Ice) Me() string {
return "加了" + self.name + "的" + self.beverage.Me()
} func (self *Ice) Cost() int {
return self.beverage.Cost() + self.price
}

上面定义两种辅料,SugarIce。角色是具体装饰,内部保存着对 Condiment 的引用,并且它们也要实现 Beverage 接口,是为了履行装饰模式的特性,即对上层调用是透明的,调用装饰件和调用具体构件方法一样,否则就违背或污染了装饰模式的优势。

具体装饰的接口方法是关键,以 Sugar 中的 Cost 方法为例,它的实现是通过将 Sugar (里)的 Condiment (里)的 beverage 的价格叠加上 Sugar 自己的价格,作为这一杯“加糖饮料”的价格。

好了,辅料也有了,让我们来做一杯加糖的茉莉和加冰的普洱吧

package main

import (
"fmt"
"golang-design-pattern/decorator/component"
"golang-design-pattern/decorator/decorator"
) func main() { moli := component.NewMoli()
puer := component.NewPuer() fmt.Printf("第 %v 杯是 %s 售价 %v 元\n", 1, moli.Me(), moli.Cost())
fmt.Printf("第 %v 杯是 %s 售价 %v 元\n", 2, puer.Me(),puer.Cost()) fmt.Printf("下面我们给刚才那杯茉莉加点糖...\n")
sugar := decorator.NewSugar(moli)
fmt.Printf("刚刚给茉莉加了点糖,现在准备尝一下\n")
fmt.Printf("第 %v 杯是 %s 售价 %v 元\n", 3, sugar.Me(), sugar.Cost()) ice := decorator.NewIce(puer)
fmt.Printf("来一杯加冰的普洱,现在准备尝一下\n")
fmt.Printf("第 %v 杯是 %s 售价 %v 元\n", 4, ice.Me(), ice.Cost())
fmt.Printf("好喝吗,欢迎再来 ^_^ ")

首先做了两杯标准的茶,一杯48元叫做“茉莉”的Moli茶,一杯38元的叫做“普洱”的Puer茶。为了给这杯Moli加点糖,创建了 sugar,并把刚才那杯 moli “传”给了它。它拿到了 moli 后加了糖(sugar.Me方法),并把价格提高到了 48+3 元,加冰的Puer亦是如此。

感受

为什么要把本体传给装饰,而不是往本体上“添加”装饰,这个逻辑让我想不通别扭了很久,其实到现在也是别扭。越别扭越佩服创造逻辑创造模式的聪明人,因为在本体上做动作,一定会增加本体的额外工作,甚至会破坏本体原有的结构,本体会怎么想,我就是一杯茉莉茶,我为什么要实现加糖、加醋、加冰这些方法。所以换个角度看,把具体装饰想象成一个厨师,把本体(具体构件)给TA,TA来做操作就好理解一些了。

单点一杯Moli,再点一个Sugar,把它们加一起也能达成效果,这和装饰模式有什么区别?个人理解装饰模式是“官方组装”,是对于客户端而言的。客户端需要一杯加了糖的茉莉茶,这是一杯经过组合加工的整体产品交付,而不是扔给客户一杯茶一袋糖,这有本质的区别。在应用中,装饰模式往往被用来做更有趣的功能扩展,核心优点是通过“组合”而不是“继承”的方式,在不改变本体的情况下,改变结果

一定要尽量理解逻辑本身的逻辑,而不能仅从文字字面意思理解,中文英文本身意思就差距甚远,更何况中文自己又那么博大精深。

参考与引用


CSDN Shulin

博客园 灌水乐园

Golang 实现设计模式 —— 装饰模式的更多相关文章

  1. Golang 常见设计模式之单例模式

    之前我们已经看过了 Golang 常见设计模式中的装饰和选项模式,今天要看的是 Golang 设计模式里最简单的单例模式.单例模式的作用是确保无论对象被实例化多少次,全局都只有一个实例存在.根据这一特 ...

  2. 设计模式 装饰模式(Decorator)

    设计模式 装饰模式(Decorator) @author ixenos 装饰模式是什么 1.装饰模式以对客户端透明的方式对象的功能,是继承关系的一个替代方案,但装饰模式可以在不创造更多子类的情况下,对 ...

  3. c++设计模式----装饰模式

    前言 在实际开发时,你有没有碰到过这种问题:开发一个类,封装了一个对象的核心操作,而这些操作就是客户使用该类时都会去调用的操作:而有一些非核心的操作,可能会使用,也可能不会使用:现在该怎么办呢? 将这 ...

  4. Golang 常见设计模式之装饰模式

    想必只要是熟悉 Python 的同学对装饰模式一定不会陌生,这类 Python 从语法上原生支持的装饰器,大大提高了装饰模式在 Python 中的应用.尽管 Go 语言中装饰模式没有 Python 中 ...

  5. Java设计模式---装饰模式

    装饰模式又名包装(Wrapper)模式.装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 装饰模式的结构 装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任.换言之,客户 ...

  6. C++设计模式——装饰模式

    前言 在实际开发时,你有没有碰到过这种问题:开发一个类,封装了一个对象的核心操作,而这些操作就是客户使用该类时都会去调用的操作:而有一些非核心的操作,可能会使用,也可能不会使用:现在该怎么办呢? 将这 ...

  7. 设计模式—装饰模式的C++实现

    这是Bwar在2009年写的设计模式C++实现,代码均可编译可运行,一直存在自己的电脑里,曾经在团队技术分享中分享过,现搬到线上来. 1. 装饰模式简述 1.1 目的 动态地给一个对象添加一些额外的职 ...

  8. Golang 常见设计模式之选项模式

    熟悉 Python 开发的同学都知道,Python 有默认参数的存在,使得我们在实例化一个对象的时候,可以根据需要来选择性的覆盖某些默认参数,以此来决定如何实例化对象.当一个对象有多个默认参数时,这个 ...

  9. 设计模式--装饰模式Decorate(结构型)

    一.装饰模式 动态地给一个对象添加额外的职责.就增加功能来说,装饰模式相比生成子类更为灵活.有时我们希望给某个对象而不是整个类添加一些功能. 二.UML图 1.Component(概念中提到的对象接口 ...

随机推荐

  1. Day005_Linux基础之文件权限

    test.sh  举例: [oldboy@luffy001 ~]$ ls -l test.sh -rw-r--r-- 1 oldboy ops 0 Nov 14 10:42 test.sh  该文件权 ...

  2. Kubernetes --- 详细介绍和架构详解

    Kubernetes是一个跨主机集群的开源的容器调度平台,它可以自动化应用容器的部署,扩展和操作,提供以容器为中心的基础架构 目录 一. Kubernetes用途 二. Kubernetes特点 三. ...

  3. Java ArrayList源码分析(有助于理解数据结构)

    arraylist源码分析 1.数组介绍 数组是数据结构中很基本的结构,很多编程语言都内置数组,类似于数据结构中的线性表 在java中当创建数组时会在内存中划分出一块连续的内存,然后当有数据进入的时候 ...

  4. 007 Python程序语法元素分析

    目录 一.概述 二.程序的格式框架 2.1 代码高亮 2.2 缩进 2.3 注释 2.4 缩进.注释 三.命名与保留字 3.1 变量 3.2 命名 3.3 保留字 3.4 变量.命名.保留字 四.数据 ...

  5. .net core 自定义404 500页面

    常用处理方式 自己定制网站的404.500页面的方式有很多,比如修改nginx配置文件,指定请求返回码对应的页面, .netframework项目中修改webconfig文件,指定customerro ...

  6. application.properties 乱码 (已验证)

    1.打开Eclipse或MyEclipse 2.选择window-Preferences-content Types-Text-Java Properties File 3.将Java Propert ...

  7. CentOS7上OpenResty安装

    1,OpenResty安装 通过repl源安装: sudo yum-config-manager --add-repo https://openresty.org/yum/cn/centos/Open ...

  8. 松软科技课堂:SQL之NOTNULL约束

    SQL NOT NULL 约束 NOT NULL 约束强制列不接受 NULL 值. NOT NULL 约束强制字段始终包含值.这意味着,如果不向字段添加值,就无法插入新记录或者更新记录. 下面的 SQ ...

  9. py-faster-rcnn的常见错误

    在py-faster-rcnn配置运行faster-rcnn-end2end-VGG-CNN_M_1024的过程中, 执行命令:./experiments/scripts/faster_rcnn_en ...

  10. 记一次 JavaScript 浮点型数字误差引发的问题

    需求 车间的工人在生产出来产品后,需要完成初步的自检,并通过手机上报.在实际生产中,用户(工人)不方便进行数值的输入,因而表单中的一些项设计成 picker 模式以供选取数值.数值的取值范围,根据允许 ...