前四章讲解了Go设计模式的一些原理性知识,后面会开始讲23种设计模式。初步计划是对每种模式至少要说明含义、画类图、找实际用例、写代码实现、描述重点信息(使用场景、原因、好处等)。
可能大家也看过很多设计模式相关的资料,但看完后或许有似懂非懂的感觉,我想一是因为资料中讲的例子不是特别贴合实际,另一点是因为没有真正的去实现一次代码。
所以希望经过写设计模式系列,我自己的设计模式的理解能够更加深入一些。
因为要画类图,这部分知识还是本科时候学的,大家可能也记不清了,这次就通过一个例子,把UML类图中的基本元素展示给大家,并讲一些名词定义,最后用代码实现,这样可以方便后面的讲解。
类图
此处用一个简单的例子,将类图中的各个元素表示出来。
https://www.processon.com/view/link/605be4175653bb2225e7c43f
名词解释
依赖关系
定义
Dependency(依赖关系)表现为函数中的参数。是类与类之间的连接,表示一个类依赖于另一个类的定义,其中一个类的变化将影响另外一个类。例如如果A依赖于B,则B体现为局部变量,方法的参数、或静态方法的调用。
表示法
实例说明
动物依赖于氧气和水,我们可以看到动物有新陈代谢函数,其入参为氧气和水,所以动物与氧气和水是依赖关系。
泛化关系
定义
Generalization(泛化)表现为继承或实现关系(is a)。具体形式为类与类之间的继承关系,接口与接口之间的继承关系,类对接口的实现关系。
即泛化关系包含继承关系和实现关系。
表示法
实例说明
鸟是动物,所以继承自动物,大雁、鸭、企鹅也是鸟,所以继承自鸟。继承的一个重要作用是复用,动物->鸟->(大雁、鸭、企鹅)都是is a的关系。不过Go没有继承关系,继承用组合来实现。
大雁实现了飞翔接口、唐老鸭实现了讲人话接口。接口的一个主要作用是确定标准,实现了相同接口的类可以相互替换。所以并不是特别重于is a的关系。
关联关系
定义
Association关联关系表现为变量(has a )。类与类之间的联接,它使一个类知道另一个类的属性和方法。例如如果A依赖于B,则B体现为A的全局变量。关联关系有双向关联和单向关联。双向关联:两个类都知道另一个类的公共属性和操作。单向关联:只有一个类知道另外一个类的公共属性和操作。大多数关联应该是单向的,单向关系更容易建立和维护,有助于寻找可服用的类。
表示法
实例说明
企鹅需要知道天气,一般是企鹅类中会有一个气候对象,两个类也是同一层次上的。
聚合关系
定义
Aggregation(聚合关系) 是关联关系的一种,是强的关联关系。聚合关系是整体和个体的关系。普通关联关系的两个类处于同一层次上,而聚合关系的两个类处于不同的层次,一个是整体,一个是部分。同时,是一种弱的“拥有”关系。体现的是A对象可以包含B对象,但B对象不是A对象的组成部分。具体表现为,如果A由B聚合成,表现为A包含有B的全局对象,但是B对象可以不在A创建的时刻创建。
表示法
实例说明
雁群是由一堆大雁组成的,两者是不同的层次,大雁是雁群的组成部分。
合成(组合关系)
定义
Composition(组合关系)是关联关系的一种,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。Composition(组合关系)是一种强的“拥有”关系,体现了严格的部分和整体的关系,部分和整体的生命周期一致。如果A由B组成,表现为A包含有B的全局对象,并且B对象在A创建的时刻创建。
表示法
实例说明
翅膀是鸟的一部分,在生成鸟的时候翅膀需要一起生成,两者生命周期一样。
PS:对于依赖、泛化、关联、聚合、组合关系,其中组合、聚合、关联较为相似,关系强度上组合>聚合>关联,如果大家判断关系不属于依赖和泛化,那可以先看看两者是否是整体和部分、有相同生命周期,按照组合、聚合、关联的顺序过一遍,就能找到合适的关系了。
代码实现
上面说了一堆理论,我们还是用代码实现以下这个类图,这样对自己、对大家的帮助更大。Go没有继承,继承用组合实现。不过倒是有类对接口的实现。相关代码Go设计模式(1)-语法在这篇文章里也写过。
package main
import (
"fmt"
"strconv"
)
type Water struct {
weight int64
}
type Animal struct {
name string
}
/**
* @Description: 动物成长
* @receiver animal
* @param water
*/
func (animal *Animal) Grow(water *Water) {
weight := strconv.FormatInt(water.weight, 10)
fmt.Printf("长大需要喝%s吨水\n", weight)
}
type Wing struct {
}
/**
* @Description: 鸟类,伪继承Animal,组合Wing,有颜色成员变量
*/
type Bird struct {
Animal
featherCol string
wing *Wing
}
/**
* @Description:鸟下蛋
* @receiver bird
*/
func (bird *Bird) LayEggs() {
fmt.Println(bird.featherCol + "鸟下蛋了哈")
}
/**
* @Description:飞接口
*/
type Fly interface {
fly()
}
/**
* @Description:大雁接口,伪继承鸟,并实现Fly接口
*/
type Goose struct {
Bird
}
func (goose *Goose) fly() {
fmt.Println("大雁可以飞")
}
/**
* @Description:雁群聚合大雁
*/
type Geese struct {
geese []*Goose
}
/**
* @Description: 雁群的飞行方式
* @receiver g
*/
func (g *Geese) flyV() {
fmt.Printf("雁群V形飞行,共有%d只大雁\n", len(g.geese))
}
/**
* @Description:鸭伪继承鸟
*/
type Duck struct {
Bird
}
/**
* @Description:说话接口
*/
type Speak interface {
Speak()
}
/**
* @Description:唐老鸭伪继承鸭子
*/
type DonaldDuck struct {
Duck
}
/**
* @Description:唐老鸭实现说话接口
* @receiver donaldDuck
*/
func (donaldDuck *DonaldDuck) Speak() {
fmt.Println("唐老鸭说人话了")
}
/**
* @Description:企鹅伪继承鸟,并关联天气
*/
type Penguin struct {
Bird
climate *Climate
}
/**
* @Description:企鹅重载了下蛋接口
* @receiver p
*/
func (p *Penguin) LayEggs() {
fmt.Printf("企鹅在%s的天气下下蛋了\n", p.climate.content)
}
/**
* @Description:天气类,有天气内容成员变量
*/
type Climate struct {
content string
}
func main() {
water := &Water{weight: 10}
//动物
fmt.Println("-----------动物篇-依赖关系")
animal := &Animal{}
animal.Grow(water)
//鸟
fmt.Println("-----------鸟篇-伪继承/组合关系")
bird := &Bird{
featherCol: "五彩斑斓的",
wing: &Wing{},
}
bird.Grow(water)
bird.LayEggs()
//大雁
fmt.Println("-----------大雁篇-实现关系")
goose := &Goose{}
goose.featherCol = "黑黑的"
goose.Grow(water)
goose.LayEggs()
goose.fly()
//鸭
fmt.Println("-----------鸭篇-伪继承关系")
duck := &Duck{}
duck.featherCol = "黄色的"
duck.Grow(water)
duck.LayEggs()
//企鹅
fmt.Println("-----------企鹅篇-伪继承/关联关系")
penguin := &Penguin{}
penguin.featherCol = "白色的"
penguin.Grow(water)
climate := &Climate{content: "寒冷的"}
penguin.climate = climate
penguin.LayEggs()
//雁群
fmt.Println("-----------雁群篇-聚合关系")
g := &Geese{}
g.geese = append(g.geese, goose)
g.flyV()
//唐老鸭
fmt.Println("-----------唐老鸭篇-实现关系")
donaldDuck := &DonaldDuck{}
donaldDuck.featherCol = "红色的"
donaldDuck.Grow(water)
donaldDuck.LayEggs()
donaldDuck.Speak()
}
输出:
➜ myproject go run main.go
—————-动物篇-依赖关系
长大需要喝10吨水
—————-鸟篇-伪继承/组合关系
长大需要喝10吨水
五彩斑斓的鸟下蛋了哈
—————-大雁篇-实现关系
长大需要喝10吨水
黑黑的鸟下蛋了哈
大雁可以飞
—————-鸭篇-伪继承关系
长大需要喝10吨水
黄色的鸟下蛋了哈
—————-企鹅篇-伪继承/关联关系
长大需要喝10吨水
企鹅在寒冷的的天气下下蛋了
—————-雁群篇-聚合关系
雁群V形飞行,共有1只大雁
—————-唐老鸭篇-实现关系
长大需要喝10吨水
红色的鸟下蛋了哈
唐老鸭说人话了
虽然这只是一个简单的示例,但是也能阐明很多知识点
- 可以看到Go使用匿名组合实现了伪继承的效果,对于Grow函数可以复用
- 匿名组合使用的不是指针类型,基类采用指针方式的组合,依然具有派生的效果,只是派生类创建实例的时候需要外部提供一个基类实例的指针
- 大雁和唐老鸭分别实现了飞翔和说话接口,如果有其它类也实现了接口,大雁和唐老鸭可以对其进行替换
- 关于泛化(伪继承/实现)、依赖、组合、聚合、关联,代码中对这些细节都有讲述
代码位置:https://github.com/shidawuhen/asap/blob/master/controller/design/5uml.go
总结
虽然工作任务重、实际工作中用到的时候比较少,但是对这些知识有一个了解,会在未来的某刻帮助解决很多问题。如果大家喜欢这篇文章,请点赞转发。