[转载-调试]设计模式整理

本文简单讲一下设计模式。

一 适应设计模式

1 迭代器模式

模式介绍

提供一种方法顺序访问一个聚合对象中的各个元素,而又无须暴露该对象的内部表示。

适用场景

遍历一个聚合对象的时候。

代码示例

package main

import "fmt"

// 接口类型的别名
type SomeSlice interface {}

// 迭代器 接口
type Iterator interface {
    HasNext() bool
    Next() SomeSlice
}

// 测试的对象
type NameRepository struct {

}


func (nr *NameRepository) GetIterator() Iterator {
    nameIterator := new(NameIterator)
    nameIterator.names = []string{"zp", "lmm", "chris"}
    return nameIterator
}

// 为测试对象准备的Iterator
type NameIterator struct {
    names []string
    index int
}

// NameIterator 的 HasNext : 继承 Iterator
func (s *NameIterator) HasNext() bool {
    if s.index < len(s.names) {
        return true
    }
    return false
}

// NameIterator 的 Next : 继承 Iterator
func (s *NameIterator) Next() SomeSlice {
    if s.HasNext() {
        n := s.index
        s.index++
        return s.names[n]
    }
    return nil
}

func main()  {
    fmt.Println("test Iterator")
    nameRepository := new(NameRepository)
    for iter := nameRepository.GetIterator(); iter.HasNext(); {
        fmt.Println(iter.Next())
    }
}

2 适配器模式

模式介绍

将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适用场景

主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。

1、系统需要使用现有的类,而此类的接口不符合系统的需要。
2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。

3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)

代码示例

package main

import "fmt"

// 老的接口
type oldInterface interface {
    inertIntoDB()
}

// 老接口对应的实例
type oldInstance struct {
    name string
}

// 老实例继承老街口的方法。
func (old *oldInstance) inertIntoDB()  {
    fmt.Println("oldInstance into db")
}

// 新接口
type newInterface interface {
    saveData(Data interface{})
}

// 适配器
type Adapter struct {
    oldInterface
}

// 适配器继承新接口
func (adapter *Adapter) saveData()  {
    fmt.Println("in adapter")
    adapter.inertIntoDB()
}

func main()  {
    fmt.Println("test addaptor")
    oldIns := &Adapter{&oldInstance{name:"zp"}}
    oldIns.saveData()
}

在项目中或者常见组件中的使用

项目实战:用户判断服务,需要依赖多个数据标签,需要提前将用户各个维度的数据提前加载到库表中,多数据源统一成一个数据源时,会使用。

二 交给子类

3 模板(Template)模式

模式介绍

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

适用场景

  • 主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
  • 缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使系统更加庞大。
  • 使用场景:
  • 有多个子类共有的方法,且逻辑相同。
  • 重要的、复杂的方法,可以考虑作为模版方法。

代码示例

package main

import "fmt"

// 基础接口-模板
type baseInterface interface {
    method1()
    method2()
}

// 实例1
type instance1 struct {
    name string
}

func (i1 *instance1) method1()  {
    fmt.Println("instance " + i1.name + " method 1")
}

func (i1 *instance1) method2()  {
    fmt.Println("instance " + i1.name + " method 2")
}


// 实例2
type instance2 struct {
    name string
}

func (i *instance2) method1()  {
    fmt.Println("instance " + i.name + " method 1")
}

func (i *instance2) method2()  {
    fmt.Println("instance " + i.name + " method 2")
}

func main()  {
    fmt.Println("Template pattern begin")
    i1 := instance1{name:"1"}
    i1.method1()
    i1.method2()

    i2 := instance2{name:"2"}
    i2.method1()
    i2.method2()
}

在常用组件中的适用

项目中的案例: 用户判断服务中,有多个不同的策略,每个策略都执行获取数据源、判断的逻辑,所以将这两个方法抽象为模板,然后每个策略 都去实现这个模板接口。

4 工厂(Factory)模式

模式介绍

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

适用场景

主要解决:主要解决接口选择的问题。
何时使用:我们明确地计划不同条件下创建不同实例时
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。

代码示例

package main

import "fmt"

type Shape interface {
    Draw()
}

type Rectangle struct {

}

func (r *Rectangle)Draw()  {
    fmt.Println("rectangle draw")
}

type Squre struct {

}

func (s *Squre)Draw()  {
    fmt.Println("Squre draw")
}

// 工厂模式
type SimplenessFactory struct {

}

// 获取具体的类型
func (factory *SimplenessFactory) getShapeInstance(typename string) Shape {
    switch typename {
    case "rectange":
        rectangle := new(Rectangle)
        return rectangle
        fallthrough
    case "squre" :
        squre := new(Squre)
        return squre
    default:
        return nil
    }
}

func main()  {
    fmt.Println("factory pattern demo")
    shape := new(SimplenessFactory).getShapeInstance("rectange")
    shape.Draw()
    shape2 := new(SimplenessFactory).getShapeInstance("squre")
    shape2.Draw()
}

在常用组件中的适用

项目中的案例:用户判断服务中,有多个策略,具体使用哪个策略呢? 根据具体的促销用户标识,去工厂类中获取具体的策略实例。

三 生成实例

5 单例(Singleton)模式

模式介绍

在应用这个模式时,单例对象的类必须保证只有一个实例存在。保证一个类仅有一个实例,并提供一个访问它的全局访问点。

适用场景

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

代码示例

懒汉模式:

type SingleObject struct {
    Count int
}
 
var singleObj *SingleObject
 
func GetInstance1() *SingleObject {
    if singleObj == nil {
        singleObj = new(SingleObject)
    }
    return singleObj
}

懒汉模式存在线程安全问题,在第3步的时候,如果有多个线程同时调用了这个方法, 那么都会检测到instance为nil,就会创建多个对象,所以出现了饿汉模式。

饿汉模式:

var singleObj *SingleObject
func init() {
    singleObj = new(SingleObject)
}
 
func GetInstance2() *SingleObject {
    return singleObj
}

饿汉模式将在包加载的时候就创建单例对象,当程序中用不到该对象时,浪费了一部分空间。和懒汉模式相比,更安全,但是会减慢程序启动速度。

双重检查机制:

var lock *sync.Mutex = &sync.Mutex{}
func GetInstance3() *SingleObject {
    if singleObj == nil {
        lock.Lock()
        defer lock.Unlock()
        singleObj = new(SingleObject)
    }
    return singleObj
}

在常用组件中的适用

一般一个项目中,数据库的实例就采用单例模式。

6 原型(Prototype)模式

模式介绍

适用场景

代码示例

在常用组件中的适用

7 建造者(Builder)模式

模式介绍

造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。

适用场景

主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

何时使用:一些基本部件不会变,而其组合经常变化的时候。

如何解决:将变与不变分离开。

代码示例

建造者模式中主要有以下几个角色需要我们了解:
1.Product 需要创建的复杂对象
2.Builder 用来规范建造者
3.ConcreteBuilder 具体的Builder实现,主要用来根据不用的业务来创建对象的所有组件。
4.Director 用来规范复杂对象的创建流程

构建结构体:

package BuilderPattern
 
type Computer struct {
    CPU      string
    Memory   string
}
 
func (c *Computer) SetCPU(cpu string) {
    c.CPU = cpu
}
 
func (c *Computer) GetCPU() string {
    return c.CPU
}
 
func (c *Computer) SetMemory(memory string) {
    c.Memory = memory
}
 
func (c *Computer) GetMemory() string {
    return c.Memory
}

Builder主要是用来规范建造者:

package BuilderPattern
 
type Builder interface {
  SetCPU(cpu string) Builder
  SetMemory(memory string) Builder
  Build() *Computer
}

ComputerBuilder用来根据不用的业务来完成要创建对象的组建的创建。

type ComputerBuilder struct {
  computer *Computer
}
 
func (c *ComputerBuilder) SetCPU(cpu string) Builder {
  if c.computer == nil {
    c.computer = new(Computer)
  }
  c.computer.SetCPU(cpu)
  return c
}
 
func (c *ComputerBuilder) SetMemory(memory string) Builder {
  if c.computer == nil {
    c.computer = new(Computer)
  }
  c.computer.SetMemory(memory)
  return c
}
 
func (c *ComputerBuilder) Build() *Computer {
  return c.computer
}

Director规范复杂对象的创建流程:

package BuilderPattern
 
type Director struct {
  Builder Builder
}
 
func (d Director) Create(cpu string, memory string) *Computer {
  return d.Builder.SetCPU(cpu).SetMemory(memory).Build()
}

测试:

func testBuilderPattern() {
    builder := new(BuilderPattern.ComputerBuilder)
    director := BuilderPattern.Director {Builder: builder}
    computer := director.Create("I7", "32G",)
    fmt.Println(*computer)
}

自己实现了一个简版:

package main

import "fmt"

// 最原始的实体
type Computer struct {
    cpu string
    memory string
}

func (c *Computer) setCpu(cpu string)  {
    c.cpu = cpu
}

func (c *Computer) setMemory(memory string)  {
    c.memory = memory
}

// builder 接口
type builderInterface interface {
    setCpu(cpu string) builderInterface
    setMemory(memory string) builderInterface
    Builder() *Computer
}

// builder 实例
type builderInstance struct {
    computer *Computer
}

func (b *builderInstance) setCpu(cpu string) builderInterface  {
    if b.computer == nil {
        b.computer = new(Computer)
    }
    b.computer.cpu = cpu
    return b
}

func (b *builderInstance) setMemory(memory string) builderInterface  {
    if b.computer == nil {
        b.computer = new(Computer)
    }
    b.computer.memory = memory
    return b
}

func (b *builderInstance) Builder() *Computer {
    return b.computer
}

func main()  {
    fmt.Println("builder pattern")
    builderIns := new(builderInstance)
    builderIns.setCpu("intel").setMemory("4G")
    fmt.Println(builderIns.computer)
}

在常用组件中的适用

一般在一些基础组件中,会用到。比如thrift中,定义对象时会用到。

8 抽象(Abstract)模式

模式介绍

适用场景

代码示例

在常用组件中的适用

四 分开思考

9 桥接(Bridge)模式

模式介绍

适用场景

代码示例

在常用组件中的适用

10 策略(Strategy)模式

模式介绍

在策略模式中,一个类的行为或其他算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的context对象。策略对象改变context对象的执行算法。

意图:定义一系列的算法,把他们一个个封装起来,并且使它们可互相替换。

适用场景

主要解决:在有多种算法相似的情况下,使用if else所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分他们的只是他们直接的行为
如何解决:将这些算法封装成一个一个的类,任意的替换。
关键代码:实现同一个接口。

优点:算法可以自由切换、避免使用多重条件判断条件、扩展性好
缺点:策略类会增多、所有策略类都需要对外暴露

使用场景

如果在一个系统里面有许多类,他们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
一个系统需要动态地在几种算法中选择一种
如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

代码示例

demo 类图.png
package main

import "fmt"

type Strategy interface {
    Do(param1, param2 int) int
}

type AddInstance struct {
}

func (add *AddInstance) Do(param1, param2 int) int  {
    return param1 + param2
}

type SubInstance struct {
}

func (sub *SubInstance) Do(param1, param2 int) int  {
    return param1 - param2
}

type MultiInstance struct {
}

func (mul *MultiInstance) Do(param1, param2 int) int  {
    return param1 * param2
}

// Context 很重要
type Context struct {
    strategy Strategy
}


func (c *Context) Context(strategy Strategy) *Context {
    c.strategy = strategy
    return c
}

func (c *Context) ExecuteStrategy(num1 int, num2 int) int {
    return c.strategy.Do(num1, num2)
}

func main()  {
    fmt.Println("Strategy pattern")

    context := new(Context).Context(new(AddInstance))
    fmt.Println("10+5:", context.ExecuteStrategy(10, 5))

    context.Context(new(SubInstance))
    fmt.Println("10-5:", context.ExecuteStrategy(10, 5))

    context.Context(new(MultiInstance))
    fmt.Println("10*5:", context.ExecuteStrategy(10, 5))

}

在常用组件中的适用

项目实战: 在用户服务中,会涉及到标签数值的计算,数值计算就用到了策略模式。

五 一致性

11 Composite(Composite)模式

模式介绍

适用场景

代码示例

在常用组件中的适用

12 装饰器(Decorateor)模式

模式介绍

装饰器设计模式允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供额外的功能。

适用场景

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类的方式更为灵活。

主要解决:一般的,我们为了扩展一个类经常使用继承的方式实现,由于继承为类引用静态特征,并且随着扩展功能的增加,子类会很庞大。

何时使用:在不向增加很多子类的情况下扩展类。

如何解决:将具体功能职责划分,同时继承装饰者模式。

关键代码

Component 类充当抽象角色,不应该具体实现。
修饰类引用和继承 Component 类,具体扩展类重写父类方法。

优点:装饰结构和被装饰结构可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:多层装饰比较复杂

代码示例

demo 图片.png
package main

import "fmt"

type Shape interface {
    Draw1()
}

type Rectangle struct {
}

func (r *Rectangle) Draw1() {
    fmt.Println("Shape: Rectangle")
}

type Circle struct {
}

func (c *Circle) Draw1() {
    fmt.Println("Shape: Circle")
}

type ShapeDecorator struct {
    decoratedShape Shape
}

func (s *ShapeDecorator) ShapeDecorator(decoratedShape Shape) {
    s.decoratedShape = decoratedShape
}

func (s *ShapeDecorator) Draw1() {
    s.decoratedShape.Draw1()
}

type RedShapeDecorator struct {
    shapeDecorator ShapeDecorator
}

func (s *RedShapeDecorator) RedShapeDecorator(decoratedShape Shape) {
    s.shapeDecorator.ShapeDecorator(decoratedShape)
}

func (s *RedShapeDecorator) Draw1() {
    s.shapeDecorator.Draw1()
    s.setRedBorder(s.shapeDecorator.decoratedShape)
}

func (s *RedShapeDecorator) setRedBorder(decoratedShape Shape) {
    fmt.Println("Border Color: Red")
}

func main()  {
    fmt.Println("Decorator Pattern")
    circle := Circle{}
    redCircle := RedShapeDecorator{}
    redCircle.RedShapeDecorator(new(Circle))

    redRectangle := RedShapeDecorator{}
    redRectangle.RedShapeDecorator(new(Rectangle))

    fmt.Println("Circle with normal border")
    circle.Draw1()
    fmt.Println("Circle of red border")
    redCircle.Draw1()
    fmt.Println("Rectangle of red border")
    redRectangle.Draw1()

}

在常用组件中的适用

六 访问数据结构

13 访问者(Visitor)模式

模式介绍

适用场景

代码示例

在常用组件中的适用

14 责任链(Chain of Resonsibility)模式

模式介绍

顾名思义,责任链模式为请求创建了一个接受者对象的链,这种模式给予请求的类型,对请求的发送者和接受者进行解偶。这种类型的设计模式属于行为模式。

在这种模式中,通常每个接受者都包含对另一个接受者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传递给下一个接收者,以此类推。

意图:避免请求发送者与接受者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

适用场景

主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解偶了。

何时使用:在处理消息的时候以过滤很多道。

如何解决:拦截的类都实现统一接口。

优点

1、降低耦合度。它将请求的发送者和接收者解耦。
2、简化了对象。使得对象不需要知道链的结构。
3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
4、增加新的请求处理类很方便。

缺点

1、不能保证请求一定被接收。
2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
3、可能不容易观察运行时的特征,有碍于除错。

代码示例

demo 类图.png

各角色实现:

package chainofresponsibility

import "fmt"

type Request struct {
    RequestType    string
    RequestContent string
    Number         int
}

type Manager interface {
    SetNext(next Manager)
    RequestHandler(request Request)
}

type CommonManager struct {
    Manager
    Name string
}

func (cm *CommonManager) SetNext(next Manager) {
    cm.Manager = next
}

func (cm *CommonManager) RequestHandler(request Request) {
    if request.RequestType == "请假" && request.Number <= 2 {
        fmt.Printf("%s: %s 数量 %d 已批准\n", cm.Name, request.RequestContent, request.Number)
    } else {
        if cm.Manager != nil {
            cm.Manager.RequestHandler(request)
        }
    }
}

type MajorManager struct {
    Manager
    Name string
}

func (mm *MajorManager) SetNext(next Manager) {
    mm.Manager = next
}

func (mm *MajorManager) RequestHandler(request Request) {
    if request.RequestType == "请假" && request.Number <= 5 {
        fmt.Printf("%s: %s 数量 %d 已批准\n", mm.Name, request.RequestContent, request.Number)
    } else {
        if mm.Manager != nil {
            mm.Manager.RequestHandler(request)
        }
    }
}

type GeneralManager struct {
    Manager
    Name string
}

func (gm *GeneralManager) SetNext(next Manager) {
    gm.Manager = next
}

func (gm *GeneralManager) RequestHandler(request Request) {
    if request.RequestType == "请假" {
        fmt.Printf("%s: %s 数量 %d 已批准\n", gm.Name, request.RequestContent, request.Number)
    } else if request.RequestType == "加薪" && request.Number <= 500 {
        fmt.Printf("%s: %s 数量 %d 已批准\n", gm.Name, request.RequestContent, request.Number)
    } else if request.RequestType == "加薪" && request.Number > 500 {
        fmt.Printf("%s: %s 数量 %d 再说吧\n", gm.Name, request.RequestContent, request.Number)
    }
}

测试

func main() {
    wenxiang := &chainofresponsibility.CommonManager{
        Name: "文祥",
    }
    xiaoyun := &chainofresponsibility.MajorManager{
        Name: "晓云",
    }
    yuanlei := &chainofresponsibility.GeneralManager{
        Name: "苑磊",
    }
    wenxiang.SetNext(xiaoyun)
    xiaoyun.SetNext(yuanlei)

    request := chainofresponsibility.Request{
        RequestType:    "请假",
        RequestContent: "小菜请假",
        Number:         2,
    }
    wenxiang.RequestHandler(request)

    request = chainofresponsibility.Request{
        RequestType:    "请假",
        RequestContent: "小菜请假",
        Number:         5,
    }
    wenxiang.RequestHandler(request)

    request = chainofresponsibility.Request{
        RequestType:    "加薪",
        RequestContent: "小菜请求加薪",
        Number:         500,
    }
    wenxiang.RequestHandler(request)

    request = chainofresponsibility.Request{
        RequestType:    "加薪",
        RequestContent: "小菜请求加薪",
        Number:         1000,
    }
    wenxiang.RequestHandler(request)
}

在常用组件中的适用

项目实战: 数据处理模块中,会经过多个数据过滤器,每个过滤器都有相同的输入和输出,这就是一种责任链思想的实现。

七 简单化

15 外观(Facade)模式

模式介绍

外观设计模式隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
为子系统中的一组接口提供一个一致的界面或调用方式,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

适用场景

何时使用
客户端不需要知道系统内部的复杂联系,整个系统只需要提供一个接待员即可。
可定义系统的入口
关键代码:在客户端和复杂系统之间加一层,这一层将调用顺序、依赖关系等处理好。

优点

减少系统互相依赖
提高灵活性
提高了安全性

缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

使用场景

为复杂的模块或子系统提供外界访问的模块
子系统相对独立
预防低水平人员带来的风险

代码示例

facadePattern demo.png
package main

import "fmt"

type shape interface {
    Draw()
}

type Circle struct{}

func (c *Circle) Draw()  {
    fmt.Println("circle draw")
}

type Rectangle struct {
}

func (r *Rectangle) Draw()  {
    fmt.Println("rectangle draw")
}

type ShapeMaker struct {
    CircleComponent shape
    RectangleComponent shape
}

func (s *ShapeMaker) DrawCircle()  {
    s.CircleComponent.Draw()
}

func (s *ShapeMaker) DrawRectangle()  {
    s.RectangleComponent.Draw()
}

func (s *ShapeMaker) newShapeMaker() *ShapeMaker {
    s.RectangleComponent = new(Rectangle)
    s.CircleComponent = new(Circle)
    return s
}
func main()  {
    fmt.Println("facade pattern")
    s := new(ShapeMaker)
    s.newShapeMaker()
    s.DrawCircle()
    s.DrawRectangle()
}

在常用组件中的适用

这个比较常见,对子类封装一下对外提供服务。

16 仲裁者(Mediator)模式

模式介绍

适用场景

代码示例

在常用组件中的适用

八 管理状态

17 观察者(Observer)模式

模式介绍

当对象间存在一对多关系时,则使用观察者模式。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。

适用场景

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象的状态发生改变,所有的依赖对象都将得到通知,进行广播通知。

如何解决:使用面向对象的技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个slice存在观察者们。

优点

  • 观察者和被观察者是抽象耦合关系
  • 建立一套触发机制。

缺点

如果一个被观察者对象有很多的直接就和间接的观察者的话,将所有的观察者都通知到会花费很多时间
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发他们之间进行循环调用,可能会导致系统崩溃。
观察者模式没有相应的机制让观察者直到所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化

使用场景

  • 一个抽象模式有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使他们可以各自独立改变和复用
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象,可以使用观察者模式创建一种链式触发机制。

代码示例

观察者模式使用三个类Subject、Observer和Client。Subject对象带有绑定观察者到Client对象和从Client对象解绑观察者的方法。我们创建Subject类、Observer抽象和扩展了抽象类Observer的实体类。

ObserverPatternDemo,我们的演示类使用 Subject 和实体类对象来演示观察者模式。


observerDemo.png
package main

import (
    "fmt"
)

// 实体
type Subject struct {
    observers []Observer
    state int
}

func (s *Subject) GetState() int{
    return s.state
}

// 修改实体状态,需要通知所有观察者
func (s *Subject) SetState(state int)  {
    s.state = state
    s.NotifyAllObservers()
}

func (s *Subject) Attach(observer Observer)  {
    s.observers = append(s.observers, observer)
}

func (s *Subject) NotifyAllObservers() {
    for _, observer := range s.observers {
        observer.Update()
    }
}

// 观察者
type Observer interface {
    Update()
}

type BinaryObserver struct {
    subject *Subject
}

func (b *BinaryObserver) BinaryObserver(subject *Subject)  {
    b.subject = subject
}

func (b *BinaryObserver) Update()  {
    fmt.Println("Binary string:" , b.subject.state)
}

type OctalObserver struct {
    subject *Subject
}

func (o *OctalObserver) Observer(subject *Subject)  {
    o.subject = subject
}

func (o *OctalObserver) Update()  {
    fmt.Println("Octal string:" , o.subject.state)
}

type HexObserver struct {
    subject *Subject
}

func (h *HexObserver) Observer(subject *Subject)  {
    h.subject = subject
}

func (h *HexObserver) Update()  {
    fmt.Println("Hex string:" , h.subject.state)
}

func main()  {
    fmt.Println("learn observer pattern")
    // 定义一个subject
    subject := new(Subject)
    // 定义二进制观察者,并注册
    bObserver := new(BinaryObserver)
    bObserver.subject = subject
    subject.Attach(bObserver)

    // 定义八进制观察者,并注册
    oObserver := new(OctalObserver)
    oObserver.subject = subject
    subject.Attach(oObserver)

    // 定义十六进制观察者,并注册
    hObserver := new(HexObserver)
    hObserver.subject = subject
    subject.Attach(hObserver)

    // 修改状态
    subject.SetState(10)
}

在常用组件中的适用

18 Memento(Memento)模式

模式介绍

适用场景

代码示例

在常用组件中的适用

19 State(State)模式

模式介绍

适用场景

代码示例

在常用组件中的适用

九 避免浪费

20 Flyweight(Flyweight)模式

模式介绍

适用场景

代码示例

在常用组件中的适用

21 代理(Proxy)模式

模式介绍

在代理模式中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有对象的对象,以便向外界提供功能接口
意图:为其他对象提供一种代理以控制对这个对象的访问。

适用场景

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:向在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

优点:职责清晰、高扩展性、智能化

缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的代理速度变慢。
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

使用场景

按职责来划分,通常有以下使用场景:
1、远程代理。
2、虚拟代理。
3、Copy-on-Write 代理。
4、保护(Protect or Access)代理。
5、Cache代理。
6、防火墙(Firewall)代理。
7、同步化(Synchronization)代理。
8、智能引用(Smart Reference)代理。

注意事项

  • 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
  • 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

代码示例

题目描述:

创建一个Image接口和实现了Image接口的实体类。ProxyImage是一个代理类,减少Reallmage对象加载的内存占用。
ProxyPatternDemo,我们演示类使用ProxyImage来获取要加载的Image对象,并按照需求来进行显示。

proxyDemo.png
package ProxyPattern
 
import "fmt"
 
type Image interface {
    Display()
}
 
type RealImage struct {
    fileName string
}
 
func (r *RealImage) Display() {
    fmt.Println("Displaying " + r.fileName)
}
 
func (r *RealImage) loadFromDisk(fileName string) {
    fmt.Println("Loading " + r.fileName)
}
 
func NewRealImage(fileName string) *RealImage {
    realImage := new(RealImage)
    realImage.fileName = fileName
    realImage.loadFromDisk(fileName)
    return realImage
}
 
type ProxyImage struct {
    fileName  string
    realImage *RealImage
}
 
func (r *ProxyImage) Display() {
    if r.realImage == nil {
        r.realImage = NewRealImage(r.fileName)
    }
    r.realImage.Display()
}
 
func NewProxyImage(fileName string) *ProxyImage {
    realImage := new(ProxyImage)
    realImage.fileName = fileName
    return realImage
}

func main() {
    proxyImage := ProxyPattern.NewProxyImage("1.jpg")
    proxyImage.Display()
    fmt.Println("...")
    proxyImage.Display()
}

在常用组件中的适用

十 用类来表现

22 Command(Command)模式

模式介绍

命令模式是一种数据驱动的设计模式,它属于行为模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

意图:将一个请求封装成一个对象,从而使得您可以用不同的请求对客户进行参数化。

适用场景

主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合关系,但某些场合,比如需要对行为进行记录、撤销或重做、事物等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

如何解决:通过调用者调用接受者执行命令,顺序:调用者➡️接收者➡️命令。

优点:降低了系统的耦合度、新的命令可以很容易添加到系统中去

缺点:使用命令模式可能会导致某些系统有过多的具体命令类。

使用场景:认为是命令的地方都可以使用命令模式。

代码示例

问题描述:

我们首先创建作为命令的接口Order,然后创建作为请求的Stock类。实体命令类BuyStock和SellStock,实现了Order接口,将执行实际的命令处理。创建作为调用对象的类Broker,它接受订单并能下订单。
Broker对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。CommandPatternDemo,我们的演示类使用Broker类来演示命令模式。

CommandDemo.png
package CommandPattern
 
type Order interface {
    Execute()
}
 
type BuyStock struct {
    abcStock *Stock
}
 
func (b *BuyStock) BuyStock(abcStock *Stock) {
    b.abcStock = abcStock
}
 
func (b *BuyStock) Execute() {
    b.abcStock.Buy()
}
 
type SellStock struct {
    abcStock *Stock
}
 
func (s *SellStock) SellStock(abcStock *Stock) {
    s.abcStock = abcStock
}
 
func (s *SellStock) Execute() {
    s.abcStock.Sell()
}

type Stock struct {
    Name     string
    Quantity int
}
 
func NewStock() *Stock {
    stock := new(Stock)
    stock.Name = "ABC"
    stock.Quantity = 10
    return stock
}
 
func (s *Stock) Buy() {
    fmt.Println("Stock [ Name: ", s.Name, ", Quantity: ", s.Quantity, " ] bought")
}
 
func (s *Stock) Sell() {
    fmt.Println("Stock [ Name: ", s.Name, ",Quantity: ", s.Quantity, " ] sold")
}

type Broker struct {
    orders []Order
}
 
func (b *Broker) TakeOrder(order Order) {
    b.orders = append(b.orders, order)
}
 
func (b *Broker) PlaceOrders() {
    for _, order := range b.orders {
        order.Execute()
    }
}

func main() {
    abcStock := CommandPattern.NewStock()
 
    buyStockOrder := new(CommandPattern.BuyStock)
    buyStockOrder.BuyStock(abcStock)
 
    sellStockOrder := new(CommandPattern.SellStock)
    sellStockOrder.SellStock(abcStock)
 
    broker := new(CommandPattern.Broker)
    broker.TakeOrder(buyStockOrder)
    broker.TakeOrder(sellStockOrder)
 
    broker.PlaceOrders()
}

23 解释器(Interpreter)模式

模式介绍

适用场景

代码示例

在常用组件中的适用

十一 参考文献

https://blog.csdn.net/weixin_40165163/article/category/8989172
适配器模式: https://blog.csdn.net/m0_38132420/article/details/78268210
责任链模式:
https://blog.csdn.net/cloudUncle/article/details/84865967
极客时间上的一节关于设计模式的介绍https://time.geekbang.org/column/article/8624

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,034评论 1 32
  • javascript设计模式与开发实践 设计模式 每个设计模式我们需要从三点问题入手: 定义 作用 用法与实现 单...
    穿牛仔裤的蚊子阅读 3,936评论 0 13
  • 创建型模式 工厂模式 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设...
    隔墙送来秋千影阅读 2,603评论 0 11
  • 创建型模式 工厂模式 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设...
    liuyang7519阅读 301评论 0 2
  • 9.30,我的九月最后一天,你都忙些什么?十月一长假马上到来,你有什么计划吗? 九月,是我遇见简书的季节。...
    孤假友人阅读 123评论 0 1