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

17.4.1. 用函数实现运算符

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

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

  1. func addSparseToDense (a *sparseMatrix, b *denseMatrix) *denseMatrix
  2. func addDenseToDense (a *denseMatrix, b *denseMatrix) *denseMatrix
  3. func addSparseToSparse (a *sparseMatrix, b *sparseMatrix) *sparseMatrix

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

  1. func Add(a Matrix, b Matrix) Matrix {
  2. switch a.(type) {
  3. case sparseMatrix:
  4. switch b.(type) {
  5. case sparseMatrix:
  6. return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))
  7. case denseMatrix:
  8. return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))
  9. default:
  10. // 不支持的参数
  11. }
  12. }

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

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

17.4.2. 用方法实现运算符

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

  1. func (a *sparseMatrix) Add(b Matrix) Matrix
  2. func (a *denseMatrix) Add(b Matrix) Matrix

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

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

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

  1. func (a *sparseMatrix) Add(b Matrix) Matrix {
  2. switch b.(type) {
  3. case sparseMatrix:
  4. return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))
  5. case denseMatrix:
  6. return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))
  7. default:
  8. // 不支持的参数
  9. }
  10. }

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

17.4.3. 使用接口

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

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

  1. type Algebraic interface {
  2. Add(b Algebraic) Algebraic
  3. Min(b Algebraic) Algebraic
  4. Mult(b Algebraic) Algebraic
  5. Elements()
  6. }

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

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

  1. func (a *denseMatrix) Add(b Algebraic) Algebraic {
  2. switch b.(type) {
  3. case sparseMatrix:
  4. return addDenseToSparse(a, b.(sparseMatrix))
  5. default:
  6. for x in range b.Elements()
  7. }

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