每周一个GoLang设计模式之策略模式

这篇文章遵循GoF书中的脉络,本篇是这个系列的第2篇:策略模式(Strategy),这周很开心可以有时间来更新这个系列,希望下周可以继续更新。欢迎大家访问我的博客,代码可以在@Zuozuohao下载。

GoF在第二章通过设计一个Lexi的文档编辑器来介绍设计模式的使用,在上一篇文章每周一个GoLang设计模式之组合模式,中我们简单的实现了组合模式的Lexi的文本编辑器。这一周我们将继续沿着GoF的脚步实现下一个设计模式:策略模式(Strategy)。

在软件开发中也常常遇到实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同进行选择。一种常用的方法是在用户代码中进行硬编码(Hard Coding),这样给后续的扩展和维护带来大量困难。

在这种情况下就可以使用策略模式,将多种算法进行封装,只暴露给外界固定数目的通用算法接口,这样就可以在后续的工作中在算法内部进行维护和扩展,避免更改用户端的代码实现,从而减小代码维护的成本,降低模块间的耦合程度。

问题提出

GoF认为在构建好Lexi文的本编辑器的物理结构之后,面临的一个问题是构造一个
特殊物理结构,该结构对应于一个恰当地格式化了的文档,以便能够在视图中按着指定的方式进行显示。

因为格式化算法趋于复杂化,因而可以考虑将它们包含于文档结构之中,但最好是将它
们彻底独立于文档结构之外。理想情况下,我们能够自由地增加一个Glyph子类而不用考虑格式算法。反过来,增加一个格式算法不应要求修改已有的图元类。

可以定义一个封装格式化算法的对象的类层次结构。类层次结构的根结点将定义支持许多格式化算法的接口,每个子类实现这个接口以执行特定的算法。那时就能让Glyph子类对象自动使用给定算法对象来排列其子图元。

GoF为能封装格式化算法的对象定义一个Compositor类。它的接口可让Compositor获知何时去格式化哪些图元。它所格式化的图元是一个被称为Composition的特定图元的各个子图元。一个Composition在创建时得到一个Compositor子类实例,并在必要的时候(如用户改变文档的时候)让Compositor对它的图元作Compose操作。下图描述了Composition类和Compositor类之间的关系。
Strategy1.PNG

一个未格式化的Composition对象只包含组成文档基本内容的可见图元。它并不包含像行
和列这样的决定文档物理结构的图元。Composition对象只在刚被创建并以待格式化的图元进行初始化后,才处于这种状态。

当Composition需要格式化时,调用它的Compositor的Compose操作。Compositor依次遍历Composition的各个子图元,根据分行算法插入新的行和列图元。下图显示了得到的对象结构。图中由Compositor创建和插入到对象结构中的图元以灰色背景显示。
Strategy2.PNG

每一个Compositor子类都能实现一个不同的分行算法。例如,一个Simple Compositor可以
执行得很快,而不考虑像文档“色彩”这样深奥的东西。好的色彩意味着文本和空白的平滑
分布。一个TeXCompositor会实现完全的TEX算法,会考虑像色彩这样的东西,而以较长的格式化时间作为代价。

Compositor-Composition类的分离确保了支持文档物理结构的代码和支持不同格式化算法
的代码之间的分离。我们能增加新的Compositor子类而不触及Glyph类,反之亦然。事实上,我们通过给Composition的基本图元接口增加一个settCompositor操作,即可在运行时刻改变分行算法。

**Golang数据结构设计

这里我们把Composition作为通用类作为暴露类,方便用户进行调用,而具体的格式化执行策略由具体类:

1. ArrayCompositor
2. TexCompositor
3. SimpleCompositor

执行。

type Composition struct {}

type ArrayCompositor struct {
    *Composition
}
type TexCompositor struct {
    *Composition
}
type SimpleCompositor struct {
    *Composition
}

Golang接口实现

我们将Compositer接口作为暴露接口,这个接口对应于GoF策略模式中Compositor的设计。SetCompositer方法赋予不同图元不同的格式化策略,Compose方法执行具体的格式化流程。

type Compositer interface {
    SetCompositer(composition interface{})
    Compose()
}

**Golang完整代码

这里为了避免冗余的代码,没有为每种Compositor数据进行方法覆写,只是利用了匿名嵌入实现了不同Compositor数据的区分,这在实际中实际中是不存在的。

package main

import (
    "fmt"
    "reflect"
)

type Compositer interface {
    SetCompositer(composition interface{})
    Compose()
}

type Composition struct{}

func (com *Composition) SetCompositer(composition interface{}) {
    fmt.Println("Here is seting ", reflect.TypeOf(composition), "before formattig")
}

func (com *Composition) Compose() {
    fmt.Println("Here is formatting")
}

func NewCompositor(strategy string) Compositer {
    composition := new(Composition)
    var g Compositer
    switch strategy {
    case "Array":
        g = &ArrayCompositor{composition}
    case "Tex":
        g = &TexCompositor{composition}
    case "Simple":
        g = &SimpleCompositor{composition}
    }
    return g
}

type ArrayCompositor struct {
    *Composition
}
type TexCompositor struct {
    *Composition
}
type SimpleCompositor struct {
    *Composition
}

func main() {
    c1 := NewCompositor("Array")
    c1.SetCompositer(c1)
    c1.Compose()
}

输出:

Here is seting  *main.ArrayCompositor before formattig
Here is formatting

当然你可以随意改变一下main函数里面的策略执行方式,点击这里可以试一下

策略模式

策略模式是对路径选择算法的封装,对外暴露一个通用的接口,降低了客户端代码与算法代码间的耦合程度,从而为后续的开发和维护带来了极大的方便和效益。
模式的主要参与者是Strategy对象(这些对象中封装了不同的算法)和它们的操作环境
Strategy模式应用的关键点在于为Strategy和它的环境设计足够通用的接口。

非常感谢您读完这篇冗长的文章,如有错误之处请指出,我会尽快修改,谢谢!

  1. 每周一个Golang设计模式之组合模式

其他链接
C的面向对象编程

共 6 个回复


billiebing

学习了。总结的很好呀。

# 3

billiebing

期待下周的大作

# 4