设计模式目前种类: GoF的23种 + “简单工厂模式” = 24种。
参考资料: yuque.com/aceld
1.面向对象设计原则
1.1.单一职责原则
类的职责单一,对外只提供一种功能,而引起类变化的原因都应该只有一个。 在面向对象编程的过程中,设计一个类,建议对外提供的功能单一,接口单一,影响一个类的范围就只限定在这一个接口上,一个类的一个接口具备这个类的功能含义,职责单一不复杂。
package main
import "fmt"
type ClothesShop struct {}
func (cs *ClothesShop) OnShop() {
fmt.Println("休闲的装扮")
}
type ClothesWork struct {}
func (cw *ClothesWork) OnWork() {
fmt.Println("工作的装扮")
}
func main() {
//工作的时候
cw := new(ClothesWork)
cw.OnWork()
//shopping的时候
cs := new(ClothesShop)
cs.OnShop()
}
1.2.开闭原则
1.2.1 平铺设计
package main
import "fmt"
//我们要写一个类,Banker银行业务员
type Banker struct {
}
//存款业务
func (this *Banker) Save() {
fmt.Println( "进行了 存款业务...")
}
//转账业务
func (this *Banker) Transfer() {
fmt.Println( "进行了 转账业务...")
}
//支付业务
func (this *Banker) Pay() {
fmt.Println( "进行了 支付业务...")
}
func main() {
banker := &Banker{}
banker.Save()
banker.Transfer()
banker.Pay()
}
这样的设计会导致,当我们去给Banker添加新的业务的时候,会直接修改原有的Banker代码,那么Banker模块的功能会越来越多,出现问题的几率也就越来越大,假如此时Banker已经有99个业务了,现在我们要添加第100个业务,可能由于一次的不小心,导致之前99个业务也一起崩溃,因为所有的业务都在一个Banker类里,他们的耦合度太高,Banker的职责也不够单一,代码的维护成本随着业务的复杂正比成倍增大。
1.2.2.开闭原则设计
- 开闭原则定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
- 简单的说就是在修改需求的时候,应该尽量通过扩展来实现变化,而不是通过修改已有代码来实现变化。
那么,如果我们拥有接口, interface这个东西,那么我们就可以抽象一层出来,制作一个抽象的Banker模块,然后提供一个抽象的方法。 分别根据这个抽象模块,去实现Banker(支付方法),Banker(转账方法)
如下:
package main
import "fmt"
//抽象的银行业务员
type AbstractBanker interface{
DoBusi() //抽象的处理业务接口
}
//存款的业务员
type SaveBanker struct {
//AbstractBanker
}
func (sb *SaveBanker) DoBusi() {
fmt.Println("进行了存款")
}
//转账的业务员
type TransferBanker struct {
//AbstractBanker
}
func (tb *TransferBanker) DoBusi() {
fmt.Println("进行了转账")
}
//支付的业务员
type PayBanker struct {
//AbstractBanker
}
func (pb *PayBanker) DoBusi() {
fmt.Println("进行了支付")
}
//实现架构层(基于抽象层进行业务封装-针对interface接口进行封装)
func BankerBusiness(banker AbstractBanker) {
//通过接口来向下调用,(多态现象)
banker.DoBusi()
}
func main() {
//进行存款
BankerBusiness(&SaveBanker{})
//进行存款
BankerBusiness(&TransferBanker{})
//进行存款
BankerBusiness(&PayBanker{})
}
1.3.里氏替换原则
虚拟机制,子类可以扩展父类的功能,但不能改变父类原有的功能。
1.4.依赖倒置原则
依赖于抽象(接口),而不依赖于具体的实现(类),针对接口编程
耦合度极高的模块关系设计
面向抽象层依赖倒转设计
package main
import "fmt"
// ===== > 抽象层 < ========
type Car interface {
Run()
}
type Driver interface {
Drive(car Car)
}
// ===== > 实现层 < ========
type BenZ struct {
//...
}
func (benz * BenZ) Run() {
fmt.Println("Benz is running...")
}
type Bmw struct {
//...
}
func (bmw * Bmw) Run() {
fmt.Println("Bmw is running...")
}
type Zhang_3 struct {
//...
}
func (zhang3 *Zhang_3) Drive(car Car) {
fmt.Println("Zhang3 drive car")
car.Run()
}
type Li_4 struct {
//...
}
func (li4 *Li_4) Drive(car Car) {
fmt.Println("li4 drive car")
car.Run()
}
// ===== > 业务逻辑层 < ========
func main() {
//张3 开 宝马
var bmw Car
bmw = &Bmw{}
var zhang3 Driver
zhang3 = &Zhang_3{}
zhang3.Drive(bmw)
//李4 开 奔驰
var benz Car
benz = &BenZ{}
var li4 Driver
li4 = &Li_4{}
li4.Drive(benz)
}
1.5.合成复用原则
如果使用继承,会导致父类的任何变换都可能影响到子类的行为。如果使用对象组合,就降低了这种依赖关系。对于继承和组合,优先使用组合。
package main
import "fmt"
type Cat struct {}
func (c *Cat) Eat() {
fmt.Println("小猫吃饭")
}
//给小猫添加一个 可以睡觉的方法 (使用继承来实现)
type CatB struct {
Cat //继承
}
func (cb *CatB) Sleep() {
fmt.Println("小猫睡觉")
}
//给小猫添加一个 可以睡觉的方法 (使用组合的方式)
type CatC struct {
C *Cat //组合
}
func (cc *CatC) Sleep(c* Cat) {
fmt.Println("小猫睡觉")
}
func main() {
//通过继承增加的功能,可以正常使用
cb := new(CatB)
cb.Eat()
cb.Sleep()
//通过组合增加的功能,可以正常使用
cc := new(CatC)
cc.C = new(Cat)
cc.Sleep(cc.C)//传入一个Cat对象
cc.C.Eat()
}
1.6.迪米特法则
2.创建型模式
2.1.简单工厂模式
业务逻辑层 ---> 工厂模块 ---> 基础类模块
package main
import "fmt"
// ======= 抽象层 =========
//水果类(抽象接口)
type Fruit interface {
Show() //接口的某方法
}
// ======= 基础类模块 =========
type Apple struct {
Fruit //为了易于理解显示继承(此行可以省略)
}
func (apple *Apple) Show() {
fmt.Println("我是苹果")
}
type Banana struct {
Fruit
}
func (banana *Banana) Show() {
fmt.Println("我是香蕉")
}
type Pear struct {
Fruit
}
func (pear *Pear) Show() {
fmt.Println("我是梨")
}
// ========= 工厂模块 =========
//一个工厂, 有一个生产水果的机器,返回一个Fruit类型的接口
type Factory struct {}
func (fac *Factory) CreateFruit(kind string) Fruit {
var fruit Fruit
if kind == "apple" {
fruit = new(Apple)
} else if kind == "banana" {
fruit = new(Banana)
} else if kind == "pear" {
fruit = new(Pear)
}
return fruit
}
// ==========业务逻辑层==============
func main() {
factory := new(Factory)
apple := factory.CreateFruit("apple")
apple.Show()
banana := factory.CreateFruit("banana")
banana.Show()
pear := factory.CreateFruit("pear")
pear.Show()
}
优点:
- 实现了对象创建和使用的分离。
- 不需要记住具体类名,记住参数即可,减少使用者记忆量。
缺点:
- 对工厂类职责过重,一旦不能工作,系统受到影响。
- 增加系统中类的个数,复杂度和理解度增加。
- 违反“开闭原则”,添加新产品需要修改工厂逻辑,工厂越来越复杂。
适用场景:
- 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
- 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。
2.2.工厂方法模式
简单工厂模式 + “开闭原则” = 工厂方法模式
package main
import "fmt"
// ======= 抽象层 =========
//水果类(抽象接口)
type Fruit interface {
Show() //接口的某方法
}
//工厂类(抽象接口)
type AbstractFactory interface {
CreateFruit() Fruit //生产水果类(抽象)的生产器方法
}
// ======= 基础类模块 =========
type Apple struct {
Fruit //为了易于理解显示继承(此行可以省略)
}
func (apple *Apple) Show() {
fmt.Println("我是苹果")
}
type Banana struct {
Fruit
}
func (banana *Banana) Show() {
fmt.Println("我是香蕉")
}
type Pear struct {
Fruit
}
func (pear *Pear) Show() {
fmt.Println("我是梨")
}
// ========= 工厂模块 =========
//具体的苹果工厂
type AppleFactory struct {
AbstractFactory
}
func (fac *AppleFactory) CreateFruit() Fruit {
var fruit Fruit
//生产一个具体的苹果
fruit = new(Apple)
return fruit
}
//具体的香蕉工厂
type BananaFactory struct {
AbstractFactory
}
func (fac *BananaFactory) CreateFruit() Fruit {
var fruit Fruit
//生产一个具体的香蕉
fruit = new(Banana)
return fruit
}
//具体的梨工厂
type PearFactory struct {
AbstractFactory
}
func (fac *PearFactory) CreateFruit() Fruit {
var fruit Fruit
//生产一个具体的梨
fruit = new(Pear)
return fruit
}
//======= 业务逻辑层 =======
func main() {
//需求1:需要一个具体的苹果对象
//1-先要一个具体的苹果工厂
var appleFac AbstractFactory
appleFac = new(AppleFactory)
//2-生产相对应的具体水果
var apple Fruit
apple = appleFac.CreateFruit()
apple.Show() //多态
//需求2:需要一个具体的香蕉对象
//1-先要一个具体的香蕉工厂
var bananaFac AbstractFactory
bananaFac = new(BananaFactory)
//2-生产相对应的具体水果
var banana Fruit
banana = bananaFac.CreateFruit()
banana.Show()
//需求3:需要一个具体的梨对象
//1-先要一个具体的梨工厂
var pearFac AbstractFactory
pearFac = new(PearFactory)
//2-生产相对应的具体水果
var pear Fruit
pear = pearFac.CreateFruit()
pear.Show()
}
优点:
- 不需要记住具体类名,甚至连具体参数都不用记忆。
- 实现了对象创建和使用的分离。
- 系统的可扩展性也就变得非常好,无需修改接口和原类。 4.对于新产品的创建,符合开闭原则。
缺点:
- 增加系统中类的个数,复杂度和理解度增加。
- 增加了系统的抽象性和理解难度。
适用场景:
- 客户端不知道它所需要的对象的类。
- 抽象工厂类通过其子类来指定创建哪个对象。
2.3.抽象工厂模式
产品族与产品等级结构
package main
import "fmt"
// ======= 抽象层 =========
type AbstractApple interface {
ShowApple()
}
type AbstractBanana interface {
ShowBanana()
}
type AbstractPear interface {
ShowPear()
}
//抽象工厂
type AbstractFactory interface {
CreateApple() AbstractApple
CreateBanana() AbstractBanana
CreatePear() AbstractPear
}
// ======== 实现层 =========
/* 中国产品族 */
type ChinaApple struct {}
func (ca *ChinaApple) ShowApple() {
fmt.Println("中国苹果")
}
type ChinaBanana struct {}
func (cb *ChinaBanana) ShowBanana() {
fmt.Println("中国香蕉")
}
type ChinaPear struct {}
func (cp *ChinaPear) ShowPear() {
fmt.Println("中国梨")
}
type ChinaFactory struct {}
func (cf *ChinaFactory) CreateApple() AbstractApple {
var apple AbstractApple
apple = new(ChinaApple)
return apple
}
func (cf *ChinaFactory) CreateBanana() AbstractBanana {
var banana AbstractBanana
banana = new(ChinaBanana)
return banana
}
func (cf *ChinaFactory) CreatePear() AbstractPear {
var pear AbstractPear
pear = new(ChinaPear)
return pear
}
/* 日本产品族 */
type JapanApple struct {}
func (ja *JapanApple) ShowApple() {
fmt.Println("日本苹果")
}
type JapanBanana struct {}
func (jb *JapanBanana) ShowBanana() {
fmt.Println("日本香蕉")
}
type JapanPear struct {}
func (cp *JapanPear) ShowPear() {
fmt.Println("日本梨")
}
type JapanFactory struct {}
func (jf *JapanFactory) CreateApple() AbstractApple {
var apple AbstractApple
apple = new(JapanApple)
return apple
}
func (jf *JapanFactory) CreateBanana() AbstractBanana {
var banana AbstractBanana
banana = new(JapanBanana)
return banana
}
func (cf *JapanFactory) CreatePear() AbstractPear {
var pear AbstractPear
pear = new(JapanPear)
return pear
}
/* 美国产品族 */
type AmericanApple struct {}
func (aa *AmericanApple) ShowApple() {
fmt.Println("美国苹果")
}
type AmericanBanana struct {}
func (ab *AmericanBanana) ShowBanana() {
fmt.Println("美国香蕉")
}
type AmericanPear struct {}
func (ap *AmericanPear) ShowPear() {
fmt.Println("美国梨")
}
type AmericanFactory struct {}
func (af *AmericanFactory) CreateApple() AbstractApple {
var apple AbstractApple
apple = new(AmericanApple)
return apple
}
func (af *AmericanFactory) CreateBanana() AbstractBanana {
var banana AbstractBanana
banana = new(AmericanBanana)
return banana
}
func (af *AmericanFactory) CreatePear() AbstractPear {
var pear AbstractPear
pear = new(AmericanPear)
return pear
}
// ======== 业务逻辑层 =======
func main() {
//需求1: 需要美国的苹果、香蕉、梨 等对象
//1-创建一个美国工厂
var aFac AbstractFactory
aFac = new(AmericanFactory)
//2-生产美国苹果
var aApple AbstractApple
aApple = aFac.CreateApple()
aApple.ShowApple()
//3-生产美国香蕉
var aBanana AbstractBanana
aBanana = aFac.CreateBanana()
aBanana.ShowBanana()
//4-生产美国梨
var aPear AbstractPear
aPear = aFac.CreatePear()
aPear.ShowPear()
//需求2: 需要中国的苹果、香蕉
//1-创建一个中国工厂
cFac := new(ChinaFactory)
//2-生产中国苹果
cApple := cFac.CreateApple()
cApple.ShowApple()
//3-生产中国香蕉
cBanana := cFac.CreateBanana()
cBanana.ShowBanana()
}
优点:
- 拥有工厂方法模式的优点
- 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。 3 增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:
- 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。
适用场景:
- 系统中有多于一个的产品族。而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
- 产品等级结构稳定。设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。
2.4.单例模式
保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法
2.4.1.“饿汉式”单例模式
package main
import "fmt"
/*
三个要点:
一是某个类只能有一个实例;
二是它必须自行创建这个实例;
三是它必须自行向整个系统提供这个实例。
*/
/*
保证一个类永远只能有一个对象
*/
//1、保证这个类非公有化,外界不能通过这个类直接创建一个对象
// 类名称首字母要小写
type singelton struct {}
//2、但是还要有一个指针可以指向这个唯一对象,但是这个指针永远不能改变方向
// Golang中没有常指针概念,所以只能通过将这个指针私有化不让外部模块访问
var instance *singelton = new(singelton)
//3、如果全部为私有化,那么外部模块将永远无法访问到这个类和对象,
// 控制对象的写操作,开放读操作
func GetInstance() *singelton {
return instance //返回singelton的单例的地址
}
func (s *singelton) SomeThing() {
fmt.Println("单例对象的某方法")
}
func main() {
s := GetInstance() //获取单例对象的地址
s.SomeThing() //指针调用单例对象的方法
}
饿汉式的好处是,不会出现线程并发创建,导致多个单例的出现,但是缺点是如果这个单例对象在业务逻辑没有被使用,也会客观的创建一块内存对象。
2.4.2.“懒汉式”单例模式
package main
import "fmt"
type singelton struct {}
var instance *singelton
func GetInstance() *singelton {
//只有首次GetInstance()方法被调用,才会生成这个单例的实例
if instance == nil {
instance = new(singelton)
return instance
}
//接下来的GetInstance直接返回已经申请的实例即可
return instance
}
func (s *singelton) SomeThing() {
fmt.Println("单例对象的某方法")
}
func main() {
s := GetInstance()
s.SomeThing()
}
2.4.3.线程安全的单例模式
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var initialized uint32 //标记是否被初始化过
var lock sync.Mutex //互斥锁
type singelton struct {} //单例对象
var instance *singelton //单例对象的指针
func GetInstance() *singelton {
//如果initialized==1(已设置),直接返回,不加锁
if atomic.LoadUint32(&initialized) == 1 {
return instance
}
//如果没有,则加锁申请
lock.Lock()
defer lock.Unlock()
//如果没有初始化,则初始化
if initialized == 0 {
instance = new(singelton)
//设置initialized==1
atomic.StoreUint32(&initialized, 1)
}
return instance
}
func (s *singelton) SomeThing() {
fmt.Println("单例对象的某方法")
}
func main() {
s := GetInstance()
s.SomeThing()
}
3.结构型模式
3.1.代理模式
package main
import "fmt"
type Goods struct {
Kind string //商品种类
Fact bool //商品真伪
}
// =========== 抽象层 ===========
//抽象的购物主题Subject
type Shopping interface {
Buy(goods *Goods) //某任务
}
// =========== 实现层 ===========
//具体的购物主题, 实现了shopping, 去韩国购物
type KoreaShopping struct {}
func (ks *KoreaShopping) Buy(goods *Goods) {
fmt.Println("去韩国进行了购物, 买了 ", goods.Kind)
}
//具体的购物主题, 实现了shopping, 去美国购物
type AmericanShopping struct {}
func (as *AmericanShopping) Buy(goods *Goods) {
fmt.Println("去美国进行了购物, 买了 ", goods.Kind)
}
//具体的购物主题, 实现了shopping, 去非洲购物
type AfrikaShopping struct {}
func (as *AfrikaShopping) Buy(goods *Goods) {
fmt.Println("去非洲进行了购物, 买了 ", goods.Kind)
}
//海外的代理
type OverseasProxy struct {
shopping Shopping //代理某个主题(抽象类型)
}
func (op *OverseasProxy) Buy(goods *Goods) {
// 1. 先验货
if (op.distinguish(goods) == true) {
//2. 进行购买
op.shopping.Buy(goods) //调用原被代理的具体主题任务
//3 海关安检
op.check(goods)
}
}
//创建一个代理,并且配置关联被代理的主题
func NewProxy(shopping Shopping) Shopping {
return &OverseasProxy{shopping}
}
//验货流程
func (op *OverseasProxy) distinguish(goods *Goods) bool {
fmt.Println("对[", goods.Kind,"]进行了辨别真伪.")
if (goods.Fact == false) {
fmt.Println("发现假货",goods.Kind,", 不应该购买。")
}
return goods.Fact
}
//安检流程
func (op *OverseasProxy) check(goods *Goods) {
fmt.Println("对[",goods.Kind,"] 进行了海关检查, 成功的带回祖国")
}
func main() {
g1 := Goods{
Kind: "韩国面膜",
Fact: true,
}
g2 := Goods{
Kind: "CET4证书",
Fact: false,
}
var overseasProxy Shopping //海外代理
overseasProxy = NewProxy(shopping) //创建一个购物主题的代理
overseasProxy.Buy(&g1)
overseasProxy.Buy(&g2)
}
一个更好理解的代码:
package main
import "fmt"
//抽象主题
type BeautyWoman interface {
//对男人抛媚眼
MakeEyesWithMan()
//和男人浪漫的约会
HappyWithMan()
}
//具体主题
type PanJinLian struct {}
//对男人抛媚眼
func (p *PanJinLian) MakeEyesWithMan() {
fmt.Println("潘金莲对本官抛了个媚眼")
}
//和男人浪漫的约会
func (p *PanJinLian) HappyWithMan() {
fmt.Println("潘金莲和本官共度了浪漫的约会。")
}
//代理中介人, 王婆
type WangPo struct {
woman BeautyWoman
}
func NewProxy(woman BeautyWoman) BeautyWoman {
return &WangPo{woman}
}
//对男人抛媚眼
func (p *WangPo) MakeEyesWithMan() {
p.woman.MakeEyesWithMan()
}
//和男人浪漫的约会
func (p *WangPo) HappyWithMan() {
p.woman.HappyWithMan()
}
//西门大官人
func main() {
//大官人想找金莲,让王婆来安排
wangpo := NewProxy(new(PanJinLian))
//王婆命令潘金莲抛媚眼
wangpo.MakeEyesWithMan()
//王婆命令潘金莲和西门庆约会
wangpo.HappyWithMan()
}
优点:
- 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
- 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
缺点:
- 代理实现较为复杂。
3.2.装饰模式
在装饰器模式中,“裸机”、“有贴膜的手机”、“有手机壳的手机”、“有手机壳&贴膜的手机”都是一个构件。
“贴膜装饰器”、“手机壳装饰器”是装饰器也是一个构件。
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()
}
装饰模式 和 代理模式 有何不同?
装饰模式的优缺点: 优点: (1) 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。 (2) 可以通过一种动态的方式来扩展一个对象的功能,从而实现不同的行为。 (3) 可以对一个对象进行多次装饰。 (4) 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。 缺点: (1) 使用装饰模式进行系统设计时将产生很多小对象,大量小对象的产生势必会占用更多的系统资源,影响程序的性能。 (2) 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。
适用场景: (1) 动态、透明的方式给单个对象添加职责。 (2) 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。 装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。
3.3.适配器模式
package main
import "fmt"
//适配的目标
type V5 interface {
Use5V()
}
//业务类,依赖V5接口
type Phone struct {
v V5
}
func NewPhone(v V5) *Phone {
return &Phone{v}
}
func (p *Phone) Charge() {
fmt.Println("Phone进行充电...")
p.v.Use5V()
}
//被适配的角色,适配者
type V220 struct {}
func (v *V220) Use220V() {
fmt.Println("使用220V的电压")
}
//电源适配器
type Adapter struct {
v220 *V220
}
func (a *Adapter) Use5V() {
fmt.Println("使用适配器进行充电")
//调用适配者的方法
a.v220.Use220V()
}
func NewAdapter(v220 *V220) *Adapter {
return &Adapter{v220}
}
// ------- 业务逻辑层 -------
func main() {
iphone := NewPhone(NewAdapter(new(V220)))
iphone.Charge()
}
优点:
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
- 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
- 灵活性和扩展性都非常好,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。 缺点:
- 适配器中置换适配者类的某些方法比较麻烦。
适应场景
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
- 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
3.4.外观模式
package main
import "fmt"
//电视机
type TV struct {}
func (t *TV) On() {
fmt.Println("打开 电视机")
}
func (t *TV) Off() {
fmt.Println("关闭 电视机")
}
//音箱
type VoiceBox struct {}
func (v *VoiceBox) On() {
fmt.Println("打开 音箱")
}
func (v *VoiceBox) Off() {
fmt.Println("关闭 音箱")
}
//灯光
type Light struct {}
func (l *Light) On() {
fmt.Println("打开 灯光")
}
func (l *Light) Off() {
fmt.Println("关闭 灯光")
}
//游戏机
type Xbox struct {}
func (x *Xbox) On() {
fmt.Println("打开 游戏机")
}
func (x *Xbox) Off() {
fmt.Println("关闭 游戏机")
}
//麦克风
type MicroPhone struct {}
func (m *MicroPhone) On() {
fmt.Println("打开 麦克风")
}
func (m *MicroPhone) Off() {
fmt.Println("关闭 麦克风")
}
//投影仪
type Projector struct {}
func (p *Projector) On() {
fmt.Println("打开 投影仪")
}
func (p *Projector) Off() {
fmt.Println("关闭 投影仪")
}
//家庭影院(外观)
type HomePlayerFacade struct {
tv TV
vb VoiceBox
light Light
xbox Xbox
mp MicroPhone
pro Projector
}
//KTV模式
func (hp *HomePlayerFacade) DoKTV() {
fmt.Println("家庭影院进入KTV模式")
hp.tv.On()
hp.pro.On()
hp.mp.On()
hp.light.Off()
hp.vb.On()
}
//游戏模式
func (hp *HomePlayerFacade) DoGame() {
fmt.Println("家庭影院进入Game模式")
hp.tv.On()
hp.light.On()
hp.xbox.On()
}
func main() {
homePlayer := new(HomePlayerFacade)
homePlayer.DoKTV()
fmt.Println("------------")
homePlayer.DoGame()
}
优点:
- 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
- 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
- 一个子系统的修改对其他子系统没有任何影响。
缺点:
- 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活 性。
- 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。
适用场景
- 复杂系统需要简单入口使用。
- 客户端程序与多个子系统之间存在很大的依赖性。
- 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。
4.行为型模式
4.1.模板方法模式
package main
import "fmt"
//抽象类,制作饮料,包裹一个模板的全部实现步骤
type Beverage interface {
BoilWater() //煮开水
Brew() //冲泡
PourInCup() //倒入杯中
AddThings() //添加酌料
WantAddThings() bool //是否加入酌料Hook
}
//封装一套流程模板,让具体的制作流程继承且实现
type template struct {
b Beverage
}
//封装的固定模板
func (t *template) MakeBeverage() {
if t == nil {
return
}
t.b.BoilWater() //煮开水
t.b.Brew() //冲泡
t.b.PourInCup() //倒入杯中
//子类可以重写该方法来决定是否执行下面动作
if t.b.WantAddThings() == true {
t.b.AddThings()
}
}
//具体的模板子类 制作咖啡
type MakeCaffee struct {
template //继承模板
}
func NewMakeCaffee() *MakeCaffee {
makeCaffe := new(MakeCaffee)
//b为Beverage,是MakeCaffee的接口,接口b指向具体的子类对象
makeCaffe.b = makeCaffe //多态
return makeCaffe
}
func (mc *MakeCaffee) BoilWater() {
fmt.Println("将水煮到100摄氏度")
}
func (mc *MakeCaffee) Brew() {
fmt.Println("用水冲咖啡豆")
}
func (mc *MakeCaffee) PourInCup() {
fmt.Println("将充好的咖啡倒入陶瓷杯中")
}
func (mc *MakeCaffee) AddThings() {
fmt.Println("添加牛奶和糖")
}
func (mc *MakeCaffee) WantAddThings() bool {
return true //启动Hook条件
}
//具体的模板子类 制作茶
type MakeTea struct {
template //继承模板
}
func NewMakeTea() *MakeTea {
makeTea := new(MakeTea)
//b 为Beverage,是MakeTea,这里需要给接口赋值,指向具体的子类对象
makeTea.b = makeTea
return makeTea
}
func (mt *MakeTea) BoilWater() {
fmt.Println("将水煮到80摄氏度")
}
func (mt *MakeTea) Brew() {
fmt.Println("用水冲茶叶")
}
func (mt *MakeTea) PourInCup() {
fmt.Println("将冲好的茶倒入茶壶中")
}
func (mt *MakeTea) AddThings() {
fmt.Println("添加柠檬")
}
func (mt *MakeTea) WantAddThings() bool {
return false //关闭Hook条件
}
func main() {
//1. 制作一杯咖啡
makeCoffee := NewMakeCaffee()
//2.调用固定模板方法
makeCoffee.MakeBeverage()
fmt.Println("------------")
//2. 制作茶
makeTea := NewMakeTea()
makeTea.MakeBeverage()
}
优点:
- 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。
- 模板方法模式是一种代码复用技术,它在类库设计中尤为重要,它提取了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行为,它鼓励我们恰当使用继承来实现代码复用。
- 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行。
- 在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则。
缺点:
- 需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象。
适用场景
- 具有统一的操作步骤或操作过程;
- 具有不同的操作细节;
- 存在多个具有同样操作步骤的应用场景,但某些具体的操作细节却各不相同; 在抽象类中统一操作步骤,并规定好接口;让子类实现接口。这样可以把各个具体的子类和操作步骤解耦合。
4.2.命令模式
病人可以先填写病单,并不会直接和医生进行交互和耦合,医生只对接订单的接口;随着病单种类的繁多,病人(业务)依然需要了解各个订单的业务,所以应该将病单抽象出来变成一个interface,然后再新增一个管理病单集合的模块,以这个按理来讲就是一名医护人员。
package main
import "fmt"
//医生-命令接收者
type Doctor struct {}
func (d *Doctor) treatEye() {
fmt.Println("医生治疗眼睛")
}
func (d *Doctor) treatNose() {
fmt.Println("医生治疗鼻子")
}
//抽象的命令
type Command interface {
Treat()
}
//治疗眼睛的病单
type CommandTreatEye struct {
doctor *Doctor
}
func (cmd *CommandTreatEye) Treat() {
cmd.doctor.treatEye()
}
//治疗鼻子的病单
type CommandTreatNose struct {
doctor *Doctor
}
func (cmd *CommandTreatNose) Treat() {
cmd.doctor.treatNose()
}
//护士-调用命令者
type Nurse struct {
CmdList []Command //收集的命令集合
}
//发送病单,发送命令的方法
func (n *Nurse) Notify() {
if n.CmdList == nil {
return
}
for _, cmd := range n.CmdList {
cmd.Treat() //执行病单绑定的命令
}
}
//病人
func main() {
//依赖病单,通过填写病单,让医生看病
doctor := new(Doctor)
//治疗眼睛的病单
cmdEye := CommandTreatEye{doctor}
//治疗鼻子的病单
cmdNose := CommandTreatNose{doctor}
//护士
nurse := new(Nurse)
//收集管理病单
nurse.CmdList = append(nurse.CmdList, &cmdEye)
nurse.CmdList = append(nurse.CmdList, &cmdNose)
//执行病单指令
nurse.Notify()
}
优点:
- 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
- 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。
- 可以比较容易地设计一个命令队列或宏命令(组合命令)。
缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。
适用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。
- 系统需要将一组操作组合在一起形成宏命令。
4.3.策略模式
package main
import "fmt"
//武器策略(抽象的策略)
type WeaponStrategy interface {
UseWeapon() //使用武器
}
//具体的策略
type Ak47 struct {}
func (ak *Ak47) UseWeapon() {
fmt.Println("使用Ak47 去战斗")
}
//具体的策略
type Knife struct {}
func (k *Knife) UseWeapon() {
fmt.Println("使用匕首 去战斗")
}
//环境类
type Hero struct {
strategy WeaponStrategy //拥有一个抽象的策略
}
//设置一个策略
func (h *Hero) SetWeaponStrategy(s WeaponStrategy) {
h.strategy = s
}
func (h *Hero) Fight() {
h.strategy.UseWeapon() //调用策略
}
func main() {
hero := Hero{}
//更换策略1
hero.SetWeaponStrategy(new(Ak47))
hero.Fight()
hero.SetWeaponStrategy(new(Knife))
hero.Fight()
}
优点:
- 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
- 使用策略模式可以避免多重条件选择语句。多重条件选择语句不易维护,它把采取哪一种算法或行为的逻辑与算法或行为本身的实现逻辑混合在一起,将它们全部硬编码(Hard Coding)在一个庞大的多重条件选择语句中,比直接继承环境类的办法还要原始和落后。
- 策略模式提供了一种算法的复用机制。由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
- 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
适用场景
- 准备一组算法,并将每一个算法封装起来,使得它们可以互换。
4.4.观察者模式
package main
import "fmt"
//--------- 抽象层 --------
//抽象的观察者
type Listener interface {
OnTeacherComming() //观察者得到通知后要触发的动作
}
//抽象的通知者
type Notifier interface {
AddListener(listener Listener)
RemoveListener(listener Listener)
Notify()
}
//--------- 实现层 --------
//观察者学生
type StuZhang3 struct {
Badthing string
}
func (s *StuZhang3) OnTeacherComming() {
fmt.Println("张3 停止 ", s.Badthing)
}
func (s *StuZhang3) DoBadthing() {
fmt.Println("张3 正在", s.Badthing)
}
type StuZhao4 struct {
Badthing string
}
func (s *StuZhao4) OnTeacherComming() {
fmt.Println("赵4 停止 ", s.Badthing)
}
func (s *StuZhao4) DoBadthing() {
fmt.Println("赵4 正在", s.Badthing)
}
type StuWang5 struct {
Badthing string
}
func (s *StuWang5) OnTeacherComming() {
fmt.Println("王5 停止 ", s.Badthing)
}
func (s *StuWang5) DoBadthing() {
fmt.Println("王5 正在", s.Badthing)
}
//通知者班长
type ClassMonitor struct {
listenerList []Listener //需要通知的全部观察者集合
}
func (m *ClassMonitor) AddListener(listener Listener) {
m.listenerList = append(m.listenerList, listener)
}
func (m *ClassMonitor) RemoveListener(listener Listener) {
for index, l := range m.listenerList {
//找到要删除的元素位置
if listener == l {
//将删除的点前后的元素链接起来
m.listenerList = append(m.listenerList[:index], m.listenerList[index+1:]...)
break
}
}
}
func (m* ClassMonitor) Notify() {
for _, listener := range m.listenerList {
//依次调用全部观察的具体动作
listener.OnTeacherComming() //多态
}
}
func main() {
s1 := &StuZhang3{
Badthing: "抄作业",
}
s2 := &StuZhao4{
Badthing: "玩王者荣耀",
}
s3 := &StuWang5{
Badthing: "看赵四玩王者荣耀",
}
classMonitor := new(ClassMonitor)
fmt.Println("上课了,但是老师没有来,学生们都在忙自己的事...")
s1.DoBadthing()
s2.DoBadthing()
s3.DoBadthing()
classMonitor.AddListener(s1)
classMonitor.AddListener(s2)
classMonitor.AddListener(s3)
fmt.Println("这时候老师来了,班长给学什么使了一个眼神...")
classMonitor.Notify()
}
优点:
- 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
- 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
- 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
- 观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。
缺点:
- 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
适用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。