go-设计模式-精简版-创建型模式
tbghg

前言

整理自刘丹冰老师的《Easy 搞定 Golang设计模式》

说是整理,到最后发现基本都是文章中的内容:p

Go中的组合与继承

参考文章:go继承

go中没有继承,只能通过组合来实现继承

继承就是子类继承了父类的特征和行为,使得子类实例具有父类的行为和方法
组合就是通过对现有对象的拼装从而获得实现更为复杂的行为的方法

  • 一个struct嵌套了另外一个匿名的struct从而实现了继承,嵌套多个匿名struct实现多重继承。
  • 一个struct嵌套了另外一个struct的实例实现了组合。

示例出处:go结构体的“继承”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//Animal 动物
type Animal struct {
name string
}

func (a *Animal) move() {
fmt.Printf("%s会动!\n", a.name)
}

//Dog 狗
type Dog struct {
Feet int8
*Animal //通过嵌套匿名结构体实现继承
}

func (d *Dog) wang() {
fmt.Printf("%s会汪汪汪~\n", d.name)
}

func main() {
d1 := &Dog{
Feet: 4,
Animal: &Animal{ //注意嵌套的是结构体指针
name: "乐乐",
},
}
d1.wang() //乐乐会汪汪汪~
d1.move() //乐乐会动!
}
  1. go继承是通过嵌套匿名struct实现继承。
  2. go继承在本质上还是组合。
  3. 子类要调用父类的实现可以通过调用组合中的父类对象的方法。
  4. 多重继承中不允许多个父类出现相同的方法。(编译不通过)

设计模式概述

设计模式是在特定环境下人们解决某类重复出现问题的一套成功或有效的解决方案。

GoF给软件设计模式提供了定义,如下:“软件设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。”
一句大白话可以总结:“在一定环境下,用固定套路解决问题。”

GoF提出的设计模式有23个,包括:

  • 创建型(Creational)模式:如何创建对象;
  • 结构型(Structural )模式:如何实现类或对象的组合;
  • 行为型(Behavioral)模式:类或对象怎样交互以及怎样分配职责。

有一个“简单工厂模式”不属于GoF 23种设计模式,但大部分的设计模式书籍都会对它进行专门的介绍。
设计模式目前种类: GoF的23种 + “简单工厂模式” = 24种。

学习设计模式的作用:

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

设计模式的基础是:多态。

https://www.yuque.com/aceld/lfhu8y/pebesh?inner=qSWMX

image

image

image

image

面向对象设计原则

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

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

原则的目的: 高内聚,低耦合

名称 定义
单一职责原则 (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 ★★★☆☆ 一个对象应当对其他对象尽可能少的了解,从而降低各个对象之间的耦合,提高系统的可维护性。例如在一个程序中,各个模块之间相互调用时,通常会提供一个统一的接口来实现。这样其他模块不需要了解另外一个模块的内部实现细节,这样当一个模块内部的实现发生改变时,不会影响其他模块的使用。(黑盒原理)

单一职责原则

类的职责单一,对外只提供一种功能,而引起类变化的原因都应该只有一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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{})
}

依赖倒转原则

依赖于抽象(接口),不要依赖具体的实现(类),也就是针对接口编程

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

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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)
}

假设现在要增加一个 丰田汽车 或者 司机王五, 那么模块和模块的依赖关系将成指数级递增,想蜘蛛网一样越来越难维护和捋顺

面向抽象层依赖倒转image

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

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

我们就将这种的设计原则叫做 依赖倒转原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import "fmt"

//水果类
type Fruit struct {
//...
//...
//...
}

func (f *Fruit) Show(name string) {
if name == "apple" {
fmt.Println("我是苹果")
} else if name == "banana" {
fmt.Println("我是香蕉")
} else if name == "pear" {
fmt.Println("我是梨")
}
}

//创建一个Fruit对象
func NewFruit(name string) *Fruit {
fruit := new(Fruit)

if name == "apple" {
//创建apple逻辑
} else if name == "banana" {
//创建banana逻辑
} else if name == "pear" {
//创建pear逻辑
}

return fruit
}

func main() {
apple := NewFruit("apple")
apple.Show("apple")

banana := NewFruit("banana")
banana.Show("banana")

pear := NewFruit("pear")
pear.Show("pear")
}

业务逻辑层 —> 基础类模块

  • 在Fruit类中包含很多“if…else…”代码块,整个类的代码相当冗长,代码越长,阅读难度、维护难度和测试难度也越大;而且大量条件语句的存在还将影响系统的性能,程序在执行过程中需要做大量的条件判断
  • Fruit类的职责过重,它负责初始化和显示所有的水果对象,将各种水果对象的初始化代码和显示代码集中在一个类中实现,违反了“单一职责原则”,不利于类的重用和维护
  • 当需要增加新类型的水果时,必须修改Fruit类的构造函数NewFruit()和其他相关方法源代码,违反了“开闭原则”

在中间加一层工厂模块层,来降低业务逻辑层对基础模块层的直接依赖和耦合关联。

业务逻辑层 —> 工厂模块 —> 基础类模块

简单工厂模式角色和职责

简单工厂模式并不属于GoF的23种设计模式。他是开发者自发认为的一种非常简易的设计模式,其角色和职责如下:

  • 工厂:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
  • 抽象产品:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
  • 具体产品:简单工厂模式所创建的具体实例对象。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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("我是梨")
}

// ========= 工厂模块 =========
//一个工厂, 有一个生产水果的机器,返回一个抽象水果的指针
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()
}

优缺点

优点:

  • 实现了对象创建和使用的分离
  • 不需要记住具体类名,记住参数即可,减少使用者记忆量。

缺点:

  • 对工厂类职责过重,一旦不能工作,系统受到影响
  • 增加系统中类的个数,复杂度和理解度增加
  • 违反“开闭原则”,添加新产品需要修改工厂逻辑,工厂越来越复杂

适用场景

  • 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂
  • 客户端只知道传入工厂类的参数,对于如何创建对象并不关心

工厂方法模式

角色和职责

  • 抽象工厂:工厂方法模式的核心,任何工厂类都必须实现这个接口。
  • 工厂:具体工厂类是抽象工厂的一个实现,负责实例化产品对象。
  • 抽象产品:工厂方法模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
  • 具体产品:工厂方法模式所创建的具体实例对象。

简单工厂模式 + “开闭原则” = 工厂方法模式

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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 JapanApple struct {
Fruit
}

func (jp *JapanApple) 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
}

//具体的日本工厂
type JapanAppleFactory struct {
AbstractFactory
}

func (fac *JapanAppleFactory) CreateFruit() Fruit {
var fruit Fruit

//生产一个具体的日本苹果
fruit = new(JapanApple)

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:需要一个日本的苹果?
//1-先要一个具体的日本评估工厂
var japanAppleFac AbstractFactory
japanAppleFac = new(JapanAppleFactory)
//2-生产相对应的具体水果
var japanApple Fruit
japanApple = japanAppleFac.CreateFruit()

japanApple.Show()
}

优缺点

优点:

  1. 不需要记住具体类名,甚至连具体参数都不用记忆
  2. 实现了对象创建和使用的分离
  3. 系统的可扩展性也就变得非常好,无需修改接口和原类
  4. 对于新产品的创建,符合开闭原则

缺点:

  1. 增加系统中类的个数,复杂度和理解度增加
  2. 增加了系统的抽象性和理解难度

适用场景

  1. 客户端不知道它所需要的对象的类
  2. 抽象工厂类通过其子类来指定创建哪个对象

抽象工厂方法模式

工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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()
}

优缺点

优点:

  • 拥有工厂方法模式的优点
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象
  • 增加新的产品族很方便,无须修改已有系统,符合“开闭原则”

缺点:

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

使用场景

  • 系统中有多于一个的产品族。而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族
  • 产品等级结构稳定。设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构

类似的实现有:

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

单例模式

保证一个类永远只能有一个对象,且该对象的功能依然能被其他模块使用。

分为恶汉式、懒汉式

恶汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import "fmt"

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

/*
保证一个类永远只能有一个对象
*/

//1、保证这个类非公有化,外界不能通过这个类直接创建一个对象
// 那么这个类就应该变得非公有访问 类名称首字母要小写
type singelton struct {}

//2、但是还要有一个指针可以指向这个唯一对象,但是这个指针永远不能改变方向
// Golang中没有常指针概念,所以只能通过将这个指针私有化不让外部模块访问
var instance *singelton = new(singelton)

//3、如果全部为私有化,那么外部模块将永远无法访问到这个类和对象,
// 所以需要对外提供一个方法来获取这个唯一实例对象
// 注意:这个方法是否可以定义为singelton的一个成员方法呢?
// 答案是不能,因为如果为成员方法就必须要先访问对象、再访问函数
// 但是类和对象目前都已经私有化,外界无法访问,所以这个方法一定是一个全局普通函数
func GetInstance() *singelton {
return instance
}

func (s *singelton) SomeThing() {
fmt.Println("单例对象的某方法")
}

func main() {
// 以本包下的main访问举例,实际上别的包也可以调用
s := GetInstance()
s.SomeThing()
}

sync.once实现懒汉式的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"sync"
)

var once sync.Once

type singelton struct {}

var instance *singelton

func GetInstance() *singelton {

once.Do(func(){
instance = new(singelton)
})

return instance
}

func (s *singelton) SomeThing() {
fmt.Println("单例对象的某方法")
}

func main() {
s := GetInstance()
s.SomeThing()
}

优缺点

优点:

  • 单例模式提供了对唯一实例的受控访问
  • 节约系统资源(在系统内存中只存在一个对象)

缺点:

  • 扩展略难(单例模式中没有抽象层)
  • 单例类的职责过重

适用场景

  • 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象
  • 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例
 评论
评论插件加载失败
正在加载评论插件