设计模式其实和语言关系不大,但是在项目工程的设计中有着很大的作用,这边使用golang实现相关的设计模式,也算是对过去看过用过的设计模式的回顾和总结。
设计模式的六大原则
1、开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。 所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何 基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受 到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。 实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽 象化的具体步骤的规范。
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出, 其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
5、迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
类之间耦合越松,越有利于复用,一个处于弱耦合的类被修改,不会对有关系的类造成波及。
6、合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
合成是强拥有关系,体现了部分和整体的关系。
聚合是弱拥有关系,体现了个体和群体的关系。
优先使用合成/聚合原则有利于后面的类封装,使得类和继承保持较小规模。
上面的六大规则在我们编程设计过程中整体上体现:解耦,抽象,封装,可复用可扩展。
设计模式
正常23种设计模式,下面分的更加详细。
创建型模式
- 抽象工厂模式:提供一个接口用于创建相关对象的家族;
- Builder模式:使用简单的对象来构建复杂的对象;
- 工厂方法模式:一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中;
- 对象池模式:实例化并维护一组相同类型的对象实例;
- 单例模式:限制类的实例,保证一个类只有一个实例。
结构模式
- 适配器模式:适配另一个不兼容的接口来一起工作;
- 桥接模式:将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化;
- 合成模式:将对象组织到树中,用来描述树的关系;
- 装饰模式:给一个静态或动态对象添加行为;
- 门面(Facade)模式:为子系统中的各类(或结构与方法)提供一个简明一致的界面,隐藏子系统的复杂性,使子系统更加容易使用;
- Flyweight模式:运用共享技术有效地支持大量细粒度的对象;
- MVC模式:是模型(model)-视图(view)-控制器(controller)的缩写,将一个应用程序划分成三个相互关联的部分,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
- 代理模式:为其他对象提供一种代理以控制对这个对象的访问。
行为模式
- 责任链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系;
- 命令模式:就是客户端发布一个命令(也就是“请求”),而这个命令已经被封装成一个对象。即这个命令对象的内部可能已经指定了该命令具体由谁负责执行;
- 中介(Mediator)模式:用一个中介对象来封装一系列关于对象交互行为;
- 观察者模式:对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新;
- 注册(Registry)模式:跟踪给定类的所有子类;
- 状态模式:基于一个对象的内部状态,给相同对象提供多种行为;
- 策略模式:定义一系列算法,并将每一个算法封装起来,而且使它们可以相互替换;
- 模板(Template)模式:定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤;
- 访问者模式:表示一个作用于某对象结构中的各元素的操作,它使开发者可以在不改变各元素类的前提下定义作用于这些元素的新操作。
- 同步模式
- 条件变量:利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待”条件变量的条件成立”而挂起;另一个线程使”条件成立”(给出条件成立信号);
- Lock/Mutex:执行互斥限制资源获得独占访问;
- 监视器模式:互斥锁和条件变量的组合模式;
- 读写锁定模式:它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作;
- Semaphore:负责协调各个线程,以保证它们能够正确、合理地使用公共资源。
并行模式
- Bounded Parallelism:完成大量资源限制的独立任务;
- 广播(Broadcast):把一个消息同时传输到所有接收端;
- 协同(Coroutines):允许在特定地方暂停和继续执行的子程序;
- 生成器:一次性生成一系列值;
- Reactor模式:在事件驱动的应用中,将一个或多个客户的服务请求分离(demultiplex)和调度(dispatch)给应用程序。同步、有序地处理同时接收的多个服务请求。
- 并行(Parallelism):完成大量的独立任务;
- 生产者消费者:从任务执行中分离任务;
- 调度器(Scheduler):协调任务步骤。
消息传递模式
- 扇入(Fan-In):该模块直接调用上级模块的个数,像漏斗型一样去工作;
- 扇出(Fan-Out):该模块直接调用的下级模块的个数;
- Futures & Promises:扮演一个占位角色,对未知的结果用于同步;
- Publish/Subscribe:将信息传递给订阅者;
- Push & Pull:把一个管道上的消息分发给多人。
稳定模式
- Bulkheads:实施故障遏制原则,例如防止级联故障;
- 断路器(Circuit-Breaker)模式:当请求有可能失败时,停止流动的请求;
- 截止日期(Deadline):一旦响应变缓,允许客户端停止一个正在等待的响应;
- Fail-Fast机制:集合的一种错误检测机制。当多个线程对集合进行结构上的改变操作时,有可能会产生fail-fast机制;
- Handshaking:如果一个组件的不能访问请求被拒绝,询问是否还能承担更多负载;
- 稳定状态(Steady-State):为每一个服务积累一个资源,其它服务必须回收这些资源;
剖析模式
- Timing Functions:包装和执行日志的函数;
- Functional Options:允许给默认值创建clean API和惯用重载;
反模式
- 级联故障:一个系统的某部分出现错误,与之有关的上下级也随之出现故障,导致多米诺效应。
具体讲解
具体讲解一下重要的设计模式
策略模式(Strategy)
介绍
策略模式: 将算法或操作抽象成实现共同接口、可以被替换的类,实现逻辑和具体算法的解耦。
将各种行为抽象成算法,封装算法为对象;
算法实现共同接口,调用者调用时不考虑算法具体实现,调用接口方法即可;
调用者可随时替换此算法对象;
场景
多个方法择一使用,且他们会被随时替换;
方法没有共性,使用继承会有大量重写,使用接口会有大量重复使用;
实现
两个算法: 冒泡排序和快速排序;
抽象冒泡排序和快速排序为算法对象,实现算法接口,拥有 used() 被使用方法;
计算器计算时不用理会是什么算法,调用 used() 即可;
观察者模式(Observer)
介绍
观察者模式:主题主动向观察者推送变化,解决观察者对主题对象的依赖。
观察者实现被通知接口,并在主题上注册,主题只保存观察者的引用,不关心观察者的实现;
在主题有变化时调用观察者的通知接口来通知已注册的观察者;
通知方式有推(主题变化时将变化数据推送给观察者)和拉(主题只告知变化,观察者主动来拉取数据);
场景
一个主题,多个观察者,主题的任何变动,观察者都要第一时刻得到;
观察者获取主题变化困难,定时不及时,轮询消耗大;
观察者可以随时停止关注某主题;
实现
张三和李四是记者,他们需要及时了解城市发生的新闻;
张三和李四在电视台注册了自己的信息;
城市发生了新闻,电视台遍历注册信息,通知了张三和李四;
李四退休了,在电视台注销了自己的信息;
城市又发生了新闻,电视台只通知了张三;
装饰者模式(Decorator)
介绍
装饰者模式:包装一个对象,在被装饰对象的基础上添加功能;
装饰者与被装饰对象拥有同一个超类,装饰者拥有被装饰对象的所有外部接口,可被调用,外界无法感知调用的是装饰者还是被装饰者;
装饰者需要被装饰者作为参数传入,并在装饰者内部,在被装饰者实现的基础上添加或修改某些功能后,提供同被装饰者一样的接口;
装饰者也可被另一个装饰者装饰,即嵌套装饰;
装饰者是一群包装类,由于装饰的复杂性,会多出很多个装饰者小类;
场景
对象需要动态地添加和修改功能;
功能改变后不影响原对象的使用;
实现
在商店内,花作为被装饰者对象、红丝带和盒子作为花的装饰者;
花、红丝带、盒子有共同的超类“商品”,他们都能被卖掉;
我们可以在红丝带装饰过花后,再用盒子再包装一次;
包装后的花,顾客买时也不会受到任何影响;
工厂模式(Factory)
介绍
工厂模式: 顾名思义,工厂模式是对象的生产器,解耦用户对具体对象的依赖。
实现依赖倒置,让用户通过一个产品工厂依赖产品的抽象,而不是一个具体的产品;
简单工厂模式:接收参数并根据参数创建对应类,将对象的实例化和具体使用解耦;
抽象工厂模式:将工厂抽象出多个生产接口,不同类型的工厂调用生产接口时,生产不同类型的对象;
简单工厂常配合抽象工厂一起使用;
场景
根据不同条件需求不同的对象;
对象实例化的代码经常需要修改;
实现
简单工厂:向鞋厂内传入不同的类型(布制),鞋厂会生产出不同类型的鞋子(布鞋);
抽象工厂:有两座鞋厂:李宁鞋厂、Adidas鞋厂,他们能生产对应各自品牌的鞋子;
搭配使用:向不同的抽象工厂(李宁)传入不同的类型(运动类型),会生产出对应品牌对应类型的鞋子(李宁运动鞋);
单例模式(Singleton)
介绍
单例模式:保证同一个类全局只有一个实例对象;
在第一次实例化后会使用静态变量保存实例,后续全局使用此静态变量;
一般将构造方法私有化,构造方法添加 final 关键字无法被重写,添加一个类静态方法用于返回此实例;
在多线程时应该考虑并发问题,防止两次调用都被判定为实例未初始化而重复初始化对象;
场景
全局共享同一个实例对象(数据库连接等);
某一处对此对象的更新全局可见;
实现
利用 Go 中包的可见性规则来隐藏对象的实例化权限;
使用包变量保存实例对象,获取实例时判断是否已实例化,如为nil,实例化对象并返回,如有值,直接返回值;
待用锁实现 Go routine 并发时的问题;
命令模式(Command)
介绍
命令模式:将一个命令封装成对象,解耦命令的发起者和执行者。
命令对象实现命令接口(excute[、undo]),命令发起者实例化命令对象,并传递此对象,并不关心此对象由谁执行;
命令执行者只负责调用命令对象的执行方法即可,不关心对象是由谁生成的;
与策略模式不同之处:策略模式是通过不同的算法做同一件事情(例如排序),而命令模式则是通过不同的命令做不同的事情;
场景
命令发起者与执行者无法直接接触;
命令需要撤销功能,却不易保存命令执行状态信息时;
实现
指挥官创建了一个“从树下跑到草地上”的命令;
命令被分配给张三执行,张三作为军人,接到命令后不管命令的具体内容,而是直接调用命令的执行接口执行;
指挥官发布了撤销指令,张三又从草地上跑到了树下;
适配器模式(Adapter)
介绍
适配器模式:包装对象提供一个接口,以适配调用者。
适配器通过一个中间对象,封装目标接口以适应调用者调用;
调用者调用此适配器,以达到调用目标接口的目的;
适配器模式与装饰者模式的不同之处:适配器模式不改变接口的功能,而装饰者会添加或修改原接口功能;
场景
提供的接口与调用者调用的其他的接口都不一致;
为一个特殊接口修改调用者的调用方式得不偿失;
实现
张三是个正常人,他能通过说话直接地表达自己;
李四是个聋哑人,他没法直接表达自己,但他会写字;
笔记本作为一个适配器,用笔记本“包装”了李四之后,当李四需要表达自己想法时,调用笔记本的“表达”功能,笔记本再调用李四“写字”的方法;
外观模式(Facade)
介绍
外观模式:通过封装多个复杂的接口来提供一个简化接口来实现一个复杂功能。
外观模式是通过封装多个接口来将接口简单化;
外观模式不会改变原有的多个复杂的单一接口,这些接口依然能被单独调用,只是提供了一个额外的接口;
外观模式与适配器模式的不同之处:外观模式是整合多个接口并添加一个简化接口,适配器是适配一个接口;
场景
实现某一功能需要调用多个复杂接口;
经常需要实现此功能;
实现
正常的冲咖啡步骤是:磨咖啡豆、烧开水、倒开水搅拌咖啡。
我们经常需要直接冲咖啡,而不是使用单一步骤,每次喝咖啡时调用三个方法很麻烦;
封装三个接口,额外提供一个 “冲咖啡” 的方法,需要喝咖啡时只需要调用一次冲咖啡方法即可;
小结
《Head First 设计模式》这书真心不错,例子很轻松,给人很多时间和空间来思考,同时介绍模式时使用结合故事,层层深入的方法,让人印象很深刻,推荐。
书中详细介绍了 14 个基础设计模式,还有 9 个简化版,就自己查资料结合自己的理解来总结了。
实践
工厂模式–解耦和面向对象
WIKI:
In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created.
百度百科:
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。
简单工厂模式是通过传递不同的参数生成不同的实例,缺点就是扩展不同的类别时需要修改代码。
工厂方法模式为每一个product提供一个工程类,通过不同工厂创建不同实例。
实现实例
1.首先,我们定义一个计算的接口
package calc
type CalcSuper interface {
SetData(data ...interface{})
CalcOperate() float64
}
2.接下来,我们实现这个类的两个子类,分别是加法和减法
加法,就是用两个数来相加
package calc
import "fmt"
type Add struct {
Num1 float64
Num2 float64
}
func NewAdd() *Add {
instance := new(Add)
return instance
}
func (a *Add) SetData(data ...interface{}) {
if len(data) != 2 {
fmt.Println("error,Need two parameters ")
return
}
if _, ok := data[0].(float64); !ok {
fmt.Println("error,Need float64 parameters ")
return
}
if _, ok := data[1].(float64); !ok {
fmt.Println("error,Need float64 parameters ")
return
}
a.Num1 = data[0].(float64)
a.Num2 = data[1].(float64)
}
func (a Add) CalcOperate() float64 {
return a.Num1 + a.Num2
}
减法,就是把两个数相减,我感觉我好冷。。。
package calc
import "fmt"
type Subtraction struct {
Num1 float64
Num2 float64
}
func NewSubtraction() *Subtraction {
instance := new(Subtraction)
return instance
}
func (a *Subtraction) SetData(data ...interface{}) {
if len(data) != 2 {
fmt.Println("error,Need two parameters ")
return
}
if _, ok := data[0].(float64); !ok {
fmt.Println("error,Need float64 parameters ")
return
}
if _, ok := data[1].(float64); !ok {
fmt.Println("error,Need float64 parameters ")
return
}
a.Num1 = data[0].(float64)
a.Num2 = data[1].(float64)
}
func (a Subtraction) CalcOperate() float64 {
return a.Num1 - a.Num2
}
3.下面到了大功告成的时候了,定义简易工厂,来实例化这两个类
package calc
type CalcFactory struct {
}
func NewCalcFactory() *CalcFactory {
instance := new(CalcFactory)
return instance
}
func (f CalcFactory) CreateOperate(opType string) CalcSuper {
var op CalcSuper
switch opType {
case "+":
op = NewAdd()
case "-":
op = NewSubtraction()
default:
panic("error ! dont has this operate")
}
return op
}
在这个简易工厂,我们只传入相应的运算方式,如“+”,“-”,用来创建相关的运算策略。它会返回一个运算接口的实例,当我们得到这个实例,就能调用里面的方法进行运算了。
4.测试
// 简易工厂模式 project main.go
package main
import (
. "calc"
"fmt"
)
func main() {
factory := NewCalcFactory()
op := factory.CreateOperate("+")
op.SetData(1.0, 2.0)
fmt.Println(op.CalcOperate())
op = factory.CreateOperate("-")
op.SetData(1.0, 2.0)
fmt.Println(op.CalcOperate())
/*
输出:3
-1
*/
}
策略模式Strategy:定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化。
将多种算法进行封装,只暴露给外界固定数目的通用算法接口,这样就可以在后续的工作中在算法内部进行维护和扩展,避免更改用户端的代码实现,从而减小代码维护的成本,降低模块间的耦合程度。
代码实现
下面我们就开始以代码的形式来展示一下策略模式吧,代码很简单,我们用一个加减乘除法来模拟。
首先,我们看到的将会是策略接口和一系列的策略,这些策略不要依赖高层模块的实现。
package strategy
/**
* 策略接口
*/
type Strategier interface {
Compute(num1, num2 int) int
}
很简单的一个接口,定义了一个方法Compute,接受两个参数,返回一个int类型的值,很容易理解,我们要实现的策略将会将两个参数的计算值返回。 接下来,我们来看一个我们实现的策略,
package strategy
import "fmt"
type Division struct {}
func (p Division) Compute(num1, num2 int) int {
defer func() {
if f := recover(); f != nil {
fmt.Println(f)
return
}
}()
if num2 == 0 {
panic("num2 must not be 0!")
}
return num1 / num2
}
为什么要拿除法作为代表呢?因为除法特殊嘛,被除数不能为0,其他的加减乘基本都是一行代码搞定,除法我们需要判断被除数是否为0,如果是0则直接抛出异常。 ok,基本的策略定义好了,我们还需要一个工厂方法,根据不用的type来返回不同的策略,这个type我们准备从命令好输入。
func NewStrategy(t string) (res Strategier) {
switch t {
case "s": // 减法
res = Subtraction{}
case "m": // 乘法
res = Multiplication{}
case "d": // 除法
res = Division{}
case "a": // 加法
fallthrough
default:
res = Addition{}
}
return
}
这个工厂方法会根据不用的类型来返回不同的策略实现,当然,哪天我们需要新增新的策略,我们只需要在这个函数中增加对应的类型判断就ok。
现在策略貌似已经完成了,接下来我们来看看主流程代码,一个Computer,
package compute
import (
"fmt"
s "../strategy"
)
type Computer struct {
Num1, Num2 int
strate s.Strategier
}
func (p *Computer) SetStrategy(strate s.Strategier) {
p.strate = strate
}
func (p Computer) Do() int {
defer func() {
if f := recover(); f != nil {
fmt.Println(f)
}
}()
if p.strate == nil {
panic("Strategier is null")
}
return p.strate.Compute(p.Num1, p.Num2)
}
这个Computer中有三个参数,Num1和Num2当然是我们要操作的数了,strate是我们要设置的策略,可能是上面介绍的Division,也有可能是其他的,在main函数中我们会调用SetStrategy方法来设置要使用的策略,Do方法会执行运算,最后返回运算的结果,可以看到在Do中我们将计算的功能委托给了Strategier。
貌似一切准备就绪,我们就来编写main的代码吧。
package main
import (
"fmt"
"flag"
c "./computer"
s "./strategy"
)
var stra *string = flag.String("type", "a", "input the strategy")
var num1 *int = flag.Int("num1", 1, "input num1")
var num2 *int = flag.Int("num2", 1, "input num2")
func init() {
flag.Parse()
}
func main() {
com := c.Computer{Num1: *num1, Num2: *num2}
strate := s.NewStrategy(*stra)
com.SetStrategy(strate)
fmt.Println(com.Do())
}
首先我们要从命令行读取要使用的策略类型和两个操作数,在main函数中,我们初始化Computer这个结构体,并将输入的操作数赋值给Computer的Num1和Num2,接下来我们根据策略类型通过调用NewStrategy函数来获取一个策略,并调用Computer的SetStrategy方法给Computer设置上面获取到的策略,最后执行Do方法计算结果,最后打印。
感觉策略就是对工厂的上一层封装,只保留对外的一个基本接口和数据结构
单一职责
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
这个解释足够简单。说白了就是假如我们希望我们在我们的系统中该类仅仅存在1个或0个该类的实例。虽然单例模式很简单,但是熟悉java的同学可能了解,单例模式有很多写法,懒汉式、饿汉式、双重锁。。。 这么多形式,难道有什么目的?确实,不过他们的目的很明确,就是保证在一种特殊情况下的单例-并发。
ok,既然了解了单例模式,那下面我们就开始用代码描述一下单例模式。首先是最简单的单例,这里我们并不去考虑并发的情况。
package manager
import (
"fmt"
)
var m *Manager
func GetInstance() *Manager {
if m == nil {
m = &Manager {}
}
return m
}
type Manager struct {}
func (p Manager) Manage() {
fmt.Println("manage...")
}
这就是一个最简单的单例了,对于Manager结构体,我们提供了一个GetInstance函数去获取它的实例,这个函数中首先去判断m变量是否为空,如果为空才去赋值一个Manager的指针类型的值,一个小小的判断,就保证了我们在第第二次调用GetInstance的时候直接返回m,而不是重新获取Manager的实例,进而保证了唯一实例。
上面的代码确实简单,也实现了最简单的单例模式,不过大家有没有考虑到并发这一点,在并发的情况下,这里是不是还可以正常工作呢? 来,先跟着下面的思路走一走,来看看问题出现在哪。
现在我们是在并发的情况下去调用的 GetInstance函数,现在恰好第一个goroutine执行到m = &Manager {}这句话之前,第二个goroutine也来获取实例了,第二个goroutine去判断m是不是nil,因为m = &Manager{}还没有来得及执行,所以m肯定是nil,现在出现的问题就是if中的语句可能会执行两遍!
在上面介绍的这种情形中,因为m = &Manager{}可能会执行多次,所以我们写的单例失效了,这个时候我们就该考虑为我们的单例加锁啦。
这个时候我们就需要引入go的锁机制-sync.Mutex了,修改我们的代码,
package manager
import (
"sync"
"fmt"
)
var m *Manager
var lock *sync.Mutex = &sync.Mutex {}
func GetInstance() *Manager {
lock.Lock()
defer lock.Unlock()
if m == nil {
m = &Manager {}
}
return m
}
type Manager struct {}
func (p Manager) Manage() {
fmt.Println("manage...")
}
代码做了简单的修改了,引入了锁的机制,在GetInstance函数中,每次调用我们都会上一把锁,保证只有一个goroutine执行它,这个时候并发的问题就解决了。不过现在不管什么情况下都会上一把锁,而且加锁的代价是很大的,有没有办法继续对我们的代码进行进一步的优化呢? 熟悉java的同学可能早就想到了双重的概念,没错,在go中我们也可以使用双重锁机制来提高效率。
package manager
import (
"sync"
"fmt"
)
var m *Manager
var lock *sync.Mutex = &sync.Mutex {}
func GetInstance() *Manager {
if m == nil {
lock.Lock()
defer lock.Unlock()
if m == nil {
m = &Manager {}
}
}
return m
}
type Manager struct {}
func (p Manager) Manage() {
fmt.Println("manage...")
}
代码只是稍作修改而已,不过我们用了两个判断,而且我们将同步锁放在了条件判断之后,这样做就避免了每次调用都加锁,提高了代码的执行效率。
这获取就是很完美的单例代码了,不过还没完,在go中我们还有更优雅的方式去实现。单例的目的是啥?保证实例化的代码只执行一次,在go中就中这么一种机制来保证代码只执行一次,而且不需要我们手工去加锁解锁。对,就是我们的sync.Once,它有一个Do方法,在它中的函数go会只保证仅仅调用一次!再次修改我们的代码,
package manager
import (
"sync"
"fmt"
)
var m *Manager
var once sync.Once
func GetInstance() *Manager {
once.Do(func() {
m = &Manager {}
})
return m
}
type Manager struct {}
func (p Manager) Manage() {
fmt.Println("manage...")
}
代码更简单了,而且有没有发现-漂亮了!Once.Do方法的参数是一个函数,这里我们给的是一个匿名函数,在这个函数中我们做的工作很简单,就是去赋值m变量,而且go能保证这个函数中的代码仅仅执行一次!
ok,到现在单例模式我们就介绍完了,内容并不多,因为单例模式太简单而且太常见了。我们用单例的目的是为了保证在整个系统中存在唯一的实例,我们加锁的目的是为了在并发的环境中单例依旧好用。不过虽然单例简单,我们还是不能任性的用,因为这样做实例会一直存在内存中,一些我们用的不是那么频繁的东西使用了单例是不是就造成了内存的浪费?大家在用单例的时候还是要多思考思考,这个模块适不适合用单例!
开放封闭原则:对于扩展开放,对于修改封闭,尽量不修改,新增,在设计发生变化的时候,就要考虑抽象来应对未来的变化
实现开放封闭的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以对修改就是封闭的;而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。
对于违反这一原则的类,必须通过重构来进行改善。常用于实现的设计模式主要有Template Method模式和Strategy 模式。而封装变化,是实现这一原则的重要手段,将经常变化的状态封装为一个类。
以银行业务员为例
没有实现OCP的设计:
public class BankProcess
{ //存款
public void Deposite(){}
//取款
public void Withdraw(){ }
//转账
public void Transfer(){}
}
public class BankStaff
{
private BankProcess bankpro = new BankProcess();
public void BankHandle(Client client)
{
switch (client.Type)
{ //存款
case "deposite":
bankpro.Deposite();
break;
//取款
case "withdraw":
bankpro.Withdraw();
break;
//转账
case "transfer":
bankpro.Transfer();
break;
}
}
}
这种设计显然是存在问题的,目前设计中就只有存款,取款和转账三个功能,将来如果业务增加了,比如增加申购基金功能,理财功能等,就必须要修改BankProcess业务类。我们分析上述设计就不能发现把不能业务封装在一个类里面,违反单一职责原则,而有新的需求发生,必须修改现有代码则违反了开放封闭原则。
从开放封闭的角度来分析,在银行系统中最可能扩展的就是业务功能的增加或变更。对业务流程应该作为扩展的部分来实现。当有新的功能时,不需要再对现有业务进行重新梳理,然后再对系统做大的修改。
如何才能实现耦合度和灵活性兼得呢?
那就是抽象,将业务功能抽象为接口,当业务员依赖于固定的抽象时,对修改就是封闭的,而通过继承和多态继承,从抽象体中扩展出新的实现,就是对扩展的开放。
以下是符合OCP的设计:
首先声明一个业务处理接口
public interface IBankProcess{ void Process();}
public class DepositProcess : IBankProcess
{
public void Process()
{ //办理存款业务
Console.WriteLine("Process Deposit");
}
}
public class WithDrawProcess : IBankProcess
{
public void Process()
{ //办理取款业务
Console.WriteLine("Process WithDraw");
}
}
public class TransferProcess : IBankProcess
{
public void Process()
{ //办理转账业务
Console.WriteLine("Process Transfer");
}
}
public class BankStaff
{
private IBankProcess bankpro = null;
public void BankHandle(Client client)
{
switch (client.Type)
{ //存款
case "Deposit":
userProc = new DepositUser();
break;
//转账
case "Transfer":
userProc = new TransferUser();
break;
//取款
case "WithDraw":
userProc = new WithDrawUser();
break;
}
userProc.Process();
}
}
这样当业务变更时,只需要修改对应的业务实现类就可以,其他不相干的业务就不必修改。当业务增加,只需要增加业务的实现就可以了。
设计建议:
开放封闭原则,是最为重要的设计原则,Liskov替换原则和合成/聚合复用原则为开放封闭原则提供保证。
可以通过Template Method模式和Strategy模式进行重构,实现对修改封闭,对扩展开放的设计思路。
封装变化,是实现开放封闭原则的重要手段,对于经常发生变化的状态,一般将其封装为一个抽象,例如银行业务中IBankProcess接口。
拒绝滥用抽象,只将经常变化的部分进行抽象。
依赖倒转原则
抽象不应该依赖于细节,细节应该依赖于抽象
针对接口编程,不要对实现编程
高层不应该依赖于底层模块,两个都应该依赖于抽象
核心其实就是针对接口编程
装饰模式
装饰模式 (decorator)就是动态的给一个对象添加一些额外的职责,就增加功能来说。装饰模式比生成子类更加的灵活。
装饰模式使用对象组合的方式动态改变或增加对象行为。
Go语言借助于匿名组合和非入侵式接口可以很方便实现装饰模式。
使用匿名组合,在装饰器中不必显式定义转调原对象方法。
package decorator
import (
"fmt"
)
type person struct {
Name string
}
func (p *person) show() {
if p == nil {
return
}
fmt.Println("姓名:", p.Name)
}
type AbsstractPerson interface {
show()
}
type Decorator struct {
AbsstractPerson
}
func (d *Decorator) SetDecorator(component AbsstractPerson) {
if d == nil {
return
}
d.AbsstractPerson = component
}
func (d *Decorator) show() {
if d == nil {
return
}
if d.AbsstractPerson != nil {
d.AbsstractPerson.show()
}
}
type TShirts struct {
Decorator
}
func (t *TShirts) show() {
if t == nil {
return
}
t.Decorator.show()
fmt.Println("T恤")
}
type BigTrouser struct {
Decorator
}
func (b *BigTrouser) show() {
if b == nil {
return
}
b.Decorator.show()
fmt.Println("大裤衩")
}
type Sneakers struct {
Decorator
}
func (b *Sneakers) show() {
if b == nil {
return
}
b.Decorator.show()
fmt.Println("破球鞋")
}
代理模式
Proxy 代理模式:为其他对象提供一种代理,以控制对这个对象的访问。
代理模式用于延迟处理操作或者在进行实际操作前后进行其它处理。一般都是在结构体外新增一个结构体和这个结构体进行组合,来达到操作或者控制访问。
代理模式的常见用法有
虚代理
COW代理
远程代理
保护代理
Cache 代理
防火墙代理
同步代理
智能指引
等。。。
实例
package proxy
import (
"fmt"
)
type GiveGift interface {
giveDolls()
giveFlowers()
giveChocolate()
}
type Girl struct {
name string
}
func (g *Girl) Name() string {
if g == nil {
return ""
}
return g.name
}
func (g *Girl) SetName(name string) {
if g == nil {
return
}
g.name = name
}
type Pursuit struct {
girl Girl
}
func (p *Pursuit) giveDolls() {
if p == nil {
return
}
fmt.Println(p.girl.name, "送你洋娃娃")
}
func (p *Pursuit) giveFlowers() {
if p == nil {
return
}
fmt.Println(p.girl.name, "送你玫瑰花")
}
func (p *Pursuit) giveChocolate() {
if p == nil {
return
}
fmt.Println(p.girl.name, "送你巧克力")
}
type Proxy struct {
p Pursuit
}
func (p *Proxy) giveDolls() {
if p == nil {
return
}
p.p.giveDolls()
}
func (p *Proxy) giveFlowers() {
if p == nil {
return
}
p.p.giveFlowers()
}
func (p *Proxy) giveChocolate() {
if p == nil {
return
}
p.p.giveChocolate()
}
func NewProxy(mm Girl) *Proxy {
gg := Pursuit{mm}
return &Proxy{gg}
}
工厂办法模式
工厂方法模式使用子类的方式延迟生成对象到子类中实现。
Go中不存在继承 所以使用匿名组合来实现
简单工厂模式是通过传递不同的参数生成不同的实例,缺点就是扩展不同的类别时需要修改代码。
工厂方法模式为每一个product提供一个工程类,通过不同工厂创建不同实例。
简单工厂和工厂模式
简单工厂定义的是静态函数,
一个函数处理所有的产品创建,工厂模式将创建对象过程抽象为一个类组,
有抽象类,有对应产品的创建类,创建的过程有创建类来完成,
工厂模式主要使用的是依赖反转原则
(高层模块不依赖底层模块,统一依赖抽象层,抽象层不依赖细节层,细节层依赖抽象层),
解决简单工厂的缺少开放-封闭原则
实例
package factorymethod
//Operator 是被封装的实际类接口
type Operator interface {
SetA(int)
SetB(int)
Result() int
}
//OperatorFactory 是工厂接口
type OperatorFactory interface {
Create() Operator
}
//OperatorBase 是Operator 接口实现的基类,封装公用方法
type OperatorBase struct {
a, b int
}
//SetA 设置 A
func (o *OperatorBase) SetA(a int) {
o.a = a
}
//SetB 设置 B
func (o *OperatorBase) SetB(b int) {
o.b = b
}
//PlusOperatorFactory 是 PlusOperator 的工厂类
type PlusOperatorFactory struct{}
func (PlusOperatorFactory) Create() Operator {
return &PlusOperator{
OperatorBase: &OperatorBase{},
}
}
//PlusOperator Operator 的实际加法实现
type PlusOperator struct {
*OperatorBase
}
//Result 获取结果
func (o PlusOperator) Result() int {
return o.a + o.b
}
//MinusOperatorFactory 是 MinusOperator 的工厂类
type MinusOperatorFactory struct{}
func (MinusOperatorFactory) Create() Operator {
return &MinusOperator{
OperatorBase: &OperatorBase{},
}
}
//MinusOperator Operator 的实际减法实现
type MinusOperator struct {
*OperatorBase
}
//Result 获取结果
func (o MinusOperator) Result() int {
return o.a - o.b
}
原型模式
原型模式使对象能复制自身,并且暴露到接口中,使客户端面向接口编程时,不知道接口实际对象的情况下生成新的对象。
原型模式配合原型管理器使用,使得客户端在不知道具体类的情况下,通过接口管理器得到新的实例,并且包含部分预设定配置。
注意浅复制、深复制
package prototype
import (
"fmt"
)
type Resume struct {
name string
sex string
age string
timeArea string
company string
}
func (r *Resume) setPersonalInfo(name, sex, age string) {
if r == nil {
return
}
r.name = name
r.age = age
r.sex = sex
}
func (r *Resume) setWorkExperience(timeArea, company string) {
if r == nil {
return
}
r.company = company
r.timeArea = timeArea
}
func (r *Resume) display() {
if r == nil {
return
}
fmt.Println("个人信息:", r.name, r.sex, r.age)
fmt.Println("工作经历:", r.timeArea, r.company)
}
func (r *Resume) clone() *Resume {
if r == nil {
return nil
}
new_obj := (*r)
return &new_obj
}
func NewResume() *Resume {
return &Resume{}
}
模版方法模式
模版方法模式使用继承机制,把通用步骤和通用方法放到父类中,把具体实现延迟到子类中实现。使得实现符合开闭原则。
如实例代码中通用步骤在父类中实现(准备、下载、保存、收尾)下载和保存的具体实现留到子类中,并且提供 保存方法的默认实现。
因为Golang不提供继承机制,需要使用匿名组合模拟实现继承。
此处需要注意:因为父类需要调用子类方法,所以子类需要匿名组合父类的同时,父类需要持有子类的引用。
Template Methed模板方法:
定义一个操作中的算法的骨架,而将一些具体步骤延迟到子类中。
模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
与建造者:一个是行为型模式,一个是创建型模式
模版方法其实就是将不变的行为抽象为一个方法,具体实现在子类中。然后直接子类结构体直接调用这个方法,就可以实现。
package template
import (
"fmt"
)
type getfood interface {
first()
secend()
three()
}
type template struct {
g getfood
}
func (b *template) getsomefood() {
if b == nil {
return
}
b.g.first()
b.g.secend()
b.g.three()
}
type bingA struct {
template
}
func NewBingA() *bingA {
b := bingA{}
return &b
}
func (b *bingA) first() {
if b == nil {
return
}
fmt.Println("打开冰箱")
}
func (b *bingA) secend() {
if b == nil {
return
}
fmt.Println("拿出东西")
}
func (b *bingA) three() {
if b == nil {
return
}
fmt.Println("关闭冰箱")
}
type Guo struct {
template
}
func NewGuo() *Guo {
b := Guo{}
return &b
}
func (b *Guo) first() {
if b == nil {
return
}
fmt.Println("打开锅")
}
func (b *Guo) secend() {
if b == nil {
return
}
fmt.Println("拿出东西锅")
}
func (b *Guo) three() {
if b == nil {
return
}
fmt.Println("关闭锅")
}
迪米特法则
最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
类之间耦合越松,越有利于复用,一个处于弱耦合的类被修改,不会对有关系的类造成波及。
外观模式
API 为facade 模块的外观接口,大部分代码使用此接口简化对facade类的访问。
facade模块同时暴露了a和b 两个Module 的NewXXX和interface,其它代码如果需要使用细节功能时可以直接调用。
为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口, 这个接口使得这一子系统更加容易使用(投资:基金,股票,房产)
package facade
import "fmt"
func NewAPI() API {
return &apiImpl{
a: NewAModuleAPI(),
b: NewBModuleAPI(),
}
}
//API is facade interface of facade package
type API interface {
Test() string
}
//facade implement
type apiImpl struct {
a AModuleAPI
b BModuleAPI
}
func (a *apiImpl) Test() string {
aRet := a.a.TestA()
bRet := a.b.TestB()
return fmt.Sprintf("%s\n%s", aRet, bRet)
}
//NewAModuleAPI return new AModuleAPI
func NewAModuleAPI() AModuleAPI {
return &aModuleImpl{}
}
//AModuleAPI ...
type AModuleAPI interface {
TestA() string
}
type aModuleImpl struct{}
func (*aModuleImpl) TestA() string {
return "A module running"
}
//NewBModuleAPI return new BModuleAPI
func NewBModuleAPI() BModuleAPI {
return &bModuleImpl{}
}
//BModuleAPI ...
type BModuleAPI interface {
TestB() string
}
type bModuleImpl struct{}
func (*bModuleImpl) TestB() string {
return "B module running"
}
建造者模式
Builder 生成器模式:(建造者模式)将一个复杂对象的构建与它表示分离,使得同样的构建过程可以创建不同的表示
个人想法:建造者的建造流程是在指挥者中,指挥者在用户通知他现在具体的建造者是谁后,建造出对应的产品,建造者中实现了产品的建造细节
package builder
import (
"fmt"
)
type IBuilder interface {
head()
body()
foot()
hand()
}
type Thin struct {
}
func (t *Thin) head() {
fmt.Println("我的头很瘦")
}
func (t *Thin) body() {
fmt.Println("我的身体很瘦")
}
func (t *Thin) foot() {
fmt.Println("我的脚很瘦")
}
func (t *Thin) hand() {
fmt.Println("我的身体手很瘦")
}
type Fat struct {
}
func (t *Fat) head() {
fmt.Println("我的头很胖")
}
func (t *Fat) body() {
fmt.Println("我的身体很胖")
}
func (t *Fat) foot() {
fmt.Println("我的脚很胖")
}
func (t *Fat) hand() {
fmt.Println("我的身体手很胖")
}
type Director struct {
person IBuilder
}
func (d *Director) CreatePerson() {
if d == nil {
return
}
d.person.head()
d.person.body()
d.person.foot()
d.person.hand()
}
观察者模式
观察者模式用于触发联动。
一个对象的改变会触发其它观察者的相关动作,而此对象无需关心连动对象的具体实现。
Observer 观察者模式:
定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。
这个主题对象在状态发生改变时,会通知所有观察者对象,使它们能够自动更新自己。
一个系统分成多个系统互相合作,需要维护多个系统的一致性的时候,,不能为了一致性而使其紧密耦合,这样不利于扩展,重用,维护。而观察者有主题subject和观察者observer,一个subject可以有依赖于 他的任意数目的observers,一旦subject发送改变,所有的observer都可以得到通知。subject不需要关心observers的实现。
package observer
import (
"fmt"
)
type Subject interface {
Notify()
State() int
SetState(int)
AddCallFunc(*update)
RemoveCallFunc(*update)
}
type update func(int)
type SubjectA struct {
state int
call []*update
}
func (s *SubjectA) Notify() {
if s == nil {
return
}
for _, c := range s.call {
(*c)(s.state)
}
}
func (s *SubjectA) State() int {
if s == nil {
return 0
}
return s.state
}
func (s *SubjectA) SetState(i int) {
if s == nil {
return
}
s.state = i
}
func (s *SubjectA) AddCallFunc(f *update) {
if s == nil {
return
}
for _, c := range s.call {
if c == f {
return
}
}
s.call = append(s.call, f)
}
func (s *SubjectA) RemoveCallFunc(f *update) {
if s == nil {
return
}
for i, c := range s.call {
if c == f {
s.call = append(s.call[:i], s.call[i+1:]...)
}
}
}
func NewSubjectA(s int) *SubjectA {
return &SubjectA{s, []*update{}}
}
type Observer interface {
Update(int)
}
type ObserverA struct {
s Subject
state int
}
func (o *ObserverA) Update(s int) {
if o == nil {
return
}
fmt.Println("ObserverA")
fmt.Println(s)
fmt.Println(o)
}
func NewObserverA(sa Subject, s int) *ObserverA {
return &ObserverA{sa, s}
}
type ObserverB struct {
s Subject
state int
}
func (o *ObserverB) Update(s int) {
if o == nil {
return
}
fmt.Println("ObserverB")
fmt.Println(s)
fmt.Println(o)
}
func NewObserverB(sa Subject, s int) *ObserverB {
return &ObserverB{sa, s}
}
抽象工厂模式
Abstract Factory 抽象工厂模式:提供一个创建一系列相关或者相互依赖对象的接口,而无需指定他们具体的类。
工厂模式和抽象工厂模式:感觉抽象工厂可以叫集团模式
工厂模式下,是一个工厂下,对产品的每一个具体生成分配不同的流水线;
集团模式:在集团下,有不同的工厂,可以生成不同的产品,每个工厂生产出来的同一个型号产品具体细节是不一样
数据库(interface)—-选择哪个数据库,创建对数据库的表进行操作实例
iuser(interface)—-不同实例对表的操作
idepartment(interface)—–不同实例对表的操作
其实就是返回对应接口的实现结构体
package abstractfactory
import (
"fmt"
)
type User struct {
id int
name string
}
func (u *User) Id() int {
if u == nil {
return -1
}
return u.id
}
func (u *User) SetId(id int) {
if u == nil {
return
}
u.id = id
}
func (u *User) Name() string {
if u == nil {
return ""
}
return u.name
}
func (u *User) SetName(name string) {
if u == nil {
return
}
u.name = name
}
type Department struct {
id int
name string
}
func (d *Department) Id() int {
if d == nil {
return -1
}
return d.id
}
func (d *Department) SetId(id int) {
if d == nil {
return
}
d.id = id
}
func (d *Department) Name() string {
if d == nil {
return ""
}
return d.name
}
func (d *Department) SetName(name string) {
if d == nil {
return
}
d.name = name
}
type IUser interface {
insert(*User)
getUser(int) *User
}
type SqlServerUser struct {
}
func (s *SqlServerUser) insert(u *User) {
if s == nil {
return
}
fmt.Println("往SqlServer的User表中插入一条User", u)
}
func (s *SqlServerUser) getUser(id int) (u *User) {
if s == nil {
return nil
}
u = &User{id, "hclacS"}
fmt.Println("从SqlServer的User表中获取一条User", *u)
return
}
type AccessUser struct {
}
func (s *AccessUser) insert(u *User) {
if s == nil {
return
}
fmt.Println("往AccessUser的User表中插入一条User", *u)
}
func (s *AccessUser) getUser(id int) (u *User) {
if s == nil {
return nil
}
u = &User{id, "hclacA"}
fmt.Println("从AccessUser的User表中获取一条User", *u)
return
}
type IDepartment interface {
insert(*Department)
getDepartment(int) *Department
}
type SqlServerDepartment struct {
}
func (s *SqlServerDepartment) insert(d *Department) {
if s == nil {
return
}
fmt.Println("往SqlServer的Department表中插入一条Department", *d)
}
func (s *SqlServerDepartment) getDepartment(id int) (u *Department) {
if s == nil {
return nil
}
u = &Department{id, "hclacDS"}
fmt.Println("从SqlServer的Department表中获取一条Department", *u)
return
}
type AccessDepartment struct {
}
func (s *AccessDepartment) insert(u *Department) {
if s == nil {
return
}
fmt.Println("往AccessDepartment的Department表中插入一条Department", *u)
}
func (s *AccessDepartment) getDepartment(id int) (u *Department) {
if s == nil {
return nil
}
u = &Department{id, "hclacDA"}
fmt.Println("从AccessDepartment的Department表中获取一条Department", *u)
return
}
type Ifactory interface {
createUser() IUser
createDepartment() IDepartment
}
type SqlServerFactory struct {
}
func (s *SqlServerFactory) createUser() IUser {
if s == nil {
return nil
}
u := &SqlServerUser{}
return u
}
func (s *SqlServerFactory) createDepartment() IDepartment {
if s == nil {
return nil
}
u := &SqlServerDepartment{}
return u
}
type AccessFactory struct {
}
func (s *AccessFactory) createUser() IUser {
if s == nil {
return nil
}
u := &AccessUser{}
return u
}
func (s *AccessFactory) createDepartment() IDepartment {
if s == nil {
return nil
}
u := &AccessDepartment{}
return u
}
type DataAccess struct {
db string
}
func (d *DataAccess) createUser(db string) IUser {
if d == nil {
return nil
}
var u IUser
if db == "sqlserver" {
u = new(SqlServerUser)
} else if db == "access" {
u = new(AccessUser)
}
return u
}
func (d *DataAccess) createDepartment(db string) IDepartment {
if d == nil {
return nil
}
var u IDepartment
if db == "sqlserver" {
u = new(SqlServerDepartment)
} else if db == "access" {
u = new(AccessDepartment)
}
return u
}
状态模式
方法实现过长是坏味道。
State 状态模式:当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
策略模式是用在对多个做同样事情(统一接口)的类对象的选择上,而状态模式是:将对某个事情的处理过程抽象成接口和实现类的形式,由context保存一份state,在state实现类处理事情时,修改状态传递给context,由context继续传递到下一个状态处理中。
状态模式用于分离状态和行为。
package state
import (
"fmt"
)
// 工作类 --context
type Work struct {
hour int
state State
}
func (w *Work) Hour() int {
if w == nil {
return -1
}
return w.hour
}
func (w *Work) State() State {
if w == nil {
return nil
}
return w.state
}
func (w *Work) SetHour(h int) {
if w == nil {
return
}
w.hour = h
}
func (w *Work) SetState(s State) {
if w == nil {
return
}
w.state = s
}
func (w *Work) writeProgram() {
if w == nil {
return
}
w.state.writeProgram(w)
}
func NewWork() *Work {
state := new(moringState)
return &Work{state: state}
}
type State interface {
writeProgram(w *Work)
}
// 上午时分状态类
type moringState struct {
}
func (m *moringState) writeProgram(w *Work) {
if w.Hour() < 12 {
fmt.Println("现在是上午时分", w.Hour())
} else {
w.SetState(new(NoonState))
w.writeProgram()
}
}
// 中午时分状态类
type NoonState struct {
}
func (m *NoonState) writeProgram(w *Work) {
if w.Hour() < 13 {
fmt.Println("现在是中午时分", w.Hour())
} else {
w.SetState(new(AfternoonState))
w.writeProgram()
}
}
// 下午时分状态类
type AfternoonState struct {
}
func (m *AfternoonState) writeProgram(w *Work) {
if w.Hour() < 17 {
fmt.Println("现在是下午时分", w.Hour())
} else {
w.SetState(new(EveningState))
w.writeProgram()
}
}
// 晚上时分状态类
type EveningState struct {
}
func (m *EveningState) writeProgram(w *Work) {
if w.Hour() < 21 {
fmt.Println("现在是晚上时分", w.Hour())
} else {
fmt.Println("现在开始睡觉", w.Hour())
}
}
适配器模式
Adapter 适配器模式:将一个类的接口转换成客户端希望的另一个接口。 适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
代理和适配器:代理和代理的对象接口一致,客户端不知道代理对象,而适配器是客户端想要适配器的接口,适配器对象的接口和客户端想要的不一样,适配器将适配器对象的接口封装一下,改成客户端想要的接口
package adapter
import (
"fmt"
)
type Player interface {
attack()
defense()
}
type Forwards struct {
name string
}
func (f *Forwards) attack() {
if f == nil {
return
}
fmt.Println(f.name, "在进攻")
}
func (f *Forwards) defense() {
if f == nil {
return
}
fmt.Println(f.name, "在防守")
}
func NewForwards(name string) Player {
return &Forwards{name}
}
type Centers struct {
name string
}
func (f *Centers) attack() {
if f == nil {
return
}
fmt.Println(f.name, "在进攻")
}
func (f *Centers) defense() {
if f == nil {
return
}
fmt.Println(f.name, "在防守")
}
func NewCenter(name string) Player {
return &Centers{name}
}
type ForeignCenter struct {
name string
}
func (f *ForeignCenter) attack(what string) {
if f == nil {
return
}
fmt.Println(f.name, "在进攻")
}
func (f *ForeignCenter) defense() {
if f == nil {
return
}
fmt.Println(f.name, "在防守")
}
type Translator struct {
f ForeignCenter
}
// 这是用户想要的接口
func (t *Translator) attack() {
if t == nil {
return
}
t.f.attack("进攻")
}
func (t *Translator) defense() {
if t == nil {
return
}
t.f.defense()
}
func NewTranslator(name string) Player {
return &Translator{ForeignCenter{name}}
}
package adapter
//Target 是适配的目标接口
type Target interface {
Request() string
}
//Adaptee 是被适配的目标接口
type Adaptee interface {
SpecificRequest() string
}
//NewAdaptee 是被适配接口的工厂函数
func NewAdaptee() Adaptee {
return &adapteeImpl{}
}
//AdapteeImpl 是被适配的目标类
type adapteeImpl struct{}
//SpecificRequest 是目标类的一个方法
func (*adapteeImpl) SpecificRequest() string {
return "adaptee method"
}
//NewAdapter 是Adapter的工厂函数
func NewAdapter(adaptee Adaptee) Target {
return &adapter{
Adaptee: adaptee,
}
}
//Adapter 是转换Adaptee为Target接口的适配器
type adapter struct {
Adaptee
}
//Request 实现Target接口
func (a *adapter) Request() string {
return a.SpecificRequest()
}
备忘录模式
Memento 备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态
将某个类的状态(某些状态,具体有该类决定)保存在另外一个类中(代码级别:提供一个函数能够将状态保存起来,返回出去),保存好状态的类对象是管理类的成员,原来的类需要恢复时,再从管理类中获取原来的状态
package memento
import (
"fmt"
)
type GameRole struct {
vit int
atk int
def int
}
func (g *GameRole) StateDisplay() {
if g == nil {
return
}
fmt.Println("角色当前状态:")
fmt.Println("体力:", g.vit)
fmt.Println("攻击:", g.atk)
fmt.Println("防御:", g.def)
fmt.Println("============")
}
func (g *GameRole) GetInitState() {
if g == nil {
return
}
g.vit = 100
g.atk = 100
g.def = 100
}
func (g *GameRole) Fight() {
if g == nil {
return
}
g.vit = 0
g.atk = 0
g.def = 0
}
func (g *GameRole) SaveState() RoleStateMemento {
if g == nil {
return RoleStateMemento{}
}
return RoleStateMemento{*g}
}
func (g *GameRole) RecoveryState(r RoleStateMemento) {
if g == nil {
return
}
g.vit = r.vit
g.atk = r.atk
g.def = r.def
}
type RoleStateMemento struct {
GameRole
}
type RoleStateCaretaker struct {
memento RoleStateMemento
}
备忘录模式用于保存程序内部状态到外部,又不希望暴露内部状态的情形。
程序内部状态使用窄接口船体给外部进行存储,从而不暴露程序实现细节。
备忘录模式同时可以离线保存内部状态,如保存到数据库,文件等。
package memento
import "fmt"
type Memento interface{}
type Game struct {
hp, mp int
}
type gameMemento struct {
hp, mp int
}
func (g *Game) Play(mpDelta, hpDelta int) {
g.mp += mpDelta
g.hp += hpDelta
}
func (g *Game) Save() Memento {
return &gameMemento{
hp: g.hp,
mp: g.mp,
}
}
func (g *Game) Load(m Memento) {
gm := m.(*gameMemento)
g.mp = gm.mp
g.hp = gm.hp
}
func (g *Game) Status() {
fmt.Printf("Current HP:%d, MP:%d\n", g.hp, g.mp)
}
组合模式
Composite 组合模式:将对象组合成树形结构,以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性
组合模式统一对象和对象集,使得使用相同接口使用对象和对象集。
组合模式常用于树状结构,用于统一叶子节点和树节点的访问,并且可以用于应用某一操作到所有子节点。
package composite
import (
"fmt"
"strings"
)
// 公司管理接口
type Company interface {
add(Company)
remove(Company)
display(int)
lineOfDuty()
}
type RealCompany struct {
name string
}
// 具体公司
type ConcreateCompany struct {
RealCompany
list []Company
}
func NewConcreateCompany(name string) *ConcreateCompany {
return &ConcreateCompany{RealCompany{name}, []Company{}}
}
func (c *ConcreateCompany) add(newc Company) {
if c == nil {
return
}
c.list = append(c.list, newc)
}
func (c *ConcreateCompany) remove(delc Company) {
if c == nil {
return
}
for i, val := range c.list {
if val == delc {
c.list = append(c.list[:i], c.list[i+1:]...)
return
}
}
return
}
func (c *ConcreateCompany) display(depth int) {
if c == nil {
return
}
fmt.Println(strings.Repeat("-", depth), " ", c.name)
for _, val := range c.list {
val.display(depth + 2)
}
}
func (c *ConcreateCompany) lineOfDuty() {
if c == nil {
return
}
for _, val := range c.list {
val.lineOfDuty()
}
}
// 人力资源部门
type HRDepartment struct {
RealCompany
}
func NewHRDepartment(name string) *HRDepartment {
return &HRDepartment{RealCompany{name}}
}
func (h *HRDepartment) add(c Company) {}
func (h *HRDepartment) remove(c Company) {}
func (h *HRDepartment) display(depth int) {
if h == nil {
return
}
fmt.Println(strings.Repeat("-", depth), " ", h.name)
}
func (h *HRDepartment) lineOfDuty() {
if h == nil {
return
}
fmt.Println(h.name, "员工招聘培训管理")
}
// 财务部门
type FinanceDepartment struct {
RealCompany
}
func NewFinanceDepartment(name string) *FinanceDepartment {
return &FinanceDepartment{RealCompany{name}}
}
func (h *FinanceDepartment) add(c Company) {}
func (h *FinanceDepartment) remove(c Company) {}
func (h *FinanceDepartment) display(depth int) {
if h == nil {
return
}
fmt.Println(strings.Repeat("-", depth), " ", h.name)
}
func (h *FinanceDepartment) lineOfDuty() {
if h == nil {
return
}
fmt.Println(h.name, "公司财务收支管理")
}
迭代器模式
Iterator 迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。比如下面只是使用next,而不会暴露具体的东西,就像range。
package iterator
//"fmt"
type Book struct {
name string
}
type Iterator interface {
first() interface{}
next() interface{}
}
type BookGroup struct {
books []Book
}
func (b *BookGroup) add(newb Book) {
if b == nil {
return
}
b.books = append(b.books, newb)
}
func (b *BookGroup) createIterator() *BookIterator {
if b == nil {
return nil
}
return &BookIterator{b, 0}
}
type BookIterator struct {
g *BookGroup
index int
}
func (b *BookIterator) first() interface{} {
if b == nil {
return nil
}
if len(b.g.books) > 0 {
b.index = 0
return b.g.books[b.index]
}
return nil
}
func (b *BookIterator) next() interface{} {
if b == nil {
return nil
}
if len(b.g.books) > b.index+1 {
b.index++
return b.g.books[b.index]
}
return nil
}
单例模式
Singleton 单例:保证一个类仅有一个实例,并提供一个访问它的全局访问点
用Go实现时,巧妙使用包级别的变量声明规则:小写字母的包级别变量是不对外开放的,创建实例时,用同步库sync.Once来保证全局只有一个对象实例。
package singleton
import (
"fmt"
"sync"
)
// 全局实例者
type singleton struct {
data int
}
// 定义一个包级别的private实例变量
var sin *singleton
// 同步Once,保证每次调用时,只有第一次生效
var once sync.Once
// 获取实例对象函数
func GetSingleton() *singleton {
once.Do(func() {
sin = &singleton{12}
})
fmt.Println("实例对象的信息和地址", sin, &sin)
return sin
}
使用懒惰模式的单例模式,使用双重检查加锁保证线程安全
桥接模式
Bridge 桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化
桥接模式是聚合/合成规则的一种使用。
桥接模式分离抽象部分和实现部分。使得两部分独立扩展。
桥接模式类似于策略模式,区别在于策略模式封装一系列算法使得算法可以互相替换。
策略模式使抽象部分和实现部分分离,可以独立变化。
package bridge
import (
"fmt"
)
type Phone struct {
soft ISoftware
name string
}
func (p *Phone) setSoft(soft ISoftware) {
if p == nil {
return
}
p.soft = soft
}
func (p *Phone) Run() {
if p == nil {
return
}
fmt.Println(p.name)
p.soft.Run()
}
type PhoneA struct {
Phone
}
func NewPhoneA(name string) *PhoneA {
return &PhoneA{Phone{name: name}}
}
type PhoneB struct {
Phone
}
func NewPhoneB(name string) *PhoneB {
return &PhoneB{Phone{name: name}}
}
type ISoftware interface {
Run()
}
type TSoftware struct {
ISoftware
}
type Software struct {
name string
}
type SoftwareA struct {
Software
}
func (s *SoftwareA) Run() {
if s == nil {
return
}
fmt.Println(s.name)
}
type SoftwareB struct {
Software
}
/*func (s *SoftwareB) Run() {
if s == nil {
return
}
fmt.Println(s.name)
}*/
命令模式
命令模式本质是把某个对象的方法调用封装到对象中,方便传递、存储、调用。
示例中把主板单中的启动(start)方法和重启(reboot)方法封装为命令对象,再传递到主机(box)对象中。于两个按钮进行绑定:
第一个机箱(box1)设置按钮1(buttion1) 为开机按钮2(buttion2)为重启。 第二个机箱(box1)设置按钮2(buttion2) 为开机按钮1(buttion1)为重启。
从而得到配置灵活性。
除了配置灵活外,使用命令模式还可以用作:
批处理
任务队列
undo, redo
等把具体命令封装到对象中使用的场合
package command
import "fmt"
type Command interface {
Execute()
}
type StartCommand struct {
mb *MotherBoard
}
func NewStartCommand(mb *MotherBoard) *StartCommand {
return &StartCommand{
mb: mb,
}
}
func (c *StartCommand) Execute() {
c.mb.Start()
}
type RebootCommand struct {
mb *MotherBoard
}
func NewRebootCommand(mb *MotherBoard) *RebootCommand {
return &RebootCommand{
mb: mb,
}
}
func (c *RebootCommand) Execute() {
c.mb.Reboot()
}
type MotherBoard struct{}
func (*MotherBoard) Start() {
fmt.Print("system starting\n")
}
func (*MotherBoard) Reboot() {
fmt.Print("system rebooting\n")
}
type Box struct {
buttion1 Command
buttion2 Command
}
func NewBox(buttion1, buttion2 Command) *Box {
return &Box{
buttion1: buttion1,
buttion2: buttion2,
}
}
func (b *Box) PressButtion1() {
b.buttion1.Execute()
}
func (b *Box) PressButtion2() {
b.buttion2.Execute()
}
package command
import (
"fmt"
)
// 命令接口 -- 可以保存在请求队形中,方便请求队形处理命令,具体对命令的执行体在实现这个接口的类型结构体中保存着
type Command interface {
Run()
}
// 请求队形,保存命令列表,在ExecuteCommand函数中遍历执行命令
type Invoker struct {
comlist []Command
}
// 添加命令
func (i *Invoker) AddCommand(c Command) {
if i == nil {
return
}
i.comlist = append(i.comlist, c)
}
// 执行命令
func (i *Invoker) ExecuteCommand() {
if i == nil {
return
}
for _, val := range i.comlist {
val.Run()
}
}
func NewInvoker() *Invoker {
return &Invoker{[]Command{}}
}
// 具体命令,实现Command接口,保存一个对该命令如何处理的执行体
type ConcreteCommandA struct {
receiver ReceiverA
}
func (c *ConcreteCommandA) SetReceiver(r ReceiverA) {
if c == nil {
return
}
c.receiver = r
}
// 具体命令的执行体
func (c *ConcreteCommandA) Run() {
if c == nil {
return
}
c.receiver.Execute()
}
func NewConcreteCommandA() *ConcreteCommandA {
return &ConcreteCommandA{}
}
// 针对ConcreteCommand,如何处理该命令
type ReceiverA struct {
}
func (r *ReceiverA) Execute() {
if r == nil {
return
}
fmt.Println("针对ConcreteCommandA,如何处理该命令")
}
func NewReceiverA() *ReceiverA {
return &ReceiverA{}
}
//////////////////////////////////////////////////////////
// 具体命令,实现Command接口,保存一个对该命令如何处理的执行体
type ConcreteCommandB struct {
receiver ReceiverB
}
func (c *ConcreteCommandB) SetReceiver(r ReceiverB) {
if c == nil {
return
}
c.receiver = r
}
// 具体命令的执行体
func (c *ConcreteCommandB) Run() {
if c == nil {
return
}
c.receiver.Execute()
}
func NewConcreteCommandB() *ConcreteCommandB {
return &ConcreteCommandB{}
}
// 针对ConcreteCommandB,如何处理该命令
type ReceiverB struct {
}
func (r *ReceiverB) Execute() {
if r == nil {
return
}
fmt.Println("针对ConcreteCommandB,如何处理该命令")
}
func NewReceiverB() *ReceiverB {
return &ReceiverB{}
}
职责链模式
Chain Of Responsibility 职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
package chainofresponsibility
import (
"fmt"
)
const (
constHandler = iota
constHandlerA
constHandlerB
)
// 处理请求接口
type IHandler interface {
SetSuccessor(IHandler)
HandleRequest(int) int
}
// 实现处理请求的接口的基本结构体类型
type Handler struct {
successor IHandler // 继承者
}
func (h *Handler) SetSuccessor(i IHandler) {
if h == nil {
return
}
h.successor = i
}
// 具体处理结构体,这里简单处理int类型的请求,判断是否在[1-10]之间,是:处理,否:交给successor处理
type ConcreteHandlerA struct {
Handler
}
func (c *ConcreteHandlerA) HandleRequest(req int) int {
if c == nil {
return constHandler
}
if req > 0 && req < 11 {
fmt.Println("ConcreteHandlerA可以处理这个请求")
return constHandlerA
} else if c.successor != nil {
return c.successor.HandleRequest(req)
}
return constHandler
}
func NewConcreteHandlerA() *ConcreteHandlerA {
return &ConcreteHandlerA{}
}
// 具体处理结构体,这里简单处理int类型的请求,判断是否在[11-20]之间,是:处理,否:交给successor处理
type ConcreteHandlerB struct {
Handler
}
func (c *ConcreteHandlerB) HandleRequest(req int) int {
if c == nil {
return constHandler
}
if req > 10 && req < 21 {
fmt.Println("ConcreteHandlerB可以处理这个请求")
return constHandlerB
} else if c.successor != nil {
return c.successor.HandleRequest(req)
}
return constHandler
}
func NewConcreteHandlerB() *ConcreteHandlerB {
return &ConcreteHandlerB{}
}
中介者模式
Mediator 中介者模式:用一个中介对象来封装一系列的对象交互。中介这使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
每个对象都有一个中介者对象,发生变化时,通知中介者,由中介者判断通知其他的对象。
package Mediator
import (
"fmt"
)
// 中介者接口
type IMediator interface {
Send(string, IColleague)
}
// 实现中介者接口的基本类型
type Mediator struct {
}
// 具体的中介者
type ConcreteMediator struct {
Mediator
colleagues []IColleague
}
func (m *ConcreteMediator) AddColleague(c IColleague) {
if m == nil {
return
}
m.colleagues = append(m.colleagues, c)
}
func (m *ConcreteMediator) Send(message string, c IColleague) {
if m == nil {
return
}
for _, val := range m.colleagues {
if c == val {
continue
}
val.Notify(message)
}
}
func NewConcreteMediator() *ConcreteMediator {
return &ConcreteMediator{}
}
// 合作者接口
type IColleague interface {
Send(string)
Notify(string)
}
// 实现合作者接口的基本类型
type Colleague struct {
mediator IMediator
}
// 具体合作者对象A
type ConcreteColleageA struct {
Colleague
}
func (c *ConcreteColleageA) Notify(message string) {
if c == nil {
return
}
fmt.Println("ConcreteColleageA get message:", message)
}
func (c *ConcreteColleageA) Send(message string) {
if c == nil {
return
}
c.mediator.Send(message, c)
}
func NewConcreteColleageA(mediator IMediator) *ConcreteColleageA {
return &ConcreteColleageA{Colleague{mediator}}
}
// 具体合作者对象B
type ConcreteColleageB struct {
Colleague
}
func (c *ConcreteColleageB) Notify(message string) {
if c == nil {
return
}
fmt.Println("ConcreteColleageB get message:", message)
}
func (c *ConcreteColleageB) Send(message string) {
if c == nil {
return
}
c.mediator.Send(message, c)
}
func NewConcreteColleageB(mediator IMediator) *ConcreteColleageB {
return &ConcreteColleageB{Colleague{mediator}}
}
享元模式
Flyweight 享元模式:运用共享技术有效地支持大量细粒度的对象
主要思想是共享,将可以共享的部分放在对象内部,不可以共享的部分放在外边,享元工厂创建几个享元对象就可以了,这样不同的外部状态,可以针对同一个对象,给人感觉是操作多个对象,通过参数的形式对同一个对象的操作,像是对多个对象的操作
package flyweight
import (
"fmt"
)
// 享元对象接口
type IFlyweight interface {
Operation(int) //来自外部的状态
}
// 共享对象
type ConcreteFlyweight struct {
name string
}
func (c *ConcreteFlyweight) Operation(outState int) {
if c == nil {
return
}
fmt.Println("共享对象响应外部状态", outState)
}
// 不共享对象
type UnsharedConcreteFlyweight struct {
name string
}
func (c *UnsharedConcreteFlyweight) Operation(outState int) {
if c == nil {
return
}
fmt.Println("不共享对象响应外部状态", outState)
}
// 享元工厂对象
type FlyweightFactory struct {
flyweights map[string]IFlyweight
}
func (f *FlyweightFactory) Flyweight(name string) IFlyweight {
if f == nil {
return nil
}
if name == "u" {
return &UnsharedConcreteFlyweight{"u"}
} else if _, ok := f.flyweights[name]; !ok {
f.flyweights[name] = &ConcreteFlyweight{name}
}
return f.flyweights[name]
}
func NewFlyweightFactory() *FlyweightFactory {
ff := FlyweightFactory{make(map[string]IFlyweight)}
ff.flyweights["a"] = &ConcreteFlyweight{"a"}
ff.flyweights["b"] = &ConcreteFlyweight{"b"}
return &ff
}
享元模式从对象中剥离出不发生改变且多个实例需要的重复数据,独立出一个享元,使多个对象共享,从而节省内存以及减少对象数量。
解释器模式
Interpreter 解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子
package interpreter
import (
"fmt"
)
type Context struct {
text string
}
// 抽象表达式
type IAbstractExpression interface {
Interpret(*Context)
}
// 终结符表达式
type TerminalExpression struct {
}
func (t *TerminalExpression) Interpret(context *Context) {
if t == nil {
return
}
context.text = context.text[:len(context.text)-1]
fmt.Println(context)
}
// 非终结符表达式
type NonterminalExpression struct {
}
func (t *NonterminalExpression) Interpret(context *Context) {
if t == nil {
return
}
context.text = context.text[:len(context.text)-1]
fmt.Println(context)
}
解释器模式定义一套语言文法,并设计该语言解释器,使用户能使用特定文法控制解释器行为。
解释器模式的意义在于,它分离多种复杂功能的实现,每个功能只需关注自身的解释。
对于调用者不用关心内部的解释器的工作,只需要用简单的方式组合命令就可以。
访问者模式
Visitor 访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作
package visitor
import (
"fmt"
)
// 访问接口
type IVisitor interface {
VisitConcreteElementA(ConcreteElementA)
VisitConcreteElementB(ConcreteElementB)
}
// 具体访问者A
type ConcreteVisitorA struct {
name string
}
func (c *ConcreteVisitorA) VisitConcreteElementA(ce ConcreteElementA) {
if c == nil {
return
}
fmt.Println(ce.name, c.name)
ce.OperatorA()
}
func (c *ConcreteVisitorA) VisitConcreteElementB(ce ConcreteElementB) {
if c == nil {
return
}
fmt.Println(ce.name, c.name)
ce.OperatorB()
}
// 具体访问者B
type ConcreteVisitorB struct {
name string
}
func (c *ConcreteVisitorB) VisitConcreteElementA(ce ConcreteElementA) {
if c == nil {
return
}
fmt.Println(ce.name, c.name)
ce.OperatorA()
}
func (c *ConcreteVisitorB) VisitConcreteElementB(ce ConcreteElementB) {
if c == nil {
return
}
fmt.Println(ce.name, c.name)
ce.OperatorB()
}
// 元素接口
type IElement interface {
Accept(IVisitor)
}
// 具体元素A
type ConcreteElementA struct {
name string
}
func (c *ConcreteElementA) Accept(visitor IVisitor) {
if c == nil {
return
}
visitor.VisitConcreteElementA(*c)
}
func (c *ConcreteElementA) OperatorA() {
if c == nil {
return
}
fmt.Println("OperatorA")
}
// 具体元素B
type ConcreteElementB struct {
name string
}
func (c *ConcreteElementB) Accept(visitor IVisitor) {
if c == nil {
return
}
visitor.VisitConcreteElementB(*c)
}
func (c *ConcreteElementB) OperatorB() {
if c == nil {
return
}
fmt.Println("OperatorB")
}
// 维护元素集合
type ObjectStructure struct {
list []IElement
}
func (o *ObjectStructure) Attach(e IElement) {
if o == nil || e == nil {
return
}
o.list = append(o.list, e)
}
func (o *ObjectStructure) Detach(e IElement) {
if o == nil || e == nil {
return
}
for i, val := range o.list {
if val == e {
o.list = append(o.list[:i], o.list[i+1:]...)
break
}
}
}
func (o *ObjectStructure) Accept(v IVisitor) {
if o == nil {
return
}
for _, val := range o.list {
val.Accept(v)
}
}
访问者模式可以给一系列对象透明的添加功能,并且把相关代码封装到一个类中。
对象只要预留访问者接口Accept则后期为对象添加功能的时候就不需要改动对象。
总结
其实在golang语言编程设计中分为这么多模式是没有必要的,在以上的实践中,大体可以归纳我们设计时候需要使用的思想模式,其他都是异曲同工的。
- 抽象,设定接口,根据不同的参数获取不同的实例,还可以在上面再封装一层,就如我们实现的简单工厂,策略,抽象工厂等模式,这种设计多数也是为了解耦,抽象解耦是设计中的最重要的思想。
- 继承,开放封闭原则,可扩展不可修改,使用组合来完成具体实现在子类,父类就是一个接口,比如模版等模式
- 封装,符合迪米特法则,就是在实现的基础上新建结构体暴露接口,比如外观,建造者等模式
- 特殊场景,观察者模式,这类就是用于注册观察通知的使用方式. 单例,全局一份。 适配器,将两个不一样的结构进行适配。。。还有好几个,可以具体去看,这边就不一一列举了。