程序猿,没有不知道设计模式的,但是很少有人拍着胸脯说:“I Got It ”。我从第一次面试起,就被challenge,你知道哪些设计模式?用过哪些?能说说它的原理吗?(但凡阿里面试,都会问,哈哈,我一般也会回问,你们平常用得到吗?)
设计模式,我已经看了三年了,《Design Pattern》看了30页(不推荐大家看了,这是本强者称奇,弱者骂娘的神书),《研磨设计模式》看了三遍,希望结合自己的感悟,对其做一番总结吧!
注1:在写总结时,偶然发现https://refactoringguru.cn/design-patterns这个神奇的网站,推荐给大家!!!
1 设计模式鸟瞰图
1.1 分类标准
创建型模式:提供创建对象的机制,增加已有代码的灵活性和可复用性
结构型模式:介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效
行为模式:负责对象间的高效沟通和职责委派
1.2 厂公的过去式
工厂模式的知名度,和单例模式并驾齐驱,在之前的《设计模式之工厂三剑客》中已经介绍过,在此不做赘述。作为一个gopher,只想多说一句,工厂方法不适合golang。
1.3 放弃的你:不是你不够好,是我爱不起
原型模式:该模式在前端(Javascript)中很重要,其本质是“克隆生成对象”,做数据拷贝之用。
享元模式:名字很唬人,其实就是多个对象使用相同的数据对象作为其属性,为了减少内存占用,将共用的数据对象以引用的方式使用。(个人困惑:这其实不就是指针引用吗?)
命令模式:你知道就好
解释器模式:自说自话,定义一种文法,定义一个解释器去读懂它!说实话,它很重要,比如golang的spf13/pflag就承担解释器的角色。只可惜,这种底层的轮子,我们不会去造,也造不来。
备忘录模式:对象的备忘录,就是对象的字段属性的拷贝另存,只不过基于业务场景,可能只保存部分字段属性。
1.4 国之重“器”
生成器 | 适配器 | 装饰器 | 迭代器 | 解释器 | |
定义 | 将一个复杂对象的构建与它的表示分离,是的同样的构建过程可以创建不同的表示 | 将一个类的接口转换为客户希望的另外一个接口。 | 动态地给一个对象添加一些额外的职责(功能) | 提供一种方法顺序访问一个聚合对象中的各个元素,而不需要暴露该对象的内部表示 | 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子 |
目的 | 用于构建复杂的产品,而且是细化的、分步骤的构建产品 | 适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以其一工作。 | 动态地为对象增加功能 | 提供对聚合对象的迭代访问 | 使用解释器对象来表示和处理相应的语法规则 |
本质 | 分离整体构建算法和部件构造 | 转换匹配,复用功能 | 动态组合 | 控制访问聚合对象中的元素 | 分离实现,解释执行 |
1.5 行者模式
中介者 | 观察者 | 访问者 | |
定义 | 用一个中介对象来封装一系列对象的交互 | 定义对象间的一种一对多的依赖关系。当一个对象的状态发生变化时,所有依赖于它的对象都能得到通知并被自动更新 | 表示一个作用域某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下,定义作用于这些元素的新操作 |
目的 | 统一管理一组关联对象,彼此之间不显示调用,使其耦合松散 | 统一管理依赖关系,建立联动机制 | 动态扩展已有类的功能 |
本质 | 封装交互 | 触发联动 | 预留通路,回调实现 |
1.6 “链”式结构
2 重点关注
2.1 单例模式:最简单,送分题
type DeployManager struct { TaskChan chan DeployTask Engine *options.Engine}var manager *DeployManagerconst size=1000func GetDeployManager(engine *options.Engine) *DeployManager { if manager == nil { manager = &DeployManager{ TaskChan: make(chan DeployTask, size), Engine: engine, } } return manager}
2.2 桥接模式:维度的划分与融合
2.2.1 功能代码
package bridgeimport "fmt"type printer interface { print()}type Mac struct { printer printer}func (m *Mac) setPrinter(p printer) { m.printer = p}func (m *Mac) print() { fmt.Print("Mac 打印,") m.printer.print()}type Windows struct { printer printer}func (w *Windows) setPrinter(p printer) { w.printer =p}func (w *Windows) print() { fmt.Print("Windows 打印,") w.printer.print()}type HPPrinter struct {}func (h *HPPrinter) print() { fmt.Println("惠普打印机!")}type CannonPrinter struct{}func (c *CannonPrinter) print() { fmt.Println("佳能打印机!")}
2.2.2 测试代码
package bridgeimport "testing"func TestBridge(t *testing.T) { mac := new(Mac) win := new(Windows) hp := new(HPPrinter) cannon := new(CannonPrinter) mac.setPrinter(hp) mac.print() mac.setPrinter(cannon) mac.print() win.setPrinter(hp) win.print() win.setPrinter(cannon) win.print()}
2.2.3 桥接模式讲解
与其说桥接模式的本质是“分离抽象和实现”,倒不如说是“维度切割”,以最小、最恰当的粒度自由组合!在发布系统中,发布介质有“容器、虚拟机”,发布模式有“滚动发布、蓝绿发布、灰度发布”,二者逻辑组合有六种方式,采用桥接模式,可以清晰权责,避免代码混乱融合!(让专业的人,做专业的事!)
2.3 装饰器模式:我最爱问你
只要你说你会python,我是一定会问装饰器的。前文已经讲过装饰器了,它在python中的@语法糖,让你可以轻松使用装饰器。
@a_new_decoratordef a_function_requiring_decoration(): """Hey you! Decorate me!""" print("I am the function which needs some decoration to " "remove my foul smell") a_function_requiring_decoration()#outputs: I am doing some boring work before executing a_func()# I am the function which needs some decoration to remove my foul smell# I am doing some boring work after executing a_func() #the @a_new_decorator is just a short way of saying:a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
设计模式,是一种思维方式,并不拘泥于形式和语言。在golang中,我们通常借助于defer来做装饰器:
func (f *Filter) StaticFuncCost(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { start := time.Now() defer func() { cost := fmt.Sprint(time.Since(start).Milliseconds()) logs.Info("Request:", req.Attribute(cv.RequestID), ",开始时间:", timeconv.Format(start), ",总耗时:", cost, "ms") }() chain.ProcessFilter(req, resp)}
装饰器有什么好处?AOP,面向切面编程,做一些与业务逻辑无关的专项功能,如日志、安全、性能统计等等!
2.4 观察者模式:Observer与Watcher
2.4.1 基础知识
2.4.2 结构图
2.4.3 时序图
2.4.4 Show Me Code
应用部署器,部署后需要通知Tester自动化测试、Securitier做安全扫描、通知研发做部署验证。
package observerimport "fmt"type Observer interface { update(string) getID() string}type Subject interface { register(obs Observer) deregister(obs Observer) notifyAll()}type Deployer struct { DeployId string ObserverMap map[string]Observer}func (d *Deployer) register(obs Observer) { d.ObserverMap[obs.getID()] = obs}func (d *Deployer) deregister(obs Observer) { delete(d.ObserverMap, obs.getID())}func (d *Deployer) notifyAll() { for _, obs := range d.ObserverMap{ obs.update(d.DeployId) }}func NewDeployer(id string) *Deployer { return &Deployer{ DeployId: id, ObserverMap: map[string]Observer{}, }}type Tester struct { id string}func (t *Tester) getID() string { return t.id}func (t *Tester) update(d string) { fmt.Printf("部署(ID:%s)完成,开始执行测试任务:%s\n", d, t.id)}type Securitier struct{ id string}func (t *Securitier) getID() string { return t.id}func (t *Securitier) update(d string) { fmt.Printf("部署(ID:%s)完成,开始执行安全扫描任务:%s\n", d, t.id)}type Developers struct { id string}func (t *Developers) getID() string { return t.id}func (t *Developers) update(d string) { fmt.Printf("部署(ID:%s)完成,请%s研发组同学验证\n", d, t.id)}
业务功能都不是孤立的,必有其上下游,观察者模式通过观察注册的方式集中管理起来,避免硬编码联动关系,松散耦合,也让代码更专注于其本身的业务逻辑!
2.5 访问者模式:来自K8S的爱
K8S代码里大规模的使用Visitor,在我看来visitor是将个性化、定制化的功能交给使用者,作为visitor服务商只需要做最底层的基础的服务功能,通常这类功能是单一的。
// URLVisitor downloads the contents of a URL, and if successful, returns// an info object representing the downloaded object.type URLVisitor struct { URL *url.URL *StreamVisitor HttpAttemptCount int}func (v *URLVisitor) Visit(fn VisitorFunc) error { body, err := readHttpWithRetries(httpgetImpl, time.Second, v.URL.String(), v.HttpAttemptCount) if err != nil { return err } defer body.Close() v.StreamVisitor.Reader = body return v.StreamVisitor.Visit(fn)}
参考文献:https://cloud.tencent.com/developer/article/1052415
3 我心憔悴,如何慰藉
23种设计模式,看完了,也就忘完了!我辈该如何?
3.1 说文解字:识名,解意,不必苛求结构
好的命名,其义自见,无需过多解释;对于设计模式的类结构关系,也未必要完全遵守,把握其灵魂即可。
3.2 目的,目的,目的,只有知其为何,才能够何为!
学习设计模式,经常会被设计模式的结构绕来绕去就绕晕了。其实,以目的为导向,知道该模式是解决什么问题,怎么做会最好。因此,无需背诵枯燥的记忆,带着问题、带着场景即可学会!
3.3 实践出真知,将设计模式套入你的功能场景中
看懂了,不是真的懂,要能自己写出来,要将设计模式应用到日常的功能开发与系统设计中,只有用,才能真正体味其原理和思想。
3.4 设计模式是思维,是太极,重意、不重形
初级阶段,要照葫芦画瓢,学习设计模式的结构;高级阶段,要掌握其思想和灵魂,这样就可以根据场景合理的设计结构,并非生搬硬套。
3.5 设计模式本质一览图