Go语言与数据库开发:01-06

Go语言包含了对OOP语言的支持,接下来我们来看看Go语言中的方法。

尽管没有被大众所接受的明确的OOP的定义,从我们的理解来讲,一个对象其实也就是一个
简单的值或者一个变量,在这个对象中会包含一些方法,而一个方法则是一个一个和特殊类
型关联的函数。一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对
象的用户就不需要直接去操作对象,而是借助方法来做这些事情。

在函数声明时,在其名字之前放上一个变量,即是一个方法。
这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。

package geometry
import "math"
type Point struct{ X, Y float64 }
// traditional function
func Distance(p, q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// same thing, but as a method of the Point type
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}

上面的代码里那个附加的参数p,叫做方法的接收器(receiver),早期的面向对象语言留下的遗
产将调用一个方法称为“向一个对象发送消息”。

在Go语言中,我们并不会像其它语言那样用this或者self作为接收器;我们可以任意的选择接
收器的名字。由于接收器的名字经常会被使用到,所以保持其在方法间传递时的一致性和简
短性是不错的主意。这里的建议是可以使用其类型的第一个字母,比如这里使用了Point的首
字母p。

在方法调用过程中,接收器参数一般会在方法名之前出现。这和方法声明是一样的,都是接
收器参数在方法名字之前。

p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // "5", function call
fmt.Println(p.Distance(q)) // "5", method call

可以看到,上面的两个函数调用都是Distance,但是却没有发生冲突。第一个Distance的调用
实际上用的是包级别的函数geometry.Distance,而第二个则是使用刚刚声明的Point,调用的
是Point类下声明的Point.Distance方法。

这种p.Distance的表达式叫做选择器,因为他会选择合适的对应p这个对象的Distance方法来
执行。选择器也会被用来选择一个struct类型的字段,比如p.X。由于方法和字段都是在同一
命名空间,所以如果我们在这里声明一个X方法的话,编译器会报错,因为在调用p.X时会有
歧义。

因为每种类型都有其方法的命名空间,我们在用Distance这个名字的时候,不同的Distance调
用指向了不同类型里的Distance方法。

在能够给任意类型定义方法这一点上,Go和很多其它的面向对象的语言不太一样。因此
在Go语言里,我们为一些简单的数值、字符串、slice、map来定义一些附加行为很方便。方
法可以被声明到任意类型,只要不是一个指针或者一个interface。


基于指针对象的方法

当调用一个函数时,会对其每一个参数值进行拷贝,如果一个函数需要更新一个变量,或者
函数的其中一个参数实在太大我们希望能够避免进行这种默认的拷贝,这种情况下我们就需
要用到指针了。

对应到我们这里用来更新接收器的对象的方法,当这个接受者变量本身比较
大时,我们就可以用其指针而不是对象来声明方法,如下:

func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}

这个方法的名字是 (*Point).ScaleBy 。这里的括号是必须的;没有括号的话这个表达式可能
会被理解为 *(Point.ScaleBy)。

实际上注意两点:
1. 不管你的method的receiver是指针类型还是非指针类型,都是可以通过指针/非指针类型
进行调用的,编译器会帮你做类型转换。
2. 在声明一个method的receiver该是指针还是非指针类型时,你需要考虑两方面的内部,第
一方面是这个对象本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷
贝;第二方面是如果你用指针类型作为receiver,那么你一定要注意,这种指针类型指向
的始终是一块内存地址,就算你对其进行了拷贝。熟悉C或者C艹的人这里应该很快能明白。

Nil也是一个合法的接收器类型
就像一些函数允许nil指针作为参数一样,方法理论上也可以用nil指针作为其接收器,尤其当
nil对于对象来说是合法的零值时,比如map或者slice。


方法值和方法表达式

我们经常选择一个方法,并且在同一个表达式里执行,比如常见的p.Distance()形式,实际上
将其分成两步来执行也是可能的。p.Distance叫作“选择器”,选择器会返回一个方法"值"->一
个将方法(Point.Distance)绑定到特定接收器变量的函数。这个函数可以不通过指定其接收器
即可被调用;即调用时不需要指定接收器(译注:因为已经在前文中指定过了),只要传入函数
的参数即可:

p := Point{1, 2}
q := Point{4, 6}
distanceFromP := p.Distance // method value
fmt.Println(distanceFromP(q)) // "5"
var origin Point // {0, 0}
fmt.Println(distanceFromP(origin)) // "2.23606797749979", sqrt(5)
scaleP := p.ScaleBy // method value
scaleP(2) // p becomes (2, 4)
scaleP(3) // then (6, 12)
scaleP(10) // then (60, 120)


封装

一个对象的变量或者方法如果对调用方是不可见的话,一般就被定义为“封装”。封装有时候也
被叫做信息隐藏,同时也是面向对象编程最关键的一个方面。

Go语言只有一种控制可见性的手段:大写首字母的标识符会从定义它们的包中被导出,小写
字母的则不会。这种限制包内成员的方式同样适用于struct或者一个类型的方法。因而如果我
们想要封装一个对象,我们必须将其定义为一个struct。

例如:
type IntSet struct {
words []uint64
}

这种基于名字的手段使得在语言中最小的封装单元是package,而不是像其它语言一样的类
型。一个struct类型的字段对同一个包的所有代码都有可见性,无论你的代码是写在一个函数
还是一个方法里。

封装提供了三方面的优点。首先,因为调用方不能直接修改对象的变量值,其只需要关注少
量的语句并且只要弄懂少量变量的可能的值即可。
第二,隐藏实现的细节,可以防止调用方依赖那些可能变化的具体实现,这样使设计包的程
序员在不破坏对外的api情况下能得到更大的自由。
把bytes.Buffer这个类型作为例子来考虑。这个类型在做短字符串叠加的时候很常用,所以在
设计的时候可以做一些预先的优化,比如提前预留一部分空间,来避免反复的内存分配。又
因为Buffer是一个struct类型,这些额外的空间可以用附加的字节数组来保存,且放在一个小
写字母开头的字段中。这样在外部的调用方只能看到性能的提升,但并不会得到这个附加变量。


接口

接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一
起,通过这种抽象的方式我们可以让我们的函数更加灵活和更具有适应能力。

很多面向对象的语言都有相似的接口概念,但Go语言中接口类型的独特之处在于它是满足隐
式实现的。也就是说,我们没有必要对于给定的具体类型定义所有满足的接口类型;简单地
拥有一些必需的方法就足够了。

这种设计可以让你创建一个新的接口类型满足已经存在的具
体类型却不会去改变这些类型的定义;当我们使用的类型来自于不受我们控制的包时这种设
计尤其有用。

时间: 2024-02-12 20:28:23

Go语言与数据库开发:01-06的相关文章

Go语言与数据库开发:01-01

一.前言 Google的三位大牛,为了解决在21世纪多核和网络化环境下越来越复杂的编程问题而发明了go语言, 从2007年9月开始设计和实现,于2009年的11月对外正式发布.从版本的发布历史来看,go语言是从 Ken Thompson发明的B语言.Dennis M. Ritchie发明的C语言逐步演化过来的,是C语言家族的成员, 因此很多人将Go语言称为21世纪的C语言. 纵观这几年来的发展趋势,Go语言已经成为云计算.云存储时代最重要的基础编程语言. Go语言有着和C语言类似的语法,但是它不

Go语言与数据库开发:01-09

包和工具 Go语言有超过100个的标准包(译注:可以用 go list std | wc -l 命令查看标准包的具体数 目),标准库为大多数的程序提供了必要的基础构件.在Go的社区,有很多成熟的包被设 计.共享.重用和改进,目前互联网上已经发布了非常多的Go语音开源包,它们可以通过http://godoc.org 检索. Go还自带了工具箱,里面有很多用来简化工作区和包管理的小工具. 包简介 任何包系统设计的目的都是为了简化大型程序的设计和维护工作,通过将一组相关的特性放 进一个独立的单元以便于

Go语言与数据库开发:01-04

讲完基础数据类型之后,我们接着学习复合数据类型. 复合数据类型,它是以不同的方式组合基本类型可以构造出来的复合数据类型. 几个基本的复合数据类型: 数组,是由同构的元素组成--每个数组元素都是完全相同的类型; 结构体,则是由异构的元素组成的; 数组和结构体都是有固定内存大小的数据结构; slice和map则是动态的数据结构,它们将根据需要动态增长: 数组: 数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成. 因为数组的长度是固定的,因此在Go语言中很少直接使用数组.

Go语言与数据库开发:01-02

接下来,开始了解go语言的程序结构,基础要打牢. Go语言和其他编程语言一样,一个大的程序是由很多小的基础构件组成的.变量保存值,简 单的加法和减法运算被组合成较复杂的表达式.基础类型被聚合为数组或结构体等更复杂的 数据结构.然后使用if和for之类的控制语句来组织和控制表达式的执行流程.然后多个语句被 组织到一个个函数中,以便代码的隔离和复用.函数以源文件和包的方式被组织. . 关于命名: 在Go中是区分大小写的:关键字不能用于自定义名字: Go语言的风格是尽量使用短小的名字,对于局部变量尤其

Go语言与数据库开发:01-08

基于共享变量的并发 .竞争条件 在一个线性(就是说只有一个goroutine的)的程序中,程序的执行顺序只由程序的逻辑来决定. 例如,我们有一段语句序列,第一个在第二个之前(废话),以此类推.在有两个或更多 goroutine的程序中,每一个goroutine内的语句也是按照既定的顺序去执行的,但是一般情况 下我们没法去知道分别位于两个goroutine的事件x和y的执行顺序,x是在y之前还是之后还是 同时发生是没法判断的.当我们能够没有办法自信地确认一个事件是在另一个事件的前面或 者后面发生的

Go语言与数据库开发:01-07

在本节,我们来说一下并发. 并发程序指同时进行多个任务的程序,随着硬件的发展,并发程序变得越来越重要 Go语言中的并发程序可以用两种手段来实现.尽管Go对并发的支持是众多强力特性之一,但跟踪调试并发程序还是很困难,在线性程序中 形成的直觉往往还会使我们误入歧途. . Goroutines 在Go语言中,每一个并发的执行单元叫作一个goroutine.设想这里的一个程序有两个函数, 一个函数做计算,另一个输出结果,假设两个函数没有相互之间的调用关系.一个线性的程 序会先调用其中的一个函数,然后再调

Go语言与数据库开发:01-03

在本文中,我们将介绍go的基础数据类型. 虽然从底层而言,所有的数据都是由比特组成,但计算机一般操作的是固定大小的数,如整 数.浮点数.比特数组.内存地址等.进一步将这些数组织在一起,就可表达更多的对象, 例如数据包.像素点.诗歌,甚至其他任何对象.Go语言提供了丰富的数据组织形式,这依 赖于Go语言内置的数据类型.这些内置的数据类型,兼顾了硬件的特性和表达复杂数据结构 的便捷性. Go语言将数据类型分为四类:基础类型.复合类型.引用类型和接口类型. 本文将介绍基础类型,包括:数字.字符串和布尔

Go语言与数据库开发:01-10

测试 现在的程序已经远比Wilkes时代的更大也更复杂,也有许多技术可以让软件的复杂性可得到控制.其中有两种技术在实践中证明是比较有效的.第一种是代码在被正式部署前需要进行代码评审.第二种则是测试 我们说测试的时候一般是指自动化测试,也就是写一些小的程序用来检测被测试代码(产品代码)的行为和预期的一样,这些通常都是精心设计的执行某些特定的功能或者是通过随机性的输入要验证边界的处理. 软件测试是一个巨大的领域.测试的任务可能已经占据了一些程序员的部分时间和另一些程序员的全部时间.和软件测试技术相关

Go语言与数据库开发:01-11

反射 Go语言提供了一种机制在运行时更新变量和检查它们的值.调用它们的方法和它们支持的内在操作,但是在编译时并不知道这些变量的具体类型.这种机制被称为反射.反射也可以让我们将类型本身作为第一类的值类型处理. Go语言的反射特性,看看它可以给语言增加哪些表达力,以及在两个至关重要的API是如何用反射机制的:一个是fmt包提供的字符串格式功能,另一个是类似encoding/json和encoding/xml提供的针对特定协议的编解码功能. 反射是一个复杂的内省技术,不应该随意使用,因此,尽管上面这些