GoPatterns: A code design experience that is repeatedly used in software design
总体而言,设计模式共分为三大类,想知道更多信息,请访问这里
创建型
工厂方法模式
简介
工厂方法模式是Golang中最常用的设计模式之一,属于创建型模式,它提供了一种创建对象的最佳方式。
意图
定义一个用于创建对象的工厂,根据不同的接收条件具体实例化相应的工作类。
工厂方法模式三要素
- 产品接口(Arithmetic) 定义产品的规范,所有的产品实现都必须遵循产品接口定义的规范。产品接口是调用者最为关心的,产品接口定义的优劣直接决定了调用者代码的稳定性
type Arithmetic interface { Calculate(int, int) int }
- 产品实现(Add/Sub/Mul/Div) 产品接口的具体实现类,不同的产品也需要不同的产品实现类,产品实现类与功能创建类相对应
type Add struct{} func (a *Add) Calculate(left int, right int) int { return left + right }
- 工厂实现(Factory) 决定如何实例化产品,是实现扩展的途径,与调用者直接交互用来提供产品的创建。
func Factory(operation string) Arithmetic { switch operation { case add: return &Add{} case sub: return &Sub{} case mul: return new(Mul) case div: return new(Div) default: panic("invalid operation") } }
优点
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心产品的接口。
缺点
- 每次增加一个产品时,都需要增加一个具体类和对象实现工厂。
- 使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
抽象工厂模式
简介
抽象工厂模式是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
意图
定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。
抽象工厂模式四要素
- 产品接口(Driver) 定义产品的规范,所有的产品实现都必须遵循产品接口定义的规范。产品接口是调用者最为关心的,产品接口定义的优劣直接决定了调用者代码的稳定性。
const (
mysql = "mysql"
oracle = "oracle"
sqlite = "sqlite"
)
type Driver interface {
Registry(name string)
}
- 产品实现(Mysql/Oracle/Sqlite) 产品接口的具体实现类,不同的产品也需要不同的产品实现类,产品实现类与功能创建类相对应。
type Mysql struct{}
func (m *Mysql) Registry(name string) {
log.Printf("registry %s driver with: %s", mysql, name)
}
type Oracle struct{}
func (o *Oracle) Registry(name string) {
log.Printf("registry %s driver with: %s", oracle, name)
}
type Sqlite struct{}
func (s *Sqlite) Registry(name string) {
log.Printf("registry %s driver with: %s", sqlite, name)
}
- 工厂接口(AbstractFactory) 工厂方法模式的核心,与调用者直接交互用来提供产品的创建。
type AbstractFactory interface {
Create() Driver
}
- 工厂实现(MysqlFactory/OracleFactory/SqliteFactory) 决定如何实例化产品,是实现扩展的途径,需要有多少种产品,就有多少个具体的工厂实现类,每个工厂实现类负责创建一种产品。
type MysqlFactory struct{}
func (m *MysqlFactory) Create() Driver {
return new(Mysql)
}
type OracleFactory struct{}
func (c *OracleFactory) Create() Driver {
return new(Oracle)
}
type SqliteFactory struct{}
func (s *SqliteFactory) Create() Driver {
return new(Sqlite)
}
优点
- 每增加一个产品就增加一个对应的工厂来创建它,这样整个工厂和产品体系都没有什么变化,而只是扩展的变化,这就完全符合开放-封闭的原则了
- 严格遵循面向对象类的设计原则,比如单一职能原则、开-闭原则、依赖倒置原则、迪米特原则。
- 业务实现解耦: 抽象工厂是静态工厂方法的进一步抽象与推广,由于使用了多态性,抽象工厂模式保持了静态工厂方法的优点同时又克服了它的缺点,不过抽象工厂模式自己的缺点是每加一个产品都需要增加一个工厂类,增加了大量的开发工作量。
缺点
- 产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
建造者模式
介绍
建造者模式是一种创建设计模式,用于封装对象的构造逻辑。 它通常在物体的构造过程复杂时使用。 这些模式非常适合构建同一类的不同表示。
目的
- 将复杂对象的构造与其表示分开,以便相同的构造过程可以创建不同的表示。
- 一种常见的软件创建设计模式,用于封装对象的构造逻辑。
涉及角色
- Builder(抽象建造者):给出一个抽象结论,以规范产品对象的各个组成成分的建造这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的对象部件的创建。
- ConcreteBuilder(具体建造者):实现Builder接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。在构造过程完成后,提供产品的实例。
- Director(指导者):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
- Product(产品类):要创建的复杂对象。
优缺点
1.优点:
- 建造者独立,易于扩展
- 便于控制细节风险
2.缺点:
- 产品必须有共同点,范围有限制
- 如果内部变化复杂,将会有很多的建造类
适用场景
- 需要生成的对象具有复杂的内部结构
- 需要生成的对象内部属性本身相互依赖
实现
1. 产品类:Person
type Person struct {
Name string
Age int
}
func (p *Person) SetName(name string){
p.Name = name
}
func (p *Person) SetAge(age string){
p.Age = age
}
2. 建造者接口: Builder
type Builder interface{
BuilderName(string) Builder
BuilderAge(int) Builder
Build() *Person
}
3. 具体建造者: PersonBuilder
type PersonBuilder struct{
person *Person
}
func (p *PersonBuilder) BuilderName(name string) Builder{
if p.person == nil{
p.person = &Person{}
}
p.person.SetName(name)
return p
}
func (p *PersonBuilder) BuilderAge(age int) Builder{
if p.person == nil{
p.person = &Person{}
}
p.person.SetAge(age)
return p
}
func (p *PersonBuilder) Build() *Person{
return p.person
}
4. 指导者:Director
type Director struct{
builder Builder
}
func (d *Director) Create(name string, age int) *Person{
return d.Builder.BuilderName(name).BuilderAge(age).Build()
}
代码调用
func main(){
var builder Builder = &PersonBuilder{}
var director *Director = &Director{builder: builder}
var person *Person = director.Create("jack",60)
fmt.Println("builder person:",person)
}
单例模式
定义
单例对象的类必须保证只有一个实例存在,并且全局有唯一的接口访问。
分类
- 懒汉方式:指全局的单例实例在第一次被使用时构建。
- 饿汉方式:指全局的单例实例在类装载时构建。
实现
1. 懒汉方式
type singleton struct{}
var ins *singleton
func GetIns() *singleton{
if ins == nil{
ins = &singleton{}
}
return ins
}
缺点:非线程安全,当正在创建时,有线程来访问此时ins==nil就会再创建,单例类就会有多个实例了。
2. 饿汉方式
type singleton struct{}
var ins *singleton = &singleton{}
func GetIns() *singleton{
return ins
}
缺点:如果singleton创建初始化比较复杂耗时时,加载时间会延长。
3. 懒汉加锁
type singleton struct{}
var ins *singleton
var mu sync.Mutex
func GetIns() *singleton{
mu.Lock()
defer mu.Unlock()
if ins == nil {
ins = &singleton{}
}
return ins
}
缺点:虽然解决并发的问题,但每次加锁是要付出代价的
4. 双重锁
type singleton struct{}
var ins *singleton
var mu sync.Mutex
func GetIns() *singleton{
if ins == nil {
mu.Lock()
defer mu.Unlock()
if ins == nil {
ins = &singleton{}
}
}
return ins
}
避免了每次加锁,提高代码效率
5. sync.Once实现
type singleton struct{}
var ins *singleton
var once sync.Once
func GetIns() *singleton {
once.Do(func(){
ins = &singleton{}
})
return ins
}
原型模式
简介
原型模式用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
意图
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。当直接创建对象的代价比较大时,则采用这种模式。 例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
优点
- 性能提高。
- 逃避构造函数的约束。
缺点
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
- 必须实现 Cloneable 接口。
- 逃避构造函数的约束。
使用场景
- 资源优化场景。
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 性能和安全要求的场景。
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 一个对象多个修改者的场景。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
- 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
实现
type Person struct{
Name string
Age int
}
func(p *Person) Clone() *Person{
return &Person{Name:p.Name, Age:p.Age}
}
结构型
适配器模式
定义
将一个类的接口转换为客户希望的另一个接口,适配器模式使得由于接口不兼容不能一起工作的类可以一起工作
简介
适配器模式是作为两个不兼容的接口之间的桥梁,这种设计模式属于结构型模式,他结合了两个独立接口的功能。就像读卡器作为适配器连接内存卡和笔记本。
解决
主要解决软件系统中,将“现存的对象”放到新的环境中,而新环境要求的接口是现阶段不能满足的。
适配器继承或依赖已有的对象,实现想要的目标接口,在我们有动机的修改一个正常运行的系统接口时,这时候考虑使用适配器模式,它不是在详细设计时添加的,而是解决正在服役的项目。
角色
- UserInterface:目标角色——目标接口,系统所期待实现的目标。
- UserInfo:源角色——当前已经存在的原有的实现类,即将被适配的类。
- UserAdapter:适配器角色——将原有实现装换为目标接口的实现。
简单点说,适配器模式是指:定义一个类,将一个已经存在的类,转换成目标接口所期望的行为形式。
举例
如果它走起来像只鸭子,叫起来像只鸭子,那么它可能是一只包装了鸭子适配器的火鸡 假设缺少鸭子对象,想用一些火鸡对象来冒充,显而易见火鸡的接口不同,需要写个适配器
1. 鸭子接口 (目标接口)
//鸭子Duck接口,具备呱呱叫和飞行的能力
type Duck interface {
quack() //呱呱叫
fly() //飞行
}
2. 火鸡接口 (源角色接口)
type Turkey interface {
gobble() //火鸡不会呱呱叫,只会咯咯叫(gobble)
fly() //火鸡也会飞,虽然飞不远
}
3. 火鸡实现类 (源角色实现类)
type WildTurkey struct{}
func (*WildTurkey) gobble() {
fmt.Println("Gobble...")
}
func (*WildTurkey) fly() {
fmt.Println("I'm flying a short distance")
}
4. 适配器 (适配器角色:)
//定义火鸡适配器,用来适配火鸡到鸭子的转换功能,里面需要持有火鸡的句柄
type TurkeyAdapter struct {
turkey Turkey
}
//接着,需要取得适配的对象引用
func NewTurkeyAdapter(turkey Turkey) *TurkeyAdapter {
return &TurkeyAdapter{ turkey }
}
//需要将火鸡的gobble()方法,适配转化成鸭子的quack()方法
func (this *TurkeyAdapter) quack() {
this.turkey.gobble()
}
5. 调用
//目前只有火鸡,没有鸭子,通过适配器转化,用火鸡代替鸭子的功能
turkey := &WildTurkey{}
turkeyAdapter := NewTurkeyAdapter(turkey)
//原来火鸡的功能
turkey.gobble()
turkey.fly()
//通过适配器转化之后,火鸡有了鸭子的quack()功能
turkeyAdapter.quack()
装饰器模式
简介
装饰器模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能。
角色
- 抽象组件(Component):需要装饰的抽象对象。
- 具体组件(ConcreteComponent):是我们需要装饰的对象
- 抽象装饰类(Decorator):内含指向抽象组件的引用及装饰者共有的方法。
- 具体装饰类(ConcreteDecorator):被装饰的对象。
应用场景
- 需要扩展一个类的功能。
- 动态的为一个对象增加功能,而且还能动态撤销(继承不能做到这一点,继承的功能是静态的,不能动态增删)
缺点
- 产生过多相似的对象,不易排错!
实现
给房屋的每一处装修无非是为了给房子添加一个功能,或者为了好看,或者为了实用,而装饰器模式也是为了给程序完成类似的效果.
1, 抽象组件 Room
type Room interface{
//房子提供展示的功能
Show()
}
2,具体组件 MyRoom
type MyRoom struct{}
func (m *MyRoom) Show(){
fmt.Println("一个简单的房子")
}
3, 装饰类 Decorator
type Decorator interface{
Decorate()
}
4, 具体装饰器
type DecoratorRoom1 struct{
room MyRoom
}
func (d *DecoratorRoom1) Decorate(){
fmt.Println("beautiful room")
}
func(d *DecoratorRoom1) Show(){
d.room.Show()
decorate()
}
调用
func main(){
room := MyRoom{}
decorate := DecoratorRoom1{room: room}
decorate.Show()
}
过滤器模式
简介
过滤器模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把他们连接起来。
角色
- 待过滤的对象
- 过滤器接口
- 过滤器实现
实现
1,待过滤的对象 Person
type Person struct{
Name string
Gender string
Age int
}
2, 待过滤的接口 Filter
type Filter interface{
[]Person Filter([]Person)
}
3, 过滤器实现 AgeFilter,GenderFilter
type AgeFilter struct{}
func (a *AgeFilter) Filter(p []Person)[]Person{
var ret []Person
for _, v := range p{
if v.Age > 15 {
ret = append(ret, v)
}
}
return ret
}
type GenderFilter struct{}
func (a *GenderFilter) Filter(p []Person)[]Person{
var ret []Person
for _, v := range p{
if v.Gender == "Male" {
ret = append(ret, v)
}
}
return ret
}
4, 调用
func main(){
p := []Person{
{Name:"Abc", Gender:"Male", Age:45},
{Name:"Bde", Gender:"Female", Age:23},
}
age := AgeFilter{}.Filter(p)
gender := GenderFilter{}.Filter(p)
}
桥接模式
简介
桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
优点
- 分离接口及其实现部分:一个实现未必不变地绑定在一个接口上,抽象类的实现可在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。将abstraction与Implementor分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类时,不需要重新编译abstraction类和客户重新。为了保证一个类库的不同版本之间二进制兼容性,一定要有这个性质。另外,接口和实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分只要知道abstraction和implementor即可。
- 提高可扩展性,可以独立对Abstraction和Implementor层次进行扩展。
- 实现细节对可对客户透明。
缺点
- 不容易设计,需不需要分离,如何分离等问题。比较难以拿捏。
角色
-
Client 调用端:这是Bridge模式的调用者。
-
抽象类(Abstraction):抽象类接口(接口这货抽象类)维护队行为实现(implementation)的引用。它的角色就是桥接类。
-
Refined Abstraction: 这是Abstraction的子类。
-
Implementor: 行为实现类接口(Abstraction接口定义了基于Implementor接口的更高层次的操作)
-
ConcreteImplementor:Implementor的子类
实现
像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。
1, 行为实现类接口:
type DriveType int
const(
DB2 DriverType = iota
MySql
Oracle
)
type Driver interface{
echoDriver() DriverType
}
2, 行为实现类
type DB2Driver struct{}
func (d *DB2Driver) echoDriver() DriverType{
return DB2
}
type MySqlDriver struct{}
func (d *MySqlDriver) echoDriver() DriverType{
return MySql
}
type OracleDriver struct{}
func (d *OracleDriver) echoDriver() DriverType{
return Oracle
}
3, 桥接类
type Bridge struct{
driver Driver
}
func(b *Bridge) SetDriver(driver Driver){
b.driver = driver
}
func(b *Bridge) getDriver() Driver{
return b.driver
}
4, 桥接子类
type BridgeManager struct{
Bridge
}
func (b *BridgeManager) getDriverType() DriverType{
return b.getDriver().echoDriver()
}
5, 调用
func main(){
bridge := BridgeManager()
//调用OracleDriver
bridge.SetDriver(OracleDriver{})
bridge.getDriverType()
//调用DB2Driver
bridge.SetDriver(DB2Driver{})
bridge.getDriverType()
//调用MysqlDriver
bridge.SetDriver(MysqlDriver{})
bridge.getDriverType()
}
代理模式
定义
代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你做,此处的代理就是这个意思。再如我们有的时候打官司,我们需要请律师,因为律师在法律方面有专长,可以替我们进行操作,表达我们的想法。
角色
1.抽象对象角色
声明了目标类及代理类对象的共同接口,这样在任何可以使用目标对象的地方都可以使用代理对象。
2.目标对象角色
定义了代理对象所代表的目标对象。
3.代理对象角色
代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象和目标对象具有统一的接口,以便可以再任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或者之后,执行某些操作,而非单纯的将调用传递给目标对象。
实现
1,抽象对象角色 IUserdao
type IUserDao interface{
Save()
}
2,目标对象角色 UserDao
type UserDao struct{}
func (u *UserDao)Save(){
fmt.Println("save user success")
}
3,代理对象角色 UserProxy
type UserProxy struct{
dao IUserDao
}
func (p *UserProxy)ProxySave(){
p.dao.Save()
}
4,代码调用
func main(){
// 目标对象
var target = UserDao{}
//代理对象包装目标对象
var proxy = UserProxy{dao:target}
//通过代理对象调用目标对象的方法
proxy.ProxySave()
}
使用场景:
如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
- 修改原有的方法来适应。显然这违反了“对扩展开放,对修改关闭”的原则。
- 采用一个代理类调用原来的方法,且对产生的结果进行控制。这就是代理模式了。
组合模式
定义
将对象组合成树形结构以表示“部分整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
优缺点
优点
- 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
- 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
- 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
- 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。
缺点
使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联
适用场景
- 需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们。
- 让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节。
总结
- 组合模式用于将多个对象组合成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(叶子对象)和组合对象(容器对象)的使用具有一致性。
- 组合对象的关键在于它定义了一个抽象构建类,它既可表示叶子对象,也可表示容器对象,客户仅仅需要针对这个抽象构建进行编程,无须知道他是叶子对象还是容器对象,都是一致对待。
- 组合模式虽然能够非常好地处理层次结构,也使得客户端程序变得简单,但是它也使得设计变得更加抽象,而且也很难对容器中的构件类型进行限制,这会导致在增加新的构件时会产生一些问题。
实现
展示一颗树结构,里面包含childern节点,下面还包含children节点
type TreeNode struct {
Label string `json:"label"`
Type string `json:"type"`
Id string `json:"id"`
IsLeaf bool `json:"isLeaf,omitempty"`
Children []*TreeNode `json:"children,omitempty"`
}
调用
func main(){
var ret = make([]*TreeNode, 0)
a := &TreeNode{Label: "A", Type: "a"}
b := &TreeNode{Label: "B", Type: "b", IsLeaf: true}
c := &TreeNode{Label: "C", Type: "c"}
b := &TreeNode{Label: "B", Type: "b", IsLeaf: true}
c.Children = append(c.Children, b)
a.children = append(a.Children, b)
a.children = append(a.Children, c)
//最后结构为: a
// |--b
// |--c
// |--d
}
外观器模式
简介
外观模式是一种使用频率非常高的结构型设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度,且客户端调用非常方便。
概述
在软件开发中,有时候为了完成一项较为复杂的功能,一个客户类需要和多个业务类交互,而这些需要交互的业务类经常会作为一个整体出现,由于涉及到的类比较多,导致使用时代码较为复杂,此时,特别需要一个类似服务员一样的角色,由它来负责和多个业务类进行交互,而客户类只需与该类交互。
外观模式通过引入一个新的外观类(Facade)来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。
在外观模式中,那些需要交互的业务类被称为子系统(Subsystem)。如果没有外观类,那么每个客户类需要和多个子系统之间进行复杂的交互,系统的耦合度将很大;而引入外观类之后,客户类只需要直接与外观类交互,客户类与子系统之间原有的复杂引用关系由外观类来实现,从而降低了系统的耦合度。
定义
外观模式中,一个子系统的外部与其内部的通信通过一个统一的外观类进行,外观类将客户类与子系统的内部复杂性分隔开,使得客户类只需要与外观角色打交道,而不需要与子系统内部的很多对象打交道。
外观模式:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用. 外观模式又称为门面模式,它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,通过引入一个新的外观角色可以降低原有系统的复杂度,同时降低客户类与子系统的耦合度。
角色
-
Facade(外观角色):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
-
SubSystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。
实现
1, 子系统
type Cpu struct{}
func (c *Cpu)Startup(){
fmt.Println("cpu startup")
}
func (c *Cpu)Shutdown(){
fmt.Println("cpu shutdown")
}
type Memory struct{}
func (c *Memory)Startup(){
fmt.Println("memory startup")
}
func (c *Memory)Shutdown(){
fmt.Println("memory shutdown")
}
type Disk struct{}
func (c *Disk)Startup(){
fmt.Println("disk startup")
}
func (c *Disk)Shutdown(){
fmt.Println("disk shutdown")
}
2, 外观角色
type ComputerFacade struct{
cpu Cpu
memory Memory
disk Disk
}
func(c *ComputerFacade) NewFacade(cpu Cpu,memory Memory, disk Disk)*ComputerFacade{
return &ComputerFacade{
cpu: cpu,
memory: memory,
disk: disk,
}
}
type (c *ComputerFacade) Startup(){
c.cpu.Startup()
c.memory.Startup()
c.disk.Startup()
}
type (c *ComputerFacade) Shutdown(){
c.cpu.Shutdown()
c.memory.Shutdown()
c.disk.Shutdown()
}
3, 调用
func main(){
facade := NewFacade(Cpu{}, Memory{}, Disk{})
facade.Startup()
facade.Shutdown()
}
享元模式
简介
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能.
享元模式从对象中剥离出不发生改变且多个实例需要的重复数据,独立出一个享元,使多个对象共享,从而节省内存以及减少对象数量。
实例
数据库连接池就是这样的例子,实现如下
1. 数据库连接 DBConnect
//数据库连接
type DBConnect struct{}
func(d *DbConnect) Do(){
fmt.Println("connecting ... and ... doing ... something ...")
}
2. 通过享元模式生成数据库连接池 DBConnectPool
//数据库连接池
type DBConnectPool struct{
ConnChan chan *DBConnect
}
func NewDBConnectPool(len int) *DBConnectPool{
return &DBConnectPool{ConnChan:make(chan *DBConnect, len)}
}
func (d *DBConnectPool) Get() *DBConnect{
select{
case conn := <- d.ConnChan:
return conn
default:
//没有新建
conn := new(DBConnect)
d.ConnChan <- conn
return conn
}
}
func(d *DBConnectPool) Put(conn *DBConnect){
select {
case d.ConnChan <- conn:
return
default:
// 满则丢弃
return
}
}
3. 代码调用
func FlyweightTest(){
pool := NewDBConnectPool(5)
conn := pool.Get()
conn.Do()
}
行为型
访问者模式
定义
访问者模式是指封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
介绍
- 访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。
- 访问者模式适用于数据结构相对稳定算法又易变化的系统。
- 若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。
- 访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。
- 访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构,其缺点就是增加新的数据结构很困难。
优点
- 符合单一职责原则。
- 优秀的扩展性。
- 灵活性。
缺点
- 具体元素对访问者公布细节,违反了迪米特原则。
- 具体元素变更比较困难。
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
使用场景
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,也不希望在增加新操作时修改这些类。
角色
- 抽象访问者: 抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法中的参数定义哪些对象是可以被访问的。
- 访问者: 实现抽象访问者所声明的方法,它影响到访问者访问到一个类后该干什么,要做什么事情。
- 抽象元素类: 接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。抽象元素一般有两类方法,一部分是本身的业务逻辑,另外就是允许接收哪类访问者来访问。
- 元素类: 实现抽象元素类所声明的accept方法,通常都是visitor.visit(this),基本上已经形成一种定式了。
- 结构对象: 一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。
代码实现
1, 抽象元素类
//定义Object抽象元素可以被Visitor访问者来访问
type Object interface {
Accept(Visitor)
}
2, 抽象访问者
//定义访问者Visitor可以访问Object接口对应的元素
type Visitor interface {
Visit(Object)
}
3, 元素类
//定义元素类,里面包含两个元素A,B, 它实现了Object接口,可以接收Visitor访问者
type Number struct {
A int
B int
}
func (ab *Number) Accept(vi Visitor) {
vi.Visit(ab)
}
4, 访问者
//定义了2个访问者,分别对Object的元素进行加法和减法的操作
type AddVisitor struct {
}
func (a *AddVisitor) Visit(ds Object) {
data := ds.(*Number)
sum := data.A + data.B
fmt.Println("A+B=", sum)
}
type SubVisitor struct {
}
func (a *SubVisitor) Visit(ds Object) {
data := ds.(*Number)
sub := data.A - data.B
fmt.Println("A-B=", sub)
}
5, 调用
func TestVisitor(t *testing.T) {
//元素类
object := &Number{A: 2, B: 7}
//访问者
add := &AddVisitor{}
sub := &SubVisitor{}
//调用访问者,对元素进行不同的操作
object.Accept(add)
object.Accept(sub)
}
参考资料
- http://www.runoob.com/design-pattern/visitor-pattern.html
- https://github.com/BPing/golang_design_pattern/tree/master/pattern
模版方法模式
定义
定义一个操作中的算法的骨架,而将步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。
角色
- 抽象类(AbstractClass):实现了模板方法,定义了算法的骨架。
- 具体类(ConcreteClass):实现抽象类中的抽象方法,已完成完整的算法。
优缺点
优点
- 模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
- 子类实现算法的某些细节,有助于算法的扩展。
- 通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
缺点
- 每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。
适用场景
- 在某些类的算法中,用了相同的方法,造成代码的重复。
- 控制子类扩展,子类必须遵守算法规则。
代码实现
1. 定义游戏模板 Game
type Game interface{
Start()
Playing()
End()
}
//游戏模版
func RunGame(g Game){
g.Start()
g.Playing()
g.End()
}
2. 定义基本游戏 BaseGame
type BaseGame struct{}
func(b *BaseGame)Start(){
fmt.Println("start game")
}
func(b *BaseGame)Playing(){
fmt.Println("game playing")
}
func(b *BaseGame)End(){
fmt.Println("the end")
}
3. 不同游戏,玩法不同,有FootBall和BasketBall, 同时覆写Playing()方法
type FootBallGame struct{
*BaseGame
}
func(f *FootBallGame)Playing(){
fmt.Println("playing FootBall")
}
type BasketBalllGame struct{
*BaseGame
}
func(b *BasketBalllGame)Playing(){
fmt.Println("playing BasketBall")
}
4. 方法调用
func TemplateTest(){
RunGame(&FootBallGame{})
RunGame(&BasketBallGame{})
}
策略模式
定义
策略模式定义了一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。
适用性
当存在以下情况时使用Strategy模式
- 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。
- 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
- 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
- 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
优缺点
Strategy模式优点:
- 相关算法系列 Strategy类层次为Context定义了一系列的可供重用的算法或行为。 继承有助于析取出这些算法中的公共功能。
- 提供了可以替换继承关系的办法: 继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context类的子类,从而给它以不同的行为。但这会将行为硬行编制到 Context中,而将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。最后你得到一堆相关的类 , 它们之间的唯一差别是它们所使用的算法或行为。 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
- 消除了一些if else条件语句 :Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。 实现的选择 Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。
Strategy模式缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类: 本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时 , 才需要使用Strategy模式。
- Strategy和Context之间的通信开销 :无论各个ConcreteStrategy实现的算法是简单还是复杂, 它们都共享Strategy定义的接口。因此很可能某些 ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的 ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题 , 那么将需要在Strategy和Context之间更进行紧密的耦合。
- 策略模式将造成产生很多策略类:可以通过使用享元模式在一定程度上减少对象的数量。 增加了对象的数目 Strategy增加了一个应用中的对象的数目。有时你可以将 Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其余的状态都由 Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去。共享的 Strategy不应在各次调用之间维护状态。
模式的组成
- 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
- 抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。
- 具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。
代码实现
1, 抽象策略类
type TravelType int
const (
Bus TravelType = iota
Airport
Train
)
//抽象策略类,返回使用不同的交通方式来旅游
type Strategy interface {
TravelAlgorithm() TravelType
}
2, 具体策略类
//具体策略类,使用公共汽车,飞机,火车等三种方式进行旅游的具体实现
type BusStrategy struct {
}
func (b *BusStrategy) TravelAlgorithm() TravelType {
return Bus
}
type AirportStrategy struct {
}
func (b *AirportStrategy) TravelAlgorithm() TravelType {
return Airport
}
type TrainStrategy struct {
}
func (b *TrainStrategy) TravelAlgorithm() TravelType {
return Train
}
3, 环境类
//环境类
type Context struct {
strategy Strategy
}
func (c *Context) SetStrategy(strategy Strategy) {
c.strategy = strategy
}
func (c *Context) TravelAlgorithm() TravelType {
return c.strategy.TravelAlgorithm()
}
4, 代码调用
func TestStrategy(t *testing.T) {
//choose bus strategy
context := Context{strategy: new(BusStrategy)}
context.TravelAlgorithm()
//choose airport strategy
context.SetStrategy(new(AirportStrategy))
context.TravelAlgorithm()
//choose train strategy
context.SetStrategy(&TrainStrategy{})
context.TravelAlgorithm()
}
状态模式
定义
状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为模式。状态模式允许一个对象在其内部状态改变的时候改变其行为。这个对象看上去就像是改变了它的类一样。
参与者
- Context 定义客户感兴趣的接口。维护一个ConcreteState子类的实例,这个实例定义当前状态
- State 定义一个接口封装与context的一个特定状态相关的行为:
- ConcreteStateSubClasses 每一子类实现一个与Context的一个状态相关的行为
应用场景
采用interface实现多继承,加入管理类,方便状态切换
- 不同的状态(可能会对应相应的行为),
- 不同的行为; 间反复进行切换,则应优先考虑状态模式。
代码实现
模拟路上红绿灯切换
1, State 定义一个接口封装与context的一个特定状态相关的行为
type LightType int
const (
Red LightType = iota
Green
Yellow
)
//State 定义一个接口封装与context的一个特定状态相关的行为:
type State interface {
GetState() LightType
Last(*Context)
Next(*Context)
}
2, Context 定义客户感兴趣的接口。维护一个ConcreteState子类的实例,这个实例定义当前状态
type Context struct {
state State
}
func (c *Context) GetState() State {
return c.state
}
func (c *Context) SetState(state State) {
c.state = state
}
func (c *Context) Push() {
c.state.Last(c)
}
func (c *Context) Pull() {
c.state.Next(c)
}
3, ConcreteStateSubClasses 每一子类实现一个与Context的一个状态相关的行为
//红灯
type RedState struct {
}
func (r *RedState) GetState() LightType {
return Red
}
func (r *RedState) Last(c *Context) {
c.SetState(new(YellowState))
}
func (r *RedState) Next(c *Context) {
c.SetState(new(GreenState))
}
//绿灯
type GreenState struct {
}
func (r *GreenState) GetState() LightType {
return Green
}
func (r *GreenState) Last(c *Context) {
c.SetState(&RedState{})
}
func (r *GreenState) Next(c *Context) {
c.SetState(&YellowState{})
}
//黄灯
type YellowState struct {
}
func (r *YellowState) GetState() LightType {
return Yellow
}
func (r *YellowState) Last(c *Context) {
c.SetState(new(GreenState))
}
func (r *YellowState) Next(c *Context) {
c.SetState(new(RedState))
}
4, 调用
func TestState(t *testing.T) {
context := new(Context)
state := new(RedState)
context.SetState(state)
fmt.Println("current state:", context.GetState().GetState())
context.Pull()
fmt.Println("next state:", context.GetState().GetState())
context.Pull()
fmt.Println("next state:", context.GetState().GetState())
}
观察者模式
简介
在生活实际中,我们经常会遇到关注一个事物数据变化的情况,例如生活中的温度记录仪,当温度变化时,我们观察它温度变化的曲线,温度记录日志等。对于这一类问题,很接近java设计模式里面的“观察者模式”,它适合解决多种对象跟踪一个对象数据变化的程序结构问题。
涉及角色
- 主题(Subject)
- 观察者(Observer)
依赖jdk已有的观察者模式编写代码观测天气变化
- 主题(WeatherSubject):Observable类派生出来的子类,只需要定义各被监控的数据及getter()、setter()方法,getter方法主要用于具体观察者“拉”数据,setter方法主要用于更新、设置changed变量及通知各具体观察者进行数据响应。
- 观察者(WeatherObserver):编写具体的观察者类实现观察者接口,通过参数传递主题对象获取更新的数据。update()方法主要用于“拉”数据及处理过程。
分析依赖jdk实现的观察者模式
由java JDK实现的观察者模式来看,当在使用时感觉代码很简单,其实去看Observerable类和Observer接口的源码就知道,这些都是专家级的代码,学习了观察者模式后,下面得出一些结论:</br>
- 主题要知道哪些观察者对其进行监测,说明主题类中一定有一个集合类成员变量,添加和删除及判断这些观察者对象是否存在。
- 观察者类一定是多态的,有共同的父类接口。
- 主题完成的功能基本是固定的,添加观察者、撤销观察者、通知消息给观察者及引起观察者响应(即“拉”数据),可以抽象出来。
自定义形式写的观察者模式
- 编写观察者接口(IObserver)
- 编写主题接口(ISubject)
- 增加主题抽象类层(AbstractSubject)
- 主题子类定义被监控数据(Subject)
- 观察者对象(Observer)“拉”数据得到数据响应
代码实现
1, 观察者接口 Observer
//观察者接口
type Observer interface {
Notify(interface{})
}
2, 主题 Subject
//主题
type Subject struct {
observers []Observer
state string
}
//增加主题订阅者
func (s *Subject) Attach(observer ...Observer) {
s.observers = append(s.observers, observer...)
}
func (s *Subject) SetState(state string) {
s.state = state
//当主题状态发生变化,通过相关订阅者
for _, obs := range s.observers {
obs.Notify(s)
}
}
3, 具体观察者 ConcreteObserver
//具体观察者
type ConcreteObserver struct {
Id string
}
func (c *ConcreteObserver) Notify(subject interface{}) {
fmt.Println(c.Id, " receive ", subject.(*Subject).state)
}
4, 代码调用
func TestObserver(t *testing.T) {
//定义一个主题
subject := new(Subject)
//定义两个观察者
observer1 := &ConcreteObserver{Id: "A"}
observer2 := &ConcreteObserver{Id: "B"}
//两个观察者订阅该主题
subject.Attach(observer1, observer2)
//主题状态发生变化,通过到每一个观察者
subject.SetState("hello, world")
subject.SetState("I know")
}
空对象模式
简介
用一个空对象取代 NULL,减少对实例的检查。这样的空对象可以在数据不可用的时候提供默认的行为。
为什么使用这个模式?还需要null吗?
之所以需要这个模式是因为这个模式可以消除重复。想象一下,假如有多个消费端,每个消费端都要判断一下是否为空,而且对于为空的场景要做特殊的处理,这样就会导致很多重复。一些例子是:日志对象和缓存对象。
参与者
- AbstractObject:声明协作对象的接口,如果需要,可以实现默认行为。
- RealObject:具体的协作对象类,提供有意义的行为。
- NullObject:空对象类,继承自 AbstractObject,但接口实现不做任何事情。
- Client:请求协作对象。
总结
Null Object Pattern,作为一种被遗忘的设计模式,却有着不能被遗忘的作用
- 它可以加强系统的稳固性,能有有效地防止空指针报错对整个系统的影响,使系统更加稳定。
- 它能够实现对空对象情况的定制化的控制,能够掌握处理空对象的主动权。
- 它并不依靠Client来保证整个系统的稳定运行。
- 它通过isNull对==null的替换,显得更加优雅,更加易懂。
举例
在一个图书信息查询系统中,你调用一个方法,传过去你要查找图书的ID,然后它返回给你,你要查找的图书对象,这样你就可以调用对象的方法来输出图书的信息。
1,图书对象ConcreteBook
type ConcreteBook struct {
ID int
Name string
Author string
}
func newConcreteBook(ID int, name, author string) *ConcreteBook {
return &ConcreteBook{
ID: ID,
Name: name,
Author: author,
}
}
func (c *ConcreteBook) show() {
fmt.Println(strconv.Itoa(c.ID) + "**" + c.Name + "**" + c.Author)
}
2,工厂提供生产图书对象BookFactory
type BookFactory struct {
}
func (b *BookFactory) getBook(ID int) *ConcreteBook {
var book *ConcreteBook
switch ID {
case 1:
book = newConcreteBook(ID, "Desing-Pattern", "GoF")
case 2:
book = newConcreteBook(ID, "forget desing pattern", "Null-Object")
default:
}
return book
}
3,代码调用
func TestNullObj(t *testing.T) {
factory := new(BookFactory)
book := factory.getBook(-1)
if book == nil {
fmt.Println("nil")
} else {
book.show()
}
}
代码实现
从上面的调用我们可以看出,在客户端调用过程中,我们需要判断nil的操作,下面我们重构代码,使用空对象模式,消除对nil的判断
1,定义Book接口
type Book interface {
isNull() bool
show()
}
2,定义空对象实现
type NullBook struct {
}
func (n *NullBook) isNull() bool {
return true
}
func (n *NullBook) show() {
fmt.Println("obj is invalid")
}
3, 图书对象ConcreteBook
type ConcreteBook struct {
ID int
Name string
Author string
}
func newConcreteBook(ID int, name, author string) *ConcreteBook {
return &ConcreteBook{
ID: ID,
Name: name,
Author: author,
}
}
func (c *ConcreteBook) isNull() bool {
return false
}
func (c *ConcreteBook) show() {
fmt.Println(strconv.Itoa(c.ID) + "**" + c.Name + "**" + c.Author)
}
4, 工厂对象BookFactory
type BookFactory struct {
}
func (b *BookFactory) getBook(ID int) Book {
var book Book
switch ID {
case 1:
book = newConcreteBook(ID, "Desing-Pattern", "GoF")
case 2:
book = newConcreteBook(ID, "forget desing pattern", "Null-Object")
default:
book = &NullBook{}
}
return book
}
5,客户端调用
func TestNullObj(t *testing.T) {
factory := new(BookFactory)
book := factory.getBook(-1)
book.show()
}
备忘录模式
简介
备忘录模式也称之为【备份-恢复】模式,在不破环封装行性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态。
模式组成
1.Originator(原发器) 保存内部状态的类称为原发器。原发器可以创建一个备忘录,并存储它的当前状态,也可以使用备忘录来恢复其内部状态。
2.Memonto(备忘录) 备忘录对象不能直接被其他类使用,根据原发器来决定保存哪些内部状态。
3.Caretaker(负责人) 负责人又称管理者,它负责保存备忘录,但不能对备忘录的内容进行检查或者操作。它可以保存一个或者多个备忘录对象。
优缺点
- 优点
- 更好的封装性:备忘录模式通过使用备忘录对象,来封装原发器对象的内部状态,虽然这个对象是保存在原发器对象的外部,但是由于备忘录对象的窄接口并不提供任何方法,这样有效的保证了对原发器对象内部状态的封装,不把原发器对象的内部实现细节暴露给外部。
- 简化了原发器:备忘录模式中,备忘录对象被保存到原发器对象之外,让客户来管理他们请求的状态,从而让原发器对象得到简化。
- 窄接口和宽接口
- 缺点
- 可能会导致高开销:备忘录模式基本的功能,就是对备忘录对象的存储和恢复,它的基本实现方式就是缓存备忘录对象。这样一来,如果需要缓存的数据量很大,或者是特别频繁的创建备忘录对象,开销是很大的。
代码实现
1, 原发器
type Originator struct {
UserName string
Password string
PhoneNumber string
}
//保存备忘录
func (o *Originator) saveMemento() *Memento {
return &Memento{
UserName: o.UserName,
Password: o.Password,
PhoneNumber: o.PhoneNumber,
}
}
//恢复备忘录
func (o *Originator) restoreMemento(memento *Memento) {
o.UserName = memento.UserName
o.Password = memento.Password
o.PhoneNumber = memento.PhoneNumber
}
//显示用户信息
func (o *Originator) show(msg string) {
fmt.Println(msg)
fmt.Println("username:" + o.UserName + ",password:" + o.Password + ",phonenumber:" + o.PhoneNumber)
}
2, 备忘录
type Memento struct {
UserName string
Password string
PhoneNumber string
}
3, 负责人
type Caretaker struct {
memento *Memento
}
func (c *Caretaker) getMemento() *Memento {
return c.memento
}
func (c *Caretaker) setMemento(memento *Memento) {
c.memento = memento
}
4, 代码调用
func TestMemento(t *testing.T) {
//创建原发器
originator := Originator{
UserName: "zhangsan",
Password: "123456",
PhoneNumber: "12306",
}
//创建负责人
taker := Caretaker{}
originator.show("初始状态")
//保存状态
taker.setMemento(originator.saveMemento())
//修改密码和电话号码
originator.Password = "34567"
originator.PhoneNumber = "234556667"
originator.show("修改状态")
//恢复状态
originator.restoreMemento(taker.getMemento())
originator.show("恢复到初始状态")
}
责任链模式
简介
责任链模式是对象的行为模式。使多个对象都有机会处理请求,从而避免请求的发送者和接受者直接的耦合关系。
将这些对象连成一条链,沿着这条链传递该请求,直到有一个对象处理它为止。
责任链模式强调的是每一个对象及其对下家的引用来组成一条链,利用这种方式将发送者和接收者解耦。
角色
- 抽象处理者角色(Handler:Approver):定义一个处理请求的接口,和一个后继连接(可选)
- 具体处理者角色(ConcreteHandler:President):处理它所负责的请求,可以访问后继者,如果可以处理请求则处理,否则将该请求转给他的后继者。
- 客户类(Application):向一个链上的具体处理者ConcreteHandler对象提交请求。
优缺点
优点
- 降低耦合度 :该模式使得一个对象无需知道是其他哪一个对象处理其请求。对象仅需知道该请求会被“正确”地处理。接收者和发送者都没有对方的明确的信息,且链中的对象不需知道链的结构。
- 职责链可简化对象的相互连接 : 结果是,职责链可简化对象的相互连接。它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接受者的引用。
- 增强了给对象指派职责(Responsibility)的灵活性 :当在对象中分派职责时,职责链给你更多的灵活性。你可以通过在运行时刻对该链进行动态的增加或修改来增加或改变处理一个请求的那些职责。你可以将这种机制与静态的特例化处理对象的继承机制结合起来使用。
- 增加新的请求处理类很方便
缺点
- 不能保证请求一定被接收。既然一个请求没有明确的接收者,那么就不能保证它一定会被处理 —该请求可能一直到链的末端都得不到处理。一个请求也可能因该链没有被正确配置而得不到处理。
- 系统性能将受到一定影响,而且在进行代码调试时不太方便;可能会造成循环调用
实现
1, 抽象处理者 Handler
type Handler interface {
Request(flag bool)
}
2, 具体处理者角色1: ConcreteHandlerA
type ConcreteHandlerA struct {
next Handler
}
func (h *ConcreteHandlerA) Request(flag bool) {
fmt.Println("ConcreteHandlerA.Request()")
if flag {
h.next.Request(flag)
}
}
3, 具体处理者角色2: ConcreteHandlerB
type ConcreteHandlerB struct {
next Handler
}
func (h *ConcreteHandlerB) Request(flag bool) {
fmt.Println("ConcreteHandlerB.Request()")
if flag {
h.next.Request(flag)
}
}
4, 具体处理者角色3: ConcreteHandlerC
type ConcreteHandlerC struct {
next Handler
}
func (h *ConcreteHandlerC) Request(flag bool) {
fmt.Println("ConcreteHandlerC.Request()")
}
5, 具体调用者
func main(){
handlerC := &ConcreteHandlerC{}
handlerB := &ConcreteHandlerB{next: handlerC}
handlerA := &ConcreteHandlerA{next: handlerB}
handlerA.Request(true)
}
中介者模式
问题提出
Q: 大家都知道,电脑里面各个配件之间的交互,主要是通过主板来完成的(事实上主板有很多的功能,这里不去讨论)。试想一下,如果电脑里面没有主板,会怎样呢?
A:
- 如果电脑里面没有了主板,那么各个配件之间就必须自行相互交互,以互相传送数据,理论上说,基本上各个配件相互之间都存在交互数据的可能;
- 由于各个配件的接口不同,那么相互之间交互的时候,还必须把数据接口进行转换才能匹配上,那就更恐怖了.
软件开发中遇到的问题
如果上面的情况发生在软件开发上呢?
- 如果把每个电脑配件都抽象成为一个类或者是子系统,那就相当于出现了多个类之间相互交互,而且交互还很繁琐,导致每个类都必须知道所有需要交互的类,也就是我们常说的类和类耦合了,是不是很麻烦?
- 在软件开发中出现这种情况可就不妙了,不但开发的时候每个类会复杂,因为要兼顾其它的类,更要命的是每个类在发生改动的时候,需要通知所有相关的类一起修改,因为接口或者是功能发生了变动,使用它的地方都得变,快要疯了吧!
中介者模式定义
用一个中介对象来封装一系列的对象交互。中介者使得各个对象之间不需要显式地相互引用,从而使其耦合松散,而且可以独立的改变他们之间的交互。
应用中介者模式来解决的思路
- 仔细分析上面的问题,根本原因就在于多个对象需要相互交互,从而导致对象之间紧密耦合,这就不利于对象的修改和维护。
- 中介者模式的解决思路很简单,跟电脑的例子一样,中介者模式通过引入一个中介对象,让其它的对象都只和中介对象交互,而中介对象知道如何和其它所有的对象交互,这样对象之间的交互关系就没有了,从而实现对象之间的解耦。
- 对于中介对象而言,所有相互交互的对象,被视为同事类,中介对象就是来维护各个同事之间的关系,而所有的同事类都只是和中介对象交互。
- 每个同事对象,当自己发生变化的时候,不需要知道这会引起其它对象有什么变化,它只需要通知中介者就可以了,然后由中介者去与其它对象交互。这样松散耦合带来的好处是,除了让同事对象之间相互没有关联外,还有利于功能的修改和扩展。
- 有了中介者过后,所有的交互都封装到中介者对象里面,各个对象就不再需要维护这些关系了。扩展关系的时候也只需要扩展或修改中介者对象就可以了。
模式组成
- Mediator:中介者接口。在里面定义各个同事之间交互需要的方法,可以是公共的通讯方法,比如changed方法,大家都用,也可以是小范围的交互方法。
- ConcreteMediator:具体中介者实现对象。它需要了解并维护各个同事对象,并负责具体的协调各同事对象的交互关系。
- Colleague:同事类的定义,通常实现成为抽象类,主要负责约束同事对象的类型,并实现一些具体同事类之间的公共功能,比如:每个具体同事类都应该知道中介者对象,也就是具体同事类都会持有中介者对象,就可以定义到这个类里面。
- ConcreteColleague:具体的同事类,实现自己的业务,在需要与其它同事通讯的时候,就与持有的中介者通信,中介者会负责与其它的同事交互。
代码实现
要使用中介者模式来实现聊天室,那就要区分出同事对象和中介者对象。很明显,聊天室是作为中介者
,而每个注册到该聊天室的都是作为同事对象
1, 中介者接口
type Mediator interface {
SendUserMsg(string)
RegisterUser(*User)
}
2, 具体中介者实现对象
type ChatRoom struct {
name string
}
func (cr *ChatRoom) SendUserMsg(msg string) {
fmt.Println(cr.name + " : " + msg)
}
func (cr *ChatRoom) RegisterUser(u *User) {
u.cr = cr
}
3, 同事类
type Colleague interface {
SendMsg(string)
}
4, 具体同事类
type User struct {
name string
cr *ChatRoom
}
func (u *User) SendMsg(msg string) {
if u.cr != nil {
u.cr.SendUserMsg(u.name + " : " + msg)
}
}
5, 代码调用
func main() {
A := &User{name: "A"}
B := &User{name: "B"}
cr := &ChatRoom{name: "chatRoom1234"}
cr.RegisterUser(A)
cr.RegisterUser(B)
A.SendMsg("I am A")
B.SendMsg("I am B")
}
迭代器模式
简介
用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。
角色
- 抽象迭代子(Iterator)角色:此抽象角色定义出遍历元素所需的接口。
- 具体迭代子(ConcreteIterator)角色:此角色实现了Iterator接口,并保持迭代过程中的游标位置。
- 集合(Collection)角色:此抽象角色给出创建迭代子(Iterator)对象的接口。
- 具体集合(ConcreteCollection)角色:实现了创建迭代子(Iterator)对象的接口,返回一个合适的具体迭代子实例。
- 客户端(Application)角色:持有对聚集及其迭代子对象的引用,调用迭代子对象的迭代接口,也有可能通过迭代子操作聚集元素的增加和删除。
实现
1, 迭代角色
type Iterator struct {
arr []interface{}
pos int
}
func (c *Iterator) previous() interface{} {
if c.pos > 0 {
c.pos = c.pos - 1
}
return c.arr[c.pos]
}
func (c *Iterator) next() interface{} {
if c.pos < len(c.arr)-1 {
c.pos = c.pos + 1
}
return c.arr[c.pos]
}
func (c *Iterator) hasNext() bool {
if c.pos < len(c.arr)-1 {
return true
}
return false
}
func (c *Iterator) first() interface{} {
c.pos = 0
return c.arr[c.pos]
}
2, 具体集合角色
//需要迭代的集合
type Collection struct {
arr []interface{}
}
func NewCollection(array []interface{}) *Collection {
return &Collection{
arr: array,
}
}
func (c *Collection) iterator() *Iterator {
return &Iterator{
arr: c.arr,
pos: -1,
}
}
3, 代码调用
func main() {
arr := []interface{}{"One", "Two", "Three"}
col := NewCollection(arr)
it := col.iterator()
for ; it.hasNext(); {
fmt.Println("item : " + (it.next().(string)))
}
}
命令模式
定义
命令模式是将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作.命令模式是一种对象行为型模式,其别名为动作模式或事务模式.
角色
- Command: 命令
- Invoker: 调用者
- Receiver: 接受者
- Client: 客户端
客户端通过调用者发送命令,命令调用接收者执行相应操作
调用者→接受者→命令
优缺点
- 优点
- 降低了系统耦合度;
- 新的命令可以很容易添加到系统中去。
- 缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。
代码调用
1, 定义命令
//命令接口
type Command interface {
Run()
}
2, 定义调用者
//对命令的调用者
type Invoker struct {
commandList [] Command
}
func (i *Invoker) AddCommand(c Command) {
if i == nil {
return
}
i.commandList = append(i.commandList, c)
}
func (i *Invoker) ExecuteCommand() {
if i == nil {
return
}
for _, val := range i.commandList {
val.Run()
}
}
3, 定义接受者
type ReceiveA struct {
}
func (r *ReceiveA) Execute() {
if r == nil {
return
}
fmt.Println("for ConcreteCommandA , how to process CommandA")
}
4, 定义命令的具体实现
//命令接口实现类
type CommandA struct {
receive ReceiveA
}
func (c *CommandA) SetReceive(r ReceiveA) {
if c == nil {
return
}
c.receive = r
}
func (c *CommandA) Run() {
if c == nil {
return
}
c.receive.Execute()
}
5, 代码调用
func main() {
//调用者
invoker := &Invoker{commandList: []Command{}}
//命令具体实现
commandA := &CommandA{}
//接受者
receiveA := &ReceiveA{}
//给命令设置接受者
commandA.SetReceive(*receiveA)
//将命令加到调用者列表
invoker.AddCommand(commandA)
//调用者调用命令
invoker.ExecuteCommand()
}
解释器模式
定义
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
优点
- 1、可扩展性比较好,灵活。
- 2、增加了新的解释表达式的方式。
- 3、易于实现简单文法。
缺点
- 1、可利用场景比较少。
- 2、对于复杂的文法比较难维护。
- 3、解释器模式会引起类膨胀。
- 4、解释器模式采用递归调用方法。
使用场景
- 1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
- 2、一些重复出现的问题可以用一种简单的语言来进行表达。
- 3、一个简单语法需要解释的场景。
代码实现
1, 表达式接口
// 表达式接口
type Expression interface {
Interpret(context string) bool
}
2, 对表达式接口的实现(终结符)
// 终结符
type TerminalExpression struct {
Word string
}
func (t *TerminalExpression) Interpret(context string) bool {
if strings.Contains(context, t.Word) {
return true
}
return false
}
3, 对表达式接口的实现(或)
// 或
type OrExpression struct {
A Expression
B Expression
}
func (o *OrExpression) Interpret(context string) bool {
return o.A.Interpret(context) || o.B.Interpret(context)
}
4, 对表达式接口的实现(与)
type AndExpression struct {
A Expression
B Expression
}
func (a *AndExpression) Interpret(context string) bool {
return a.A.Interpret(context) && a.B.Interpret(context)
}
5, 代码调用
func main() {
isMail := &OrExpression{&TerminalExpression{"Robert"}, &TerminalExpression{"John"}}
isMarriedWoman := &AndExpression{&TerminalExpression{"Julie"}, &TerminalExpression{"Married"}}
fmt.Println("john is male ?", isMail.Interpret("John"))
fmt.Println("Julie is a marred woman?", isMarriedWoman.Interpret("Married Julie"))
}