menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right All_wiki chevron_right the-way-to-go_ZH_CN chevron_right eBook chevron_right 17.4.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    17.4.md
    4.63 KB / 2024-07-16 23:14:29
        # 17.4 运算符模式和接口
    
    运算符是一元或二元函数,它返回一个新对象而不修改其参数,类似 C++ 中的 `+` 和 `*`,特殊的中缀运算符(`+`,`-`,`*` 等)可以被重载以支持类似数学运算的语法。但除了一些特殊情况,Go 语言并不支持运算符重载:为了克服该限制,运算符必须由函数来模拟。既然 Go 同时支持面向过程和面向对象编程,我们有两种选择:
    
    ## 17.4.1 函数作为运算符
    
    运算符由包级别的函数实现,以操作一个或两个参数,并返回一个新对象。函数针对要操作的对象,在专门的包中实现。例如,假设要在包 `matrix` 中实现矩阵操作,就会包含 `Add()` 用于矩阵相加,`Mult()` 用于矩阵相乘,他们都会返回一个矩阵。这两个函数通过包名来调用,因此可以创造出如下形式的表达式:
    ```go
    m := matrix.Add(m1, matrix.Mult(m2, m3))
    ```
    
    如果我们想在这些运算中区分不同类型的矩阵(稀疏或稠密),由于没有函数重载,我们不得不给函数起不同的名称,例如:
    ```go
    func addSparseToDense (a *sparseMatrix, b *denseMatrix) *denseMatrix
    func addDenseToDense (a *denseMatrix, b *denseMatrix) *denseMatrix
    func addSparseToSparse (a *sparseMatrix, b *sparseMatrix) *sparseMatrix
    ```
    
    这可不怎么优雅,我们能选择的最佳方案是将它们隐藏起来,作为包的私有函数,并暴露单一的 `Add()` 函数作为公共 API。可以在嵌套的 `switch` 断言中测试类型,以便在任何支持的参数组合上执行操作:
    ```go
    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 实现的线性代数包的更详细信息,可以在 https://github.com/skelterjohn/go.matrix 找到。
    
    ## 17.4.2 方法作为运算符
    
    根据接收者类型不同,可以区分不同的方法。因此我们可以为每种类型简单地定义 `Add` 方法,来代替使用多个函数名称:
    ```go
    func (a *sparseMatrix) Add(b Matrix) Matrix
    func (a *denseMatrix) Add(b Matrix) Matrix
    ```
    
    每个方法都返回一个新对象,成为下一个方法调用的接收者,因此我们可以使用*链式调用*表达式:
    ```go
    m := m1.Mult(m2).Add(m3)
    ```
    比上一节面向过程的形式更简洁。
    
    正确的实现同样可以基于类型,通过 `switch` 类型断言在运行时确定:
    ```go
    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:
    		// 不支持的参数
    		…
    	}
    }
    ```
    
    再次地,这比上一节嵌套的 `switch` 更简单。
    
    ## 17.4.3 使用接口
    
    当在不同类型上执行相同的方法时,创建一个通用化的接口以实现多态的想法,就会自然产生。
    
    例如定义一个代数 `Algebraic` 接口:
    ```go
    type Algebraic interface {
    	Add(b Algebraic) Algebraic
    	Min(b Algebraic) Algebraic
    	Mult(b Algebraic) Algebraic
    	…
    	Elements()
    }
    ```
    
    然后为我们的 `matrix` 类型定义 `Add()`,`Min()`,`Mult()`,……等方法。
    
    每种实现上述 `Algebraic` 接口类型的方法都可以链式调用。每个方法实现都应基于参数类型,使用 `switch` 类型断言来提供优化过的实现。另外,应该为仅依赖于接口的方法,指定一个默认处理分支:
    ```go
    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` 是一个集合而 `b` 是一个矩阵,那么编写 `a.Add(b)` 没有意义。就集合和矩阵运算而言,很难实现一个通用的 `a.Add(b)` 方法。遇到这种情况,把包拆分成两个,然后提供单独的 `AlgebraicSet` 和 `AlgebraicMatrix` 接口。
    
    
    ## 链接
    
    - [目录](directory.md)
    - 上一节:[可见性模式](17.3.md)
    - 下一章:[出于性能考虑的实用代码片段](18.0.md)
    
    
    links
    file_download