Go语言设计模式

一.设计模式概述

1.设计模式分类

1.创建型(Creational)模式

  • 如何创建对象

2.结构性(Structural)模式

  • 如何实现类或对象的组合

3.行为型(Beahvioral)模式

  • 类或对象怎样交互以及怎样分配职责

2.设计模式的作用

1.有助于更加深入地理解面向对象思想

  1. 如何将代码分散在几个不同的类中?
  2. 为什么要有“接口”?
  3. 何谓针对抽象编程?
  4. 何时不应该使用继承?
  5. 如果不修改源代码增加新功能?
  6. 更好地阅读和理解现有类库与其他系统中的源代码。

2.学习设计模式会让你早点脱离面向对象编程的“菜鸟期”

3.设计模式总览

模式分类 模式名称 作用
  创建型模式
Creational Pattern
     (6)
单例模式 ★★★★☆ 是保证一个类仅有一个实例,并提供一个访问它的全局访问点。
简单工厂模式 ★★★☆☆ 通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
工厂方法模式 ★★★★★ 定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类中。
抽象工厂模式 ★★★★★ 提供一个创建一系列相关或者相互依赖的接口,而无需指定它们具体的类。
原型模式 ★★★☆☆ 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
建造者模式 ★★☆☆☆ 将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
  结构型模式
Structural Pattern
     (7)
适配器模式 ★★★★☆ 将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
桥接模式 ★★★☆☆ 将抽象部分与实际部分分离,使它们都可以独立的变化。
组合模式 ★★☆☆☆ 将对象组合成树形结构以表示“部分--整体”的层次结构。使得用户对单个对象和组合对象的使用具有一致性。
装饰模式 ★★★☆☆ 动态的给一个对象添加一些额外的职责。就增加功能来说,此模式比生成子类更为灵活。
外观模式 ★★★★★ 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
享元模式 ★☆☆☆☆ 以共享的方式高效的支持大量的细粒度的对象。
代理模式 ★★★★☆ 为其他对象提供一种代理以控制对这个对象的访问。
  行为型模式
Behavioral Pattern
     (11)
职责链模式 ★★☆☆☆ 在该模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。
命令模式 ★★★★☆ 将一个请求封装为一个对象,从而使你可用不同的请求对客户端进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
解释器模式 ★☆☆☆☆ 如何为简单的语言定义一个语法,如何在该语言中表示一个句子,以及如何解释这些句子。
迭代器模式 ★☆☆☆☆ 提供了一种方法顺序来访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
中介者模式 ★★☆☆☆ 定义一个中介对象来封装系列对象之间的交互。终结者使各个对象不需要显示的相互调用 ,从而使其耦合性松散,而且可以独立的改变他们之间的交互。
备忘录模式 ★★☆☆☆ 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
状态模式 ★★☆☆☆ 对象的行为,依赖于它所处的状态。
策略模式 ★★★★☆ 准备一组算法,并将每一个算法封装起来,使得它们可以互换。
模板方法模式 ★★★☆☆ 得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
访问者模式 ★☆☆☆☆ 表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
观察者模式 ★★★★★ 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

二.面向对象设计原则

1.设计原则

        对于面向对象软件系统的设计而言,在支持可维护性的同时,提高系统的可复用性是一个至关重要的问题,如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一。在面向对象设计中,可维护性的复用是以设计原则为基础的。每一个原则都蕴含一些面向对象设计的思想,可以从不同的角度提升一个软件结构的设计水平。

        面向对象设计原则为支持可维护性复用而诞生,这些原则蕴含在很多设计模式中,它们是从许多设计方案中总结出的指导性原则。面向对象设计原则也是我们用于评价一个设计模式的使用效果的重要指标之一。

2.对象设计原则

1.设计原则表

名称 定义
单一职责原则
(Single Responsibility Principle, SRP)
★★★★☆
类的职责单一,对外只提供一种功能,而引起类变化的原因都应该只有一个。
开闭原则
(Open-Closed Principle, OCP)
★★★★★
类的改动是通过增加代码进行的,而不是修改源代码。
里氏代换原则
(Liskov Substitution Principle, LSP
★★★★★
任何抽象类(interface接口)出现的地方都可以用他的实现类进行替换,实际就是虚拟机制,语言级别实现面向对象功能。
依赖倒转原则
(Dependence Inversion Principle, DIP)
★★★★★
依赖于抽象(接口),不要依赖具体的实现(类),也就是针对接口编程。
接口隔离原则
(Interface Segregation Principle, ISP
★★☆☆☆
不应该强迫用户的程序依赖他们不需要的接口方法。一个接口应该只提供一种对外功能,不应该把所有操作都封装到一个接口中去。
合成复用原则
(Composite Reuse Principle, CRP)
★★★★☆
如果使用继承,会导致父类的任何变换都可能影响到子类的行为。如果使用对象组合,就降低了这种依赖关系。对于继承和组合,优先使用组合。
迪米特法则
(Law of Demeter, LoD
★★★☆☆
一个对象应当对其他对象尽可能少的了解,从而降低各个对象之间的耦合,提高系统的可维护性。例如在一个程序中,各个模块之间相互调用时,通常会提供一个统一的接口来实现。这样其他模块不需要了解另外一个模块的内部实现细节,这样当一个模块内部的实现发生改变时,不会影响其他模块的使用。(黑盒原理)

2.单一职责原则

  • 类的职责单一,对外只提供一种功能,而引起类变化的原因都应该只有一个。
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()
}

3.开闭原则

  • 即添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。

  • 场景:有一个银行业务员,他拥有很多的业务,比如Save()存款、Transfer()转账、Pay()支付等。

3.1平铺式设计

  • 当我们去给Banker添加新的业务的时候,会直接修改原有的Banker代码,那么Banker模块的功能会越来越多,出现问题的几率也就越来越大,假如此时Banker已经有99个业务了,现在我们要添加第100个业务,可能由于一次的不小心,导致之前99个业务也一起崩溃,因为所有的业务都在一个Banker类里,他们的耦合度太高,Banker的职责也不够单一,代码的维护成本随着业务的复杂正比成倍增大。
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()
}

3.2.开闭原则设计

  • 接口的意义:如果我们拥有接口, interface这个东西,那么我们就可以抽象一层出来,制作一个抽象的Banker模块,然后提供一个抽象的方法。 分别根据这个抽象模块,去实现支付Banker(实现支付方法),转账Banker(实现转账方法)

  • 当我们想要给Banker添加额外功能的时候,之前我们是直接修改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{})
}

3.3接口的意义

  • 接口的最大的意义就是实现多态的思想,就是我们可以根据interface类型来设计API接口,那么这种API接口的适应能力不仅能适应当下所实现的全部模块,也适应未来实现的模块来进行调用。 调用未来可能就是接口的最大意义所在吧,这也是为什么架构师那么值钱,因为良好的架构师是可以针对interface设计一套框架,在未来许多年却依然适用。

4.里氏替换原则

  • 任何抽象类(interface)接口,出现的地方都可以用他的实现类进行替代,实际就是虚拟机制,语言级别实现面向对象功能

4.1.含义

(1)子类可以隐式的转为父类
(2)父类必须强转子类

4.2.里氏替换原则的优点:

(1)约束继承泛滥,是开闭原则的一种体现
(2)加强程序的健壮性,同时变更时可以做到非常好的兼容性

5.依赖倒转原则

5.1耦合度极高的模块关系设计

  • 图中每个模块之间的依赖关系,实际上并没有用到任何的interface接口层的代码,显然最后我们的两个业务 张三开奔驰, 李四开宝马,程序中也都实现了。但是这种设计的问题就在于,小规模没什么问题,但是一旦程序需要扩展,比如我现在要增加一个丰田汽车 或者 司机王五, 那么模块和模块的依赖关系将成指数级递增,想蜘蛛网一样越来越难维护和捋顺。
package main

import "fmt"

// === > 奔驰汽车 <===
type Benz struct {

}

func (this *Benz) Run() {
    fmt.Println("Benz is running...")
}

// === > 宝马汽车  <===
type BMW struct {

}

func (this *BMW) Run() {
    fmt.Println("BMW is running ...")
}


//===> 司机张三  <===
type Zhang3 struct {
    //...
}

func (zhang3 *Zhang3) DriveBenZ(benz *Benz) {
    fmt.Println("zhang3 Drive Benz")
    benz.Run()
}

func (zhang3 *Zhang3) DriveBMW(bmw *BMW) {
    fmt.Println("zhang3 drive BMW")
    bmw.Run()
}

//===> 司机李四 <===
type Li4 struct {
    //...
}

func (li4 *Li4) DriveBenZ(benz *Benz) {
    fmt.Println("li4 Drive Benz")
    benz.Run()
}

func (li4 *Li4) DriveBMW(bmw *BMW) {
    fmt.Println("li4 drive BMW")
    bmw.Run()
}

func main() {
    //业务1 张3开奔驰
    benz := &Benz{}
    zhang3 := &Zhang3{}
    zhang3.DriveBenZ(benz)

    //业务2 李四开宝马
    bmw := &BMW{}
    li4 := &Li4{}
    li4.DriveBMW(bmw)
}

5.2面向抽象层依赖倒转

  • 如上图所示,如果我们在设计一个系统的时候,将模块分为3个层次,抽象层、实现层、业务逻辑层。那么,我们首先将抽象层的模块和接口定义出来,这里就需要了interface接口的设计,然后我们依照抽象层,依次实现每个实现层的模块,在我们写实现层代码的时候,实际上我们只需要参考对应的抽象层实现就好了,实现每个模块,也和其他的实现的模块没有关系,这样也符合了上面介绍的开闭原则。这样实现起来每个模块只依赖对象的接口,而和其他模块没关系,依赖关系单一。系统容易扩展和维护。

  • 我们在指定业务逻辑也是一样,只需要参考抽象层的接口来业务就好了,抽象层暴露出来的接口就是我们业务层可以使用的方法,然后可以通过多态的线下,接口指针指向哪个实现模块,调用了就是具体的实现方法,这样我们业务逻辑层也是依赖抽象成编程。

package main

import (
	"fmt"
)


// ----------------------------抽象层----------------------------
type Car interface {
	Run()
}

type Driver interface {
	Drive(car Car)
}

// ----------------------------实现层----------------------------
// 如果新来一个司机,只需要增加一个司机实体(不会对原有类进行修改)
// 如果新增一个品牌的车,只需要增加一个品牌实体(不会对原有类进行修改)
type FtCar struct {}

func (f *FtCar) Run() {
	fmt.Println("丰田车开起来了...")
}

type BmCar struct {}

func (f *BmCar) Run() {
	fmt.Println("宝马车开起来了...")
}

type BzCar struct {}

func (f *BzCar) Run() {
	fmt.Println("奔驰车开起来了...")
}

type Zh3 struct {}

func (z *Zh3) Drive(car Car) {
	car.Run()
}


func main()  {
	// ----------------------------业务层----------------------------
	// 1.new一个司机张三
	zs := &Zh3{}
	// 2.new一个奔驰汽车
	car := &BzCar{}
	// 3.把汽车交给张三驾驶
	zs.Drive(car)
}

6.接口隔离原则

  • 不应该强迫用户依赖他们不需要的接口方法,一个接口应该只提供一种对外功能,不应该把所有操做都封装到一个接口中去

7.合成复用原则

  • 如果使用继承,会导致父类的任何变换都可能影响到子类的行为。如果使用对象组合,就降低了这种依赖关系。对于继承和组合,优先使用组合。

  • 组合的方式即让一个类成为另一个类的属性(如果使用继承,父类增加一个方法,则子类就具有这个方法,并且可以直接调用,父类如果修改,调用结果也会变,如果使用组合,被组合进来的类,增加、修改自身方法或属性,都不会影响原类的使用方式以及结果)

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() {
    fmt.Println("小猫睡觉")
}


func main() {
    //通过继承增加的功能,可以正常使用
    cb := new(CatB)
    cb.Eat()
    cb.Sleep()

    //通过组合增加的功能,可以正常使用
    cc := new(CatC)
    cc.C = new(Cat)
    cc.C.Eat()
    cc.Sleep()
}

8.迪米特法则

  • 一个对象应当与其他对象尽可能少的了解,从而降低各个对象之间的耦合,提高系统的可维护性,例如在一个程序中,各个模块相互调用时,通常会提供一个统一接口来实现,这样其他模块就不需要了解另一个模块了。

三、创建型模式

1.简单工厂模式

1.1为什么需要工厂模式

1.1.1平铺模式

package main

import "fmt"

type Phone struct {
    name string
}

func (p *Phone) Call() {
    if p.name == "华为" {
	fmt.Println("通过华为手机打电话")
    } else if p.name == "小米" {
	fmt.Println("通过小米手机打电话")
    } else {
	fmt.Println("通过苹果手机打电话")
    }
}

func CreatePhone(name string) *Phone {
    phone := &Phone{name}
    return phone
}

func main()  {
    p := CreatePhone("华为")
    p.Call()
}

1.1.2缺点

(1)Call方法本身是打电话,但内部又有很多的if-else判断。一方面不符合见名知意的命名法则,另一方面不易于阅读,Call功能变得不纯粹

(2)当需要增加新类型的手机时,必须修改Phone类的Call方法,违反了“开闭原则”

1.2简单工厂模式

package simple

import "fmt"

// --------------抽象层------------
type Phone interface {
    Call()
}

// --------------实现层------------
type HuwPhone struct {
}

func (h *HuwPhone) Call() {
    fmt.Println("通过华为手机打电话")
}

type XMiPhone struct {
}

func (h *XMiPhone) Call() {
    fmt.Println("通过小米手机打电话")
}

type IPhone struct {
}

func (h *IPhone) Call() {
    fmt.Println("通过苹果手机打电话")
}

type SimpleFactory struct {
}

// 接口类型的返回值,只接受引用类型
func (f *SimpleFactory) CreatePhone(name string) Phone {
    if name == "h" {
	return &HuwPhone{}
    } else if name == "x" {
	return &XMiPhone{}
    } else {
	return &IPhone{}
    }
}

1.2.1优点

(1)解放了Call方法,使其共功能职责明确

1.2.2缺点

(1)增加系统中类的个数,复杂度和理解度增加

(2)违反“开闭原则”,添加新产品需要修改工厂逻辑,工厂越来越复杂

1.2.3适用场景:

(1)工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。

(2)客户端只知道传入工厂类的参数,对于如何创建对象并不关心。

2.工厂模式

2.1示例

package normal


import "fmt"


// --------------抽象层------------
type Phone interface {
	Call()
}

type Factory interface {
	CreatePhone() *Phone
}

// --------------实现层------------
type HuaWeiPhone struct {
}

func (h *HuaWeiPhone) Call()  {
	fmt.Println("华为手机打电话")
}

type XiMiPhone struct {
}

func (h *XiMiPhone) Call()  {
	fmt.Println("小米手机打电话")
}

type IPhone struct {
}

func (h *IPhone) Call()  {
	fmt.Println("苹果手机打电话")
}

// --------------工厂层------------
type WClFactory struct {
}

func (wcf *WClFactory) CreatePhone() Phone {
	return &HuaWeiPhone{}
}

type FuSKFactory struct {
}

func (fsk *FuSKFactory) CreatePhone() Phone {
	return &IPhone{}
}

type YHdFactory struct {
}

func (yhd *YHdFactory) CreatePhone() Phone {
	return &XiMiPhone{}
}

func main()  {
    hFac := &normal.WClFactory{}
    hw1 := hFac.CreatePhone()
    hw1.Call()
    mFac := &normal.YHdFactory{}
    xm1 := mFac.CreatePhone()
    xm1.Call()
}

2.2优点:

  1. 不需要记住具体类名,甚至连具体参数都不用记忆。

  2. 实现了对象创建和使用的分离。

  3. 系统的可扩展性也就变得非常好,无需修改接口和原类。

  4. 对于新产品的创建,符合开闭原则。

2.3缺点:

  1. 增加系统中类的个数,复杂度和理解度增加。

  2. 增加了系统的抽象性和理解难度。

2.4适用场景:

  1. 客户端不知道它所需要的对象的类。

  2. 抽象工厂类通过其子类来指定创建哪个对象。

3.抽象工厂模式

3.1抽象工厂

  • 工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题

  • 但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。

  • 因此,可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产,这就是本文将要学习的抽象工厂模式的基本思想。

  • 所以“抽象工厂方法模式”引出了产品族产品等级结构概念,其目的是为了更加高效的生产同一个产品组产品

3.2角色和职责

  • 抽象工厂(Abstract Factory)角色:它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。

  • 具体工厂(Concrete Factory)角色:它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。

  • 抽象产品(Abstract Product)角色:它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。

  • 具体产品(Concrete Product)角色:它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。

3.3示例

package abstract

import "fmt"

// -----------抽象层------------
type XiMiPhone interface {
    Call()
}

type HuaWeiPhone interface {
    Call()
}

type IPhone interface {
    Call()
}

type AbsFactory interface {
    CreateHuaWei() *HuaWeiPhone
    CreateXiMi() *XiMiPhone
    CreateIphone() *IPhone
}
// -----------实现层------------
// 富士康产品族
type FSKHuaWei struct {
}

func (fsk *FSKHuaWei) Call() {
    fmt.Println("富士康华为手机打电话")
}

type FSKXiMi struct {
}

func (fsk *FSKXiMi) Call() {
    fmt.Println("富士康小米手机打电话")
}

type FSKIphone struct {
}

func (fsk *FSKIphone) Call() {
    fmt.Println("富士康苹果手机打电话")
}

type FSKFactory struct {
}

func (fsk *FSKFactory) CreateHuaWei() HuaWeiPhone {
    return &FSKHuaWei{}
}

func (fsk *FSKFactory) CreateXiMi() XiMiPhone {
    return &FSKXiMi{}
}

func (fsk *FSKFactory) CreateIphone() IPhone {
    return &FSKIphone{}
}

// 伟创力产品族
type WCLHuaWei struct {
}

func (fsk *WCLHuaWei) Call() {
    fmt.Println("伟创力华为手机打电话")
}

type WCLXiMi struct {
}

func (fsk *WCLXiMi) Call() {
    fmt.Println("伟创力小米手机打电话")
}

type WCLIphone struct {
}

func (fsk *WCLIphone) Call() {
    fmt.Println("伟创力苹果手机打电话")
}

type WCLFactory struct {
}

func (fsk *WCLFactory) CreateHuaWei() HuaWeiPhone {
    return &WCLHuaWei{}
}

func (fsk *WCLFactory) CreateXiMi() XiMiPhone {
    return &WCLXiMi{}
}

func (fsk *WCLFactory) CreateIphone() IPhone {
    return &WCLIphone{}
}

func main()  {
    fFac := &abstract.FSKFactory{}
    hw2 := fFac.CreateHuaWei()
    hw2.Call()
    iphone := fFac.CreateIphone()
    iphone.Call()

    wFac := &abstract.WCLFactory{}
    xm2 := wFac.CreateXiMi()
    xm2.Call()
}

// 解决问题:当一个产品族(工厂)中的多个对象被设计成一起工作时,
//			它能够保证客户端始终只用同一个产品族中的对象(减少类的创建)
//          增加新的产品族很方便,无需修改已有系统,符合:开闭原则
// 问题:增加新的产品等级(手机种类)结构麻烦,需要对原有系统进行较大的改动,
//		 甚至需要修改抽象层代码,这显然会带来较大的不便,违背了:开闭原则
// 2.增加了系统的抽象性和理解难度

3.4优点

(1)拥有工厂方法模式的优点

(2)当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。

(3)增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。

3.5缺点

(1)增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。

3.6适用场景

(1)系统中有多于一个的产品族。而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。

(2)产品等级结构稳定。设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。

3.7练习

--->设计一个电脑主板架构,电脑包括(显卡,内存,CPU)3个固定的插口,显卡具有显示功能(display,功能实现只要打印出意义即可),内存具有存储功能(storage),cpu具有计算功能(calculate)。
--->现有Intel厂商,nvidia厂商,Kingston厂商,均会生产以上三种硬件。
--->要求组装两台电脑,
------->1台(Intel的CPU,Intel的显卡,Intel的内存)
------->1台(Intel的CPU, nvidia的显卡,Kingston的内存)
用抽象工厂模式实现。

package main

import "fmt"

// -----------抽象层------------
type AbstractGPU interface {
    // 显卡图像渲染
    ImageRendering()
}

type AbstractCPU interface {
    // CPU实时计算
    RealTimeComputing()
}

type AbstractMemory interface {
    // 缓存数据
    CacheData()
}

type AbstractFactory interface {
    // 生产显卡GPU
    CreateCPU() *AbstractGPU
    // 生产CPU
    CreateCpu() *AbstractCPU
    // 生产内存
    CreateMemory() *AbstractMemory
}

// -----------实现层------------
// Intel产品族
type IntelGPU struct {
}

func (intel *IntelGPU) ImageRendering() {
    fmt.Println("图像渲染成功")
}

type IntelCPU struct {
}
func (intel *IntelCPU) RealTimeComputing() {
    fmt.Println("数据已处理完成")
}

type IntelMemory struct {
}
func (intel *IntelMemory) CacheData() {
    fmt.Println("数据缓存成功")
}

type Intel struct {
}

func (intel *Intel) CreateGPU() AbstractGPU {
    absGpu := &IntelGPU{}
    return absGpu
}

func (intel *Intel) CreateCPU() AbstractCPU {
    abCGpu := &IntelCPU{}
    return abCGpu
}

func (intel *Intel) CreateMemory() AbstractMemory {
    absMem := &IntelMemory{}
    return absMem
}

func main()  {
    fac := &Intel{}
    cpu := fac.CreateCPU()
    cpu.RealTimeComputing()
}

4.单例模式

4.1.示例

package main

import (
    "fmt"
    "sync"
)

/*
三个要点:
	类只能有一个实例
	它必须自行创建这个实例
	它必须自行向整个系统提供这个实例
 */

/*
	保证一个类永远只能有一个对象,这个对象还能被系统的其他模块使用
 */

// 1.使用小写,不让外部导入,保证私有
type single struct {}

// ------------------------每次加载都会产生一个对象--------------------
/*
// 2.保证内部通过指针,可以访问到该对象,且指针永远不能被改变指向
var instance *single = new(single)

// 3.需要对外提供一个方法来获取到这个对象
func GetInstance() *single {
    return instance
}
 */

// -------------------------懒汉模式:当调用GetInstance时再创建对象;问题:并发情况下可能出现多次创建--------------------
/*
// 2.保证内部通过指针,可以访问到该对象,且指针永远不能被改变指向
var instance *single

// 3.需要对外提供一个方法来获取到这个对象
func GetInstance() *single {
    if instance == nil {
	instance = new(single)
	return instance
    }
    return instance
}
*/

// -------------------------懒汉模式:加锁处理,问题:影响效率--------------------
/*
// 2.保证内部通过指针,可以访问到该对象,且指针永远不能被改变指向
var instance *single

var lock sync.Mutex
// 3.需要对外提供一个方法来获取到这个对象
func GetInstance() *single {
    // 为了线程安全
    lock.Lock()
    defer lock.Unlock()
    if instance == nil {
	instance = new(single)
	return instance
    }
    return instance
}
*/

// -------------------------懒汉模式:解决每次都加锁得到耗时问题--------------------
/*
// 定义锁
var lock sync.Mutex
// 标记
var initialized  uint32

// 2.保证内部通过指针,可以访问到该对象,且指针永远不能被改变指向
var instance *single

// 3.需要对外提供一个方法来获取到这个对象
func GetInstance() *single {
    if atomic.LoadUint32(&initialized) == 1 {
	return instance
    }
    // 为了线程安全,申请加锁
    lock.Lock()
    defer lock.Unlock()

    if instance == nil {
	instance = new(single)
	// 设置标记位
	atomic.StoreUint32(&initialized, 1)
	return instance
    }
    return instance
}
 */

// -------------------------Go语言提供的简便方式--------------------
var once sync.Once
// 2.保证内部通过指针,可以访问到该对象,且指针永远不能被改变指向
var instance *single
// 3.需要对外提供一个方法来获取到这个对象
func GetInstance() *single {
    once.Do(func() {  // 只能执行一次
	instance = new(single)
    })
    return instance
}

func (s *single) Print() {
    fmt.Println("........")
}

func main()  {
    c := GetInstance()
    fmt.Println(c)

    c1 := GetInstance()
    fmt.Println(c1)

    fmt.Println(c == c1)
}

4.2.优点

(1) 单例模式提供了对唯一实例的受控访问。

(2) 节约系统资源。由于在系统内存中只存在一个对象。

4.3.缺点

(1) 扩展略难。单例模式中没有抽象层。

(2) 单例类的职责过重。

4.4.适用场景

(1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。

(2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

四.结构型模式

1.代理模式

package main

import "fmt"

type Goods struct {
    Kind string
    Fact bool
}

// --------------抽象层------------
type Shopping interface {
    Buy(Goods *Goods)
}

// --------------实现层------------
type KoreaShopping struct {
}

func (ks *KoreaShopping) Buy(goods *Goods) {
    fmt.Println("去韩国进行了购物,买了:", goods.Kind)
}

type AmericaShopping struct {
}

func (ks *AmericaShopping) Buy(goods *Goods) {
    fmt.Println("去美国进行了购物,买了:", goods.Kind)
}

// 海外代理
type OverSeasProxy struct {
    // 代理抽象的数据类型
    shopping Shopping
}

func NewProxy(shopping Shopping) Shopping {
    return &OverSeasProxy{shopping}
}

func (op *OverSeasProxy) Buy(goods *Goods) {
    // 1.鉴别真伪
    if op.distinguish(goods) {
	// 2.调用具体要被代理的购物方式的Buy()方法
	op.shopping.Buy(goods)
	// 3.海关安检
	op.check(goods)
    }
}

func (op *OverSeasProxy) distinguish(goods *Goods) bool {
    fmt.Println("对[", goods.Kind, "]进行了辨别真伪.")
    if goods.Fact {
	return goods.Fact
    }
    fmt.Println("发现假货,不应该购买")
    return false
}

func (op *OverSeasProxy) check(goods *Goods) {
    fmt.Println("对[", goods.Kind, "]进行了海关检查,成功待会祖国.")
}

func main()  {
    g1 := &Goods{
	Kind: "面膜",
	Fact: true,
    }

    g2 := &Goods{
	Kind: "CET4",
	Fact: false,
    }

    ks := &KoreaShopping{}
    ps := NewProxy(ks)
    ps.Buy(g1)
    ps.Buy(g2)
}
  • 场景案例
package main

import "fmt"

// 抽象主题
type BeautyWoman interface {
    // 抛媚眼
    MakeEyes()
    // 约会
    Appointment()
}

// 具体主题
// ------------------潘金莲------------------
type PanJinLian struct {
}

func (p *PanJinLian) MakeEyes() {
    fmt.Println("潘金莲抛了一个媚眼...")
}

func (p *PanJinLian) Appointment() {
    fmt.Println("潘金莲答应约会...")
}

// 中间代理
// -------------------王婆------------------
type WangPo struct {
    // 王婆手里有美女资源(都是抽象的)
    beauty BeautyWoman
}

func (p *WangPo) MakeEyes() {
    fmt.Println("王婆开始联系美女...")
    p.beauty.MakeEyes()
}

func (p *WangPo) Appointment() {
    fmt.Println("王婆开始联系美女...")
    p.beauty.Appointment()
}

// 王婆得到美女的信息,开始沟通
func BeautyProxy(meiNv BeautyWoman) BeautyWoman {
    return &WangPo{meiNv}
}

func main()  {
    // 1.西门庆找潘金莲,联系到了王婆,将潘金莲的联系方式交给了王婆
    pan := &PanJinLian{}
    proxy := BeautyProxy(pan)
    // 2.王婆与潘金莲一番沟通后,潘金莲答应与西门庆约会
    proxy.MakeEyes()
    proxy.Appointment()
}

2.装饰器模式

package main

import "fmt"

/*
场景:给手机增加铃声
 */

// -----------------抽象层-----------------
type Phone interface {
	Call()
}

// 抽象的装饰器,装饰器的基础类
// 该类本应该是interface,但是Golang的interface语法不可以有成员属性
type BaseDecorator struct {
	phone Phone  // 组合进来抽象的Phone
}

func (bd *BaseDecorator) Call() {}

// -----------------实现层-----------------
type HwPhone struct {}

func (h *HwPhone) Call() {
	fmt.Println("通过华为手机打电话,电话已接通....")
}

type XmPhone struct {}

func (h *XmPhone) Call() {
	fmt.Println("通过小米手机打电话,电话已接通....")
}

// 添加铃声的装饰器

/* 这样写就是代理模式
type RingingDecorator struct {
	phone Phone
}

func (rd *RingingDecorator) Call() {
	fmt.Println("在你的心上,自由的飞翔....")
	rd.phone.Call()
}

func NewRingingDecorator(phone Phone) Phone {
	return &RingingDecorator{phone}
}
 */

type RingingDecorator struct {
	BaseDecorator  // 继承基础的装饰器类(主要继承Phone的Call方法)
}

func (rd *RingingDecorator) Call() {
	fmt.Println("在你的心上,自由的飞翔....")
	rd.phone.Call()
}

func NewRingingDecorator(phone Phone) Phone {
	return &RingingDecorator{BaseDecorator{phone}}
}

// 添加贴膜装饰器
type FilmDecorator struct {
	BaseDecorator  // 继承基础的装饰器类(主要继承Phone的Call方法)
}

func (fd *FilmDecorator) Call() {
	fmt.Println("手机已贴膜...")
	fd.phone.Call()
}

func NewFilmDecorator(phone Phone) Phone {
	return &FilmDecorator{BaseDecorator{phone}}
}

func main()  {
    // -----------------业务层-----------------
    // 打电话
    hw := &HwPhone{}
    hw.Call()
    fmt.Println("------------------------")

    // 增加铃声后打电话
    rd := NewRingingDecorator(hw)
    rd.Call()
    fmt.Println("------------------------")

    // 贴膜后打电话
    fd := NewFilmDecorator(hw)
    fd.Call()
    fmt.Println("------------------------")

    // 增加铃声并且贴膜
    fd1 := NewFilmDecorator(rd)  // 可以无限装饰
    fd1.Call()
}

3.适配器模式

package main

import "fmt"

/*
例如:英雄盖伦释放技能
		原本:普通攻击
		使用适配器处理后:
			增加皮肤:战斗学院,攻击 +10
		使用适配器处理后:
			增加皮肤:王国机神至臻,攻击 +15
      最终盖伦一释放技能就会关机

优点:
	1.将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无需修改原有结构
	2.增加了类的透明性和复用性,将具体的业务实现过程封装再适配者类中,对于业务层是透明的,而且提高了适配者的复用性,同一个适配者类可以再多个不同的系统中复用
	3.灵活性和扩展性都非常好,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合:"开闭原则"
缺点:
	适配器中置换适配者类的某些方法比较麻烦

适用场景:
	1.系统需要使用一些现有的类,而这些类的接口(如方法名)不太符合系统的需要,甚至没有这些类的源代码
	2.想创建一个可以重复使用的类,用于一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作
 */

// ------------------------------适配目标(抽象技能)---------------------------
type Attach interface {
    Fight()
}

// 具体技能
type Skill struct {
}

func (s *Skill) Fight() {
    fmt.Println("普通攻击.")
}


// 英雄类
type Hero struct {
    Name string
    attach Attach  // 攻击方式
}

func (h *Hero) Skill() {
    fmt.Println(h.Name, "使用了技能.")
    h.attach.Fight()  // 使用具体技能
}


// ---------------------------------适配者----------------------------------
type Skin interface {
    Skill()
}

type 战斗学院 struct {
}

func (p *战斗学院) Skill() {
    fmt.Println("更换皮肤战斗学院,攻击 +10.")
}

type 王国机神至臻 struct {
}

func (p *王国机神至臻) Skill() {
    fmt.Println("更换皮肤王国机神至臻,攻击 +15.")
}


// --------------------适配器(将适配目标和适配者关联起来)--------------------
type Adapter struct {
    skin Skin
}

func (a *Adapter) Fight() {
    a.skin.Skill()  // 调用适配者的攻击方式
}

func NewAdapter(s Skin) *Adapter {
    return &Adapter{s}
}

func main()  {
    // 业务类
    gailun := Hero{"盖伦", new(Skill)}
    gailun.Skill()

    gailun1 := Hero{"盖伦", NewAdapter(new(战斗学院))}
    gailun1.Skill()

    gailun2 := Hero{"盖伦", NewAdapter(new(王国机神至臻))}
    gailun2.Skill()
}

4.外观模式

package main

import "fmt"

/*
外观模式
优点:
	1.它对业务层屏蔽了子系统组件,减少了业务层所需要处理的对象的数目,并使得子系统使用起来更加容易,通过引入外观模式,客户端代码将变得非常简单,与之关联的对象也很少
	2.它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的业务层,只需要调整外观类即可
	3.一个子系统的修改对其他子系统没有任何影响
缺点:
	1.不能很好地限制业务层直接使用子系统类,如果业务层访问子系统类做太多的限制,则减少了可变性和灵活性
	2.如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则

适用场景:
	1.复杂系统需要简单入口使用
	2.业务层与多个子系统之间存在很大的依赖性
	3.在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度
 */

// 电视机
type Tv struct {}

func (t *Tv) on() {
    fmt.Println("打开电视机.")
}

func (t *Tv) off() {
    fmt.Println("关闭电视机.")
}

// 音响
type VoiceBox struct {}

func (t *VoiceBox) on() {
    fmt.Println("打开音响.")
}

func (t *VoiceBox) off() {
    fmt.Println("关闭音响.")
}

// 灯光
type Light struct {}

func (t *Light) on() {
    fmt.Println("打开灯光.")
}

func (t *Light) off() {
    fmt.Println("关闭灯光.")
}

// 游戏机
type XBox struct {}

func (t *XBox) on() {
    fmt.Println("打开游戏机.")
}

func (t *XBox) off() {
    fmt.Println("关闭游戏机.")
}

// 麦克风
type MicroPhone struct {}

func (t *MicroPhone) on() {
    fmt.Println("打开麦克风.")
}

func (t *MicroPhone) off() {
    fmt.Println("关闭麦克风.")
}

// 投影仪
type Projector struct {}

func (t *Projector) on() {
    fmt.Println("打开投影仪.")
}

func (t *Projector) off() {
    fmt.Println("关闭投影仪.")
}

// 家庭影院(外观)
type HomePlayerFacade struct {
    vb VoiceBox
    pr Projector
    light Light
    tv Tv
    xb XBox
    mp MicroPhone
}

// KTV模式
func (hp *HomePlayerFacade) DoKTV() {
    hp.light.on()
    hp.tv.on()
    hp.vb.on()
    hp.mp.on()

    hp.pr.off()
    hp.xb.off()
}

// 游戏模式
func (hp *HomePlayerFacade) DoGame() {
    hp.tv.on()
    hp.mp.on()
    hp.xb.on()

    hp.vb.off()
    hp.light.off()
    hp.pr.off()
    hp.tv.off()
}

// 观影模式
func (hp *HomePlayerFacade) DoMovie() {
    hp.vb.on()
    hp.pr.on()

    hp.mp.off()
    hp.light.off()
    hp.xb.off()
    hp.tv.off()
}

func main()  {
    hp := &HomePlayerFacade{}
    hp.DoGame()
}

五.行为型模式

1.模板方法模式

package main

import "fmt"

/*
模板方法模式

优点:
	1.在父类中i形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序
	2.模板方式是一种代码复用技术,它在类库设计中尤为重要,他提取了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行为,它鼓励我们恰当的使用继承来复用代码
	3.可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行
	4.在模板方法模式中,可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则
	5.在抽象类中统一操做步骤,并规定好接口;让子类实现接口,这样可以把各个具体的子类和操做步骤解耦合
缺点:
	需要为每一个基本方法的不同实现提供一个子类,如果符类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象

适用场景:
	1.具有统一操做步骤或者操做过程
	2.具有不同的操做细节
	3.存在多个具有相同操作步骤的应用场景,单某些具体操做细节却各不相同
*/


// 抽象类,泡茶,包裹在一个模板,全部实现的步骤
type Tea interface {
    // 烧水
    BoilWater()
    // 将茶叶放入杯中
    Input()
    // 给杯中加入开水
    PourWater()
    // 封口
    Cover()
    // 装袋
    Encapsulation()
    // 打包
    Pack() bool
}


// 封装一套流程模板,让具体类的制作流程继承且实现
type template struct {
    t Tea
}

func (t *template) Make() {
    if t == nil {
	return
    }
    // 固定流程
    t.t.BoilWater()
    t.t.Input()
    t.t.PourWater()
    t.t.Cover()
    if t.t.Pack() {
	t.t.Encapsulation()
    }
  fmt.Println("制作完成,欢迎下次光临...")
}

// 具体泡茶流程,制作饮料
type Drinks struct {
    template  // 继承模板
}

func (d *Drinks) BoilWater() {
    fmt.Println("水已烧开.")
}

func (d *Drinks) Input() {
    fmt.Println("将柠檬汁倒入杯中.")
}
func (d *Drinks) PourWater() {
    fmt.Println("将开水倒入杯中.")
}

func (d *Drinks) Cover() {
    fmt.Println("封口.")
}

func (d *Drinks) Encapsulation() {
    fmt.Println("打包成功.")
}

func (d *Drinks) Pack() bool {
    return true
}

func NewDrinks() *Drinks {
    drink := &Drinks{}
    // t是Tea,是Drinks的接口,这里需要给接口赋值,让t指向具体的子类
    // 来触发t的全部方法的多态特征
    drink.t = drink
    return drink
}

func main()  {
    //drink := NewDrinks()
    //drink.Make()

    // 如果没有构造函数,可以这样调用
    t := template{&Drinks{}}  // 给模板方法实例化具体的饮料制作接口
    d := &Drinks{t}
    d.Make()  // 调用继承来的值作方法
    // t.Make()  // 直接调用模板方法
}

2.命令模式

package main

import "fmt"

/*
命令模式

场景:
	路边撸串烧烤场景:
		有烤羊肉串、烤鸡翅、烤茄子、.....的命令
		有烤串师傅
		有服务员
		有顾客

优点:
	1.降低系统耦合度,由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间完全解耦,相同的请求者可以对应不同的接收者,同样相同的接收者可以供不同的请求者使用,两者具有良好的独立性
	2.新的命令可以很容易的加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无需修改原有系统源码,甚至客户类代码,满足开闭原则的要求
	3.可以比较容易地设计一个命令队列或宏命令(组合命令)
缺点:
	使用命令模式可能会导致某些系统有过多的具体命令类,因为针对每一个对请求接收者的调用操做,
	都需要设计一个具体命令类,因此在系统中可能需要提供大量的具体命令类,这将影响命令模式的使用

适用场景:
	1.系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无需知道接收者的存在,也无需知道接收者是谁。接收者无需关系何时被调用
	2.系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命周期,换言之。最初的请求调用者可能已不存在了,
		而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无需关心请求调用者的存在性,
		可以通过请求日志文件等机制来具体实现
	3.系统需要将一组操做合在一起形成宏命令
*/

// -----------------------------命令接收者(烤串师傅)-----------------------------
type Cook struct {}

func (c Cook) Kabob() {
    fmt.Println("厨师烤了肉串.")
}

func (c Cook) Chicken() {
    fmt.Println("厨师烤了鸡翅.")
}

func (c Cook) Eggplant() {
    fmt.Println("厨师烤了茄子.")
}

// ----------------------------------抽象命令-----------------------------------
type Command interface {
    Make()
}

// ----------------------------------具体命令-----------------------------------
type CommandCookKabob struct {
    cook *Cook
}

func (cmd *CommandCookKabob) Make() {
    cmd.cook.Kabob()
}


type CommandCookChicken struct {
    cook *Cook
}

func (cmd *CommandCookChicken) Make() {
    cmd.cook.Chicken()
}
// -----------------------------命令发出者(服务员)-----------------------------
type Waiter struct {
    CmdList []Command  // 接收一系列的命令
}

func (w *Waiter) Notify() {
    if w.CmdList == nil {
	return
    }
    for _, cmd := range w.CmdList {
	cmd.Make()
    }
}

func main()  {
    cook := &Cook{}
    cmdKabob := &CommandCookKabob{cook}
    cmdChicken := &CommandCookChicken{cook}

    waiter := &Waiter{}
    waiter.CmdList = append(waiter.CmdList, cmdKabob)
    waiter.CmdList = append(waiter.CmdList, cmdChicken)
    waiter.Notify()
}

3.策略模式

package main

import "fmt"

/*
策略模式

优点:
	1.策略模式提供了对开闭原则的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活的增加新的算法或行为
	2.使用策略模式可以避免多重条件选择语句。多重条件选择语句不易维护,它把采取哪一种算法或行为的逻辑与算法或行为本身的实现逻辑混合在一起,
		将他们全部硬编码在一个庞大的多重条件选择语句中,比直接继承环境类的办法还要原始落后
	3.策略模式提供了一种算法的复用机制。由于将算法单独取出来封装在策略类中,因此,不同的环境类可以方便的复用这些策略类
缺点:
	1.业务层必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客业务层须理解这些算法的区别,以便适时选择恰当的算法
		换言之,策略模式只是用于业务层知道所有的算法或行为的情况
	2.策略模式将造成系统产生很多具体策略类,任何小的变化都将导致系统要增加一个新的具体策略类
*/

// ---------------------------抽象策略---------------------------
type WeaponPolicy interface {
    UseWeapon()  // 使用武器
}

// ---------------------------具体策略---------------------------
type Ak47 struct {}

func (ak *Ak47) UseWeapon() {
    fmt.Println("使用Ak47去战斗")
}

type Knife struct {}

func (ak *Knife) UseWeapon() {
    fmt.Println("使用匕首去战斗")
}

// --------------------------策略使用者---------------------------
type Hero struct {
    policy WeaponPolicy  // 拥有一个抽象策略
}

// 设置不同的策略的方法
func (h *Hero) SetWeaponPolicy(s WeaponPolicy) {
    h.policy = s
}

// 战斗方法
func (h *Hero) Fight() {
    if h.policy == nil {
	fmt.Println("空手去战斗")
	return
    }
    h.policy.UseWeapon()  // 调用具体策略
}

func main()  {
    hero := &Hero{}

    // 不使用策略
    hero.Fight()

    // 使用策略:Ak47
    hero.SetWeaponPolicy(&Ak47{})
    hero.Fight()

    // 使用策略:匕首
    hero.SetWeaponPolicy(&Knife{})
    hero.Fight()
}
  • 场景示例
package main

import (
    "fmt"
    "math"
)

/*
模拟场景:
	商场促销:策略A(0.8折)策略B(消费满200.减少80)
 */

type Goods struct {
    Name string
    Price float64
}

// ---------------------------抽象策略---------------------------
type ActivityPolicy interface {
    UsePolicy([]*Goods)
}

// ---------------------------具体策略---------------------------
type DiscountPolicy struct {}

func (dp *DiscountPolicy) UsePolicy(gs []*Goods) {
    totalPrice := 0.0
    for _, goods := range gs {
	totalPrice += goods.Price
	fmt.Println("美女买了:", goods.Name, "价格:", goods.Price)
    }

    discountPrice := totalPrice - totalPrice * 0.08
    fmt.Println("总价:", totalPrice, " 折后总共需要支付:", discountPrice, "元人民币.")
}

type FullDecrementPolicy struct {}

func (fdp *FullDecrementPolicy) UsePolicy(gs []*Goods) {
    totalPrice := 0.0
    for _, goods := range gs {
	totalPrice += goods.Price
	fmt.Println("美女买了:", goods.Name, "价格:", goods.Price)
    }
    num := math.Floor(totalPrice / 200)  // 向下取整
    decrPrice := totalPrice - num * 80
    fmt.Println("总价:", totalPrice, " 满减后总共需要支付:", decrPrice, "元人民币.")
}
// --------------------------策略使用者---------------------------
type MeiNu struct {
    policy ActivityPolicy
    goods []*Goods
}

func (m *MeiNu) OriginalPrice() {
	totalPrice := 0.0
    for _, goods := range m.goods {
	totalPrice += goods.Price
	fmt.Println("美女买了:", goods.Name, "价格:", goods.Price)
    }
    fmt.Println("总共需要支付:", totalPrice, "元人民币.")
}

func (m *MeiNu) SetPolicy(policy ActivityPolicy) {
    m.policy = policy
}

func (m *MeiNu) Closing() {
    if m.policy == nil {
	m.OriginalPrice()
	return
    }
    m.policy.UsePolicy(m.goods)
}

func main()  {
    g1 := &Goods{
	Name:  "口红",
	Price: 126.8,
    }

    g2 := &Goods{
	Name:  "面膜",
	Price: 98.0,
    }

    g3 := &Goods{
	Name:  "羽绒服",
	Price: 998.9,
    }

    g4 := &Goods{
	Name:  "火锅",
  	Price: 216.5,
    }


    meiNv := &MeiNu{}
    meiNv.goods = append(meiNv.goods, g1)
    meiNv.goods = append(meiNv.goods, g2)
    meiNv.goods = append(meiNv.goods, g3)
    meiNv.goods = append(meiNv.goods, g4)

    // 直接支付,不参加活动
    meiNv.Closing()

    // 参加8折活动
    meiNv.SetPolicy(&DiscountPolicy{})
    meiNv.Closing()

    // 参加满减活动
    meiNv.SetPolicy(&FullDecrementPolicy{})
    meiNv.Closing()
}

4.观察者模式

package main

import "fmt"

/*
观察者模式

优点:
	1.观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体的观察者角色
	2.观察者模式在观察目标和观察者之间建立一个抽象的耦合,观察目标只需要维持在一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者之间没有紧密的耦合在一起,因此他们可以属于不同的抽象化层次
	3.观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多的系统设计的难度
	4.观察者模式满足开闭原则,增加新的具体观察者无需修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便
缺点:
	1.如果一个观察目标对象(通知者)有很多直接和间接观察者,将所有的观察者都通知到,会花很多时间
	2.如果在观察者和观察目标之间存在循环依赖,观察目标会触发他们之间进行循环调用,可能导致系统崩溃
	3.观察者模式没有相应的机制,让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化
 */

// ------------------------------抽象的观察者------------------------------
type Listener interface {
    OnTeacherComing()	// 观察对象
}

// ------------------------------抽象的通知者------------------------------
type Notifier interface {
    AddListener(listener Listener)	// 通知对象的添加
    RemListener(listener Listener)	// 通知对象的删除
    Notify()  // 通知
}


// ------------------------------具体的观察者------------------------------
type StuZh3 struct {
    thing string
}

func (s *StuZh3) DoThing() {
    fmt.Println("张三正在", s.thing, "...")
}

func (s *StuZh3) StopThing() {
    fmt.Println("张三停止了", s.thing, ".")
}

func (s *StuZh3) OnTeacherComing() {
    s.StopThing()
}

type StuLi4 struct {
    thing string
}

func (l *StuLi4) OnTeacherComing() {
    l.StopThing()
}

func (l *StuLi4) DoThing() {
    fmt.Println("李四正在", l.thing, "...")
}

func (l *StuLi4) StopThing() {
    fmt.Println("李四停止了", l.thing, ".")
}
// ------------------------------具体的通知者------------------------------
type ClassMonitor struct {
    listenerList []Listener  // 需要通知的全部观察者集合
}

func (c *ClassMonitor) AddListener(listener Listener) {
    c.listenerList = append(c.listenerList, listener)
}

func (c *ClassMonitor) RemListener(listener Listener) {
    for index, lisner := range c.listenerList {
	if lisner == listener {
	    // 将元素前后位置连接起来
	    c.listenerList = append(c.listenerList[:index], c.listenerList[index+1:]...)
	    break
	}
    }
}

func (c *ClassMonitor) Notify() {
    for _, listener := range c.listenerList {
	listener.OnTeacherComing()  // 呈现多态
    }
}

func main()  {
    s1 := &StuZh3{thing:"抄作业"}
    s2 := &StuLi4{thing:"打游戏"}

    fmt.Println("老师不在,大家在自己做自己的事...")
    s1.DoThing()
    s2.DoThing()

    cm := &ClassMonitor{}
    cm.AddListener(s1)
    cm.AddListener(s2)

    fmt.Println("班长通知大家老师来了.")
    cm.Notify()
}
posted @ 2022-10-20 11:56  fatpuffer  阅读(407)  评论(0)    收藏  举报