第14节 装饰器模式


❤️💕💕Java和Golang的设计模式,设计模式介绍、创建者模式、结构型模式、行为型模式。Myblog:http://nsddd.topopen in new window


[TOC]

why

装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。

image-20230514204330417

图片来自 https://www.yuque.com/aceld/lfhu8y/nzlggd

以上图为例,一开始有个手机(裸机Phone类),如果需要不断的为这个Phone增添某个功能从而变成一个新功能的Phone,就需要一个装饰器的类,来动态的给一个类额外添加一个指定的功能,而生成另一个类,但原先的类又没有改变,不影响原有系统的稳定。

在装饰器模式中,“裸机”、“有贴膜的手机”、“有手机壳的手机”、“有手机壳&贴膜的手机”都是一个构件。 “贴膜装饰器”、“手机壳装饰器”是装饰器也是一个构件。

装饰模式中的角色和职责

Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。

ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。 其标准的类图如下所示:

image-20230514204708448

图片来自 https://www.yuque.com/aceld/lfhu8y/nzlggd

装饰器模式是一种常用的设计模式,它允许我们动态地为一个对象添加新的功能。在Go语言中,我们可以使用函数和闭包来实现装饰器模式。

在这种模式中,我们定义一个函数,它接收一个函数作为参数,并返回一个新的函数。新的函数可以在执行原始函数之前或之后执行一些额外的逻辑,或者替换原始函数的返回值。

下面是一个简单的例子,通过装饰器模式来记录函数的执行时间:

func timeTrack(start time.Time, name string) {
    elapsed := time.Since(start)
    log.Printf("%s took %s", name, elapsed)
}

func runSomething() {
    defer timeTrack(time.Now(), "runSomething")
    // 执行一些操作
}

在上面的代码中,timeTrack 函数是一个装饰器函数,它接收函数的开始时间和名称作为参数,并在函数执行结束时打印出该函数的执行时间。runSomething 函数通过 defer 关键字来调用 timeTrack 函数,从而实现了装饰器模式。

除了记录执行时间,装饰器模式还可以用于实现其他功能,例如重试机制、缓存等。在Go语言中,使用装饰器模式可以让我们的代码更加灵活和易于维护。

总结一下,装饰器模式是一种常用的设计模式,它允许我们动态地为一个对象添加新的功能。在Go语言中,我们可以使用函数和闭包来实现装饰器模式,从而让我们的代码更加灵活和易于维护。

代码的实现逻辑

接下来按照上述手机案例,结合装饰器的设计模式特点,得到对应案例的类图,如下:

image-20230514205225859

💡简单的一个案例如下:

package main

import "fmt"

// ---------- 抽象层 ----------
//抽象的构件
type Phone interface {
	Show() //构件的功能
}

//装饰器基础类(该类本应该为interface,但是Golang interface语法不可以有成员属性)
type Decorator struct {
	phone Phone
}

func (d *Decorator) Show() {}


// ----------- 实现层 -----------
// 具体的构件
type HuaWei struct {}

func (hw *HuaWei) Show() {
	fmt.Println("秀出了HuaWei手机")
}

type XiaoMi struct{}

func (xm *XiaoMi) Show() {
	fmt.Println("秀出了XiaoMi手机")
}

// 具体的装饰器类
type MoDecorator struct {
	Decorator   //继承基础装饰器类(主要继承Phone成员属性)
}

func (md *MoDecorator) Show() {
	md.phone.Show() //调用被装饰构件的原方法
	fmt.Println("贴膜的手机") //装饰额外的方法
}

func NewMoDecorator(phone Phone) Phone {
	return &MoDecorator{Decorator{phone}}
}

type KeDecorator struct {
	Decorator   //继承基础装饰器类(主要继承Phone成员属性)
}

func (kd *KeDecorator) Show() {
	kd.phone.Show()
	fmt.Println("手机壳的手机") //装饰额外的方法
}

func NewKeDecorator(phone Phone) Phone {
	return &KeDecorator{Decorator{phone}}
}


// ------------ 业务逻辑层 ---------
func main() {
	var huawei Phone
	huawei = new(HuaWei)
	huawei.Show()	 //调用原构件方法

	fmt.Println("---------")
	//用贴膜装饰器装饰,得到新功能构件
	var moHuawei Phone
	moHuawei = NewMoDecorator(huawei) //通过HueWei ---> MoHuaWei
	moHuawei.Show() //调用装饰后新构件的方法

	fmt.Println("---------")
	var keHuawei Phone
	keHuawei = NewKeDecorator(huawei) //通过HueWei ---> KeHuaWei
	keHuawei.Show()

	fmt.Println("---------")
	var keMoHuaWei Phone
	keMoHuaWei = NewMoDecorator(keHuawei) //通过KeHuaWei ---> KeMoHuaWei
	keMoHuaWei.Show()
}

🚀 编译结果如下:

秀出了HuaWei手机
---------
秀出了HuaWei手机
贴膜的手机
---------
秀出了HuaWei手机
手机壳的手机
---------
秀出了HuaWei手机
手机壳的手机
贴膜的手机

优缺点

优点

  • 装饰器模式通过将功能分离到不同的装饰器中,使得代码更加灵活和易于维护。
  • 可以在不修改原始对象的情况下动态地为其添加新的功能。
  • 可以通过组合不同的装饰器来实现不同的功能组合。

缺点

  • 由于装饰器模式增加了许多小类和对象,因此会增加系统的复杂性。
  • 可能会导致过多的装饰器嵌套,使得代码难以理解和维护。
  • 如果装饰器和原始对象的接口不一致,可能需要额外的适配器代码来实现兼容性。

END 链接