Go语言基础:sort包的使用与策略模式

323 阅读4分钟

策略模式

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

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

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

主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。

如何解决:将这些算法封装成一个一个的类,任意地替换。(Go语言中甚至不用封装成类)

关键代码:实现同一个接口。

优点:

  1. 算法可以自由切换。
  2. 避免使用多重条件判断。
  3. 扩展性良好。

缺点:

  1. 策略类会增多。
  2. 所有策略类都需要对外暴露。

使用场景:

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

一等公民

Go语言中,作为一等公民的函数具有如下特征:

  1. 函数允许多返回值,这是与目前主流的C/C++、Java存在差异的。
  2. 函数本身可以作为值进行传递
  3. 函数可以作为变量的值
  4. 函数可以作为参数和返回值
  5. 支持匿名函数(没有名字的函数)一般用于工厂模式。
  6. 可以满足接口。

代码实例

假设我们现在有一个自定义结构体,其中有多个字段。

在不同的场景,我们需要按照不同的字段进行排序。

结合策略模式一等公民函数的特征,我们可以写出如下代码:

结构体

首先,我们定义一个需要对其进行排序的结构体:

type earthMass float64
type au float64

// Planet定义了太阳系行星的属性。
type Planet struct {
        name     string
        mass     earthMass
        distance au
}

然后,我们定义一个封装Planet与排序算法的结构体:

// planetSorter封装一个By()函数和一组要排序的Planet。
type planetSorter struct {
        planets []Planet
        by func(p1, p2 *Planet) bool // Less()方法
}

排序模板

由于函数是一等公民,所以我们可以将函数声明为一个新的类型,并对该类型添加一个排序算法。

这个排序算法只是一个模板,具体的排序实现要根据By对象来决定。

// By是Less()函数的类型,它定义了Planet参数的顺序。
type By func(p1, p2 *Planet) bool

// Sort()是函数类型By的一个方法,它根据函数对参数切片进行排序。
func (by By) Sort(planets []Planet) {
        ps := &planetSorter{
                planets: planets,
                by: by, // Sort()方法的接收器是定义排序顺序的函数。
        }
        sort.Sort(ps)
}

排序接口

由于使用sort包中的排序函数时要实现sort.Interface接口。

我们为planetSorter结构体实现Len()Swap()Less()方法。

注意:实现Less()方法时,是通过调用by()函数来实现的。

func (s *planetSorter) Len() int {
        return len(s.planets)
}

func (s *planetSorter) Swap(i, j int) {
        s.planets[i], s.planets[j] = s.planets[j], s.planets[i]
}

// 通过调用分类器中的“by”函数来实现的。
func (s *planetSorter) Less(i, j int) bool {
        return s.by(&s.planets[i], &s.planets[j])
}

使用

var planets = []Planet{
        {"Mercury", 0.055, 0.4},
        {"Venus", 0.815, 0.7},
        {"Earth", 1.0, 1.0},
        {"Mars", 0.107, 1.5},
}

// 演示了一种使用可编程排序条件对结构类型进行排序的技术。
func main() {
        // 对Planet排序的函数。
        name := func(p1, p2 *Planet) bool {
                return p1.name < p2.name
        }
        mass := func(p1, p2 *Planet) bool {
                return p1.mass < p2.mass
        }
        distance := func(p1, p2 *Planet) bool {
                return p1.distance < p2.distance
        }
        decreasingDistance := func(p1, p2 *Planet) bool {
                return distance(p2, p1)
        }

        // 根据不同的标准对Planet进行排序。
        By(name).Sort(planets)
        fmt.Println("By name:", planets)

        By(mass).Sort(planets)
        fmt.Println("By mass:", planets)

        By(distance).Sort(planets)
        fmt.Println("By distance:", planets)

        By(decreasingDistance).Sort(planets)
        fmt.Println("By decreasing distance:", planets)

        // 输出: 
	// By name: [{Earth 1 1} {Mars 0.107 1.5} {Mercury 0.055 0.4} {Venus 0.815 0.7}]
        // By mass: [{Mercury 0.055 0.4} {Mars 0.107 1.5} {Venus 0.815 0.7} {Earth 1 1}]
        // By distance: [{Mercury 0.055 0.4} {Venus 0.815 0.7} {Earth 1 1} {Mars 0.107 1.5}]
        // By decreasing distance: [{Mars 0.107 1.5} {Earth 1 1} {Venus 0.815 0.7} {Mercury 0.055 0.4}]
}