17.4. 操作者模式和接口

未匹配的标注

运算符是个一元或者二元函数,它返回一个新的对象并且不能修改它的参数,比如 +* 。在 C++ 中,可以重载中缀运算符(+ 、 - 、* 等)用来支持数学类语法,但是除了一些特殊情况,Go 不支持运算符重载: 为了克服这个限制,运算符必须用函数进行模拟。由于 Go 支持程序以及一个面向对象的范例,因此有两种方案:

17.4.1. 用函数实现运算符

运算符被实现为一个包级别的函数,在专用于它们所在的对象的包中,它去操作一个或者两个参数,并返回一个新的对象。例如,我们如果想在一个 matrix 包中实现矩阵操作,在 matrix 的结果中,它要包含添加矩阵的 Add() 和相乘的 Mult() 。这些将用包本身的名称去调用,所以我们可以这样使用: m := matrix.Add(m1, matrix.Mult(m2, m3))

如果我们想在这个操作中区分不同的矩阵( sparsedense ),因为它不能函数重载,我们要给它们不同的名字,例如:

func addSparseToDense (a *sparseMatrix, b *denseMatrix) *denseMatrix

func addDenseToDense (a *denseMatrix, b *denseMatrix) *denseMatrix

func addSparseToSparse (a *sparseMatrix, b *sparseMatrix) *sparseMatrix

这个非常不优雅,我们最好能作为一个私有的函数隐藏这些,并且通过一个单独的公共函数 Add() 去暴露它们。可以通过嵌套 switch type 对它们进行类型检测,来操作任意组合的被支持的参数:

func Add(a Matrix, b Matrix) Matrix {

    switch a.(type) {

    case sparseMatrix:

        switch b.(type) {

        case sparseMatrix:

            return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))

        case denseMatrix:

            return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))

        …

        default:

            // 不支持的参数

    …

    }

}

但是更加优雅和首选的做法是将运算符作为一个方法去实现,因为它在标准库中的任何地方都可以完成。关于 Ryanne Dolan 实现的线性代数的包的更多信息,这个在这里找到: code.google.com/p/gomatrix/

译者注: 貌似这个包现在已经不被支持了,Google Code 给的链接变成了 github.com/skelterjohn/go.matrix

17.4.2. 用方法实现运算符

方法可以根据他们的接收器类型进行区分,所以不必使用不同名称的函数(上一小节的方法),我们可以简单的为每种类型定义一个 Add 方法:

func (a *sparseMatrix) Add(b Matrix) Matrix

func (a *denseMatrix) Add(b Matrix) Matrix

每个方法都会返回一个新对象,该对象将成为下一个方法调用的接收者,因此我们可以创建链式表达式: m1.Mult(m2).Add(m3)

这种方式比 17.4.1 章节的程序更简短、更清晰。

基于 type-switch ,正确的实现可以在运行时再次被选择:

func (a *sparseMatrix) Add(b Matrix) Matrix {

    switch b.(type) {

    case sparseMatrix:

    return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))

    case denseMatrix:

    return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))

    …

    default:

    // 不支持的参数

    …
    }

}

比 17.4.1 章节的嵌套 type switch 更容易一些。

17.4.3. 使用接口

当在不同类型中使用相同的方法进行操作时,应该想到创建一个泛化接口去实现这种多态性。

例如,我们可以定义接口 Algebraic

type Algebraic interface {

    Add(b Algebraic) Algebraic

    Min(b Algebraic) Algebraic

    Mult(b Algebraic) Algebraic

    …

    Elements()

}

并为我们的 matrix 类型定义方法 Add()Min()Mult() ……

实现上述 Algebraic 接口的每种类型都将允许方法链接。每个方法的实现都应该根据参数类型使用 type-switch 来提供优化的实现。此外,应指定一个仅依赖于接口中方法的默认情况:

func (a *denseMatrix) Add(b Algebraic) Algebraic {

    switch b.(type) {

    case sparseMatrix:

        return addDenseToSparse(a, b.(sparseMatrix))

    default:

        for x in range b.Elements() …

    …

}

如果通用实现不能仅使用接口中的方法实现,你可能正在处理那些不够相似的类,这种操作模式应该被抛弃。例如: 如果 a 是一个 set 、 b 是一个 matrix ,写一个 a.Add(b) 就不太合理了;因此,在一个 setmatrix 操作条件中,实现一个通用的 a.Add(b) 将非常困难。在这种情况下,将你的包分成两部分,并定义单独的 AlgebraicSetAlgebraicMatrix 接口。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/the-way-to-go/1...

译文地址:https://learnku.com/docs/the-way-to-go/1...

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~