传统的面向对象编程
在面向对象编程(OOP)中,类与对象是面向对象编程的两个主要方面。一个类(Class)能够创建一种新的类型(Type),其中对象(Object)就是类的实例(Instance)。可以这样来类比:你可以拥有类型 int
的变量,也就是说存储整数的变量是 int
类的实例(对象)。
对象可以使用属于它的普通变量来存储数据。这种从属于对象或类的变量叫作字段(Field)。对象还可以使用属于类的函数来实现某些功能,这种函数叫作类的方法(Method)。这两个术语很重要,它有助于我们区分函数与变量,哪些是独立的,哪些又是属于类或对象的。总之,字段与方法通称类的属性(Attribute)。
方法
在 Golang 中,方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量;接收者可以是任意类型(指针、接口除外),包括结构体类型、函数类型、可以是 int ,boll,string或数组别名类型。
- 接收者不能是一个接口类型,因为接口是一个抽象定义,方法却必须要具体实现
- 接收者不能是一个指针类型,但可以是任何其他类型允许的指针
方法重载
类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在不同的源文件中,唯一的要求是它们必须是同一个包的。 因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法,但是如果基于接收器类型,是有重载的。如下面的例子,具有同样名字的方法Add
可以在 2 个或多个不同的接收器类型*denseMatrix
和*sparseMatrix
上存在,比如在同一个包里这么做是允许的:
//a是接收器变量,*denseMatrix和*sparseMatrix是接收器类型,Add是方法,b Matrix是参数列表,Matrix是返回参数
func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix
接收器的标准格式
-
recv
是接收器名称,receiver_type
是接收器类型( 接收器中的参数变量名在命名时,官方建议使用接收器类型名的第一个小写字母 ,例如 Socket 类型的接收器变量应该命名为 s ) -
methodName
是方法名,paramater_list
是参数列表,return_value_list
是返回值列表
func (recv receiver_type)methodName(paramater_list)(return_value_list) {...}
接收器的类型
在计算机中,小对象由于值复制时的速度较快,所以适合使用非指针接收器,大对象因为复制性能较低,适合使用指针接收器,在接收器和参数间传递时不进行复制,只是传递指针。
-
指针类型
由于指针的特性,调用方法时,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的。指针类型的接收器由一个结构体的指针组成,更接近于面向对象中的 this 或者 self;但 Go 并没有 this 和 self 这两个关键字,因此,也可以使用 this 和 self 作为接收者的实例化名字(this 和 self 跟一般的实例化名字没什么两样)。
//定义TwoInts结构体 type TwoInts struct { a int b int } func main(){ two1 := new(TwoInts)//实例化,返回指针*TwoInts two1.a = 12 two1.b = 10 //调用AddThem方法 fmt.Println(two1.AddThem()) //调用AddToParam方法 fmt.Println(two1.AddToParam(20)) two2 := TwoInts{3,4} //实例化,返回结构体TwoInts{3,4} //调用AddThem方法 fmt.Println(two2.AddThem()) } //指针接收器的两个值相加方法 func (tn *TwoInts)AddThem()int{ return tn.a + tn.b } //指针接收器的三个值相加方法 func (tn *TwoInts)AddToParam(param int)int{ return tn.a + tn.b + param } /* 22 42 7 */
代码说明:
- 定义一个
TwoInts
结构,拥有两个整型的成员变量。使用名字two1
实例化TwoInts
结构体,返回指针类型*TwoInts
,分别设置实例化成员变量two1.a
和two1,b
的值为12
和10
;使用名字two2
实例化TwoInts
结构体,设置成员变量返回结构体TwoInts
- 构造成员变量的方法
AddThem
,设置方法的接收器类型为指针*TwoInts
,返回tn.a + tn.b
的整型值;因此可以修改成员值,即便退出方法,也有效;构造另一个成员变量的方法AddToParam
,设置方法的接收器类型为指针*TwoInts
,返回tn.a + tn.b + param
的整型值。 - 在
main
函数中,使用fmt.Println
函数分别调用AddThem
和AddToParam
方法,获取成员变量的值。
- 定义一个
-
非指针类型
当方法作用于非指针接收器时,Go语言会在代码运行时将接收器的值复制一份,在非指针接收器的方法中可以获取接收器的成员值,但修改后无效。
// 定义点结构 type Point struct { X int Y int } // 非指针接收器的加方法 func (p Point) Add(other Point) Point { // 成员值与参数相加后返回新的结构 return Point{p.X + other.X, p.Y + other.Y} } func main() { // 初始化点 p1 := Point{3, 4} p2 := Point{2, 5} // 与另外一个点相加 result := p1.Add(p2) // 输出结果 fmt.Println(result.X,result.Y,result) } /* 5 9 {5 9} */
代码说明:
- 定义一个
Point
点结构,拥有X
和Y
两个整型的成员变量 - 为
Point
结构定义一个非指针接收器的Add
方法,传入和返回都是Point
的结构,可以方便地实现多个点连续相加的效果,例如P4 := P1.Add( P2 ).Add( P3 )
- 用
p1
和p2
实例化两个点 - 将
p1
和p2
两个点相加后返回结果并存储到result
- 打印结果
result
和X
和Y
相加的值由于例子中使用了非指针接收器,Add() 方法变得类似于只读的方法,Add() 方法内部不会对成员进行任何修改。
- 定义一个
### 函数和方法的区别
函数将变量作为参数:Function(recv);方法在变量上被调用:recv.Method1()
- 当接收者是指针时,方法可以改变接收者的值和状态。(对于方法来说)
- 当参数作为指针传递时,即通过引用调用时,函数也可以改变参数的状态。(对于函数来说)
### Golang设计模式之工厂方法
参考链接:Golang设计模式之工厂方法(掘金)
在面向对象编程中,可以通过构造子方法实现工厂模式,但 Golang 并不是面向对象编程语言,因此不能使用构造子方法来实现设计模式,而是相应地提供了其他方案。以结构体为例,通常会为结构体类型定义一个工厂,工厂的名字以new
或者New
开头。
//不强制构造函数,首字母大写
type File struct {
fd int
name string
}
//构造工厂方法
func NewFile(fd int,name string) *File{
if fd < 0 {
return nil
}
return &File{fd,name}
}
func main() {
//调用工厂方法NewFile
f := NewFile(10,"./test.yxy")
fmt.Println(f)
//计算结构体占用多少内存
size := unsafe.Sizeof(File{})
fmt.Println(size)
}
/*
&{10 ./test.yxy}
24
*/
代码说明:
- 以首字母为大写,不强制使用构造函数,创建结构体
File
- 为这个结构体构造一个工厂方法
NewFile
,并返回指向结构体的指针*File
- 在
main
函数调用该工厂方法
强制使用工厂方法,通过应用可见性就可以禁止使用new
函数,强制用户使用工厂方法,从而使类型转变成私有的。
### 指针或值作为接收者
如果想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。
change()
接收一个指向B
的指针,并改变它的内部成员;write()
通过拷贝接收B
的值并只输出B
的内容,
type B struct {
thing int
}
func (b *B) change() {
b.thing = 1
}
func (b B) write() string {
return fmt.Sprint(b)
}
func main() {
var b1 B // b1是值
b1.change()
fmt.Println(b1.write())
b2 := new(B) // b2是指针
b2.change()
fmt.Println(b2.write())
}
/*
{1}
{1}
*/
有疑问加站长微信联系(非本文作者)