go是面向对象语言吗? - Go语言中文社区

go是面向对象语言吗?


转载链接:https://segmentfault.com/a/1190000001832282#articleHeader5

原文链接:http://spf13.com/post/is-go-object-oriented

前言

面向对象的含义引入了对象(object)、类(class)、继承(inheritance)、子类(subclass)、虚方法(virtual method)、协程(coroutine)等概念。面向对象引入颠覆性的思想——将数据和逻辑完全分离。大部分程序员通过编程语言进行软件开发都遵循着将数据和逻辑完全分离的原则。

由于面向对象没有标准的定义,为了讨论的方便,接下来我们将提供一个标准的定义。

面向对象系统将数据和代码通过“对象”集成到一起,而不是将程序看成由分离的数据和代码组成。对象是数据类型的抽象,它有状态(数据)和行为(代码)

面向对象包括封装、继承、多态、虚派生等特性,接下来我们将看看go语言是怎样处理对象、多态、继承,相信读完接下来的介绍,您会对go是如何处理面向对象有自己的见解。

go中的对象-封装

go语言中没有对象(object)这个关键词。对象(object)仅仅是一个单词,重要的是它所表示的封装数据含义。尽管go中没有object这种类型,但是go中的struct有着跟object相同的特性。而在go中,采用了一种算是规定的方法:使用大小写来确定,大写是包外可见的,小写的struct或函数只能在包内使用。

struct是一种包含了命名域和方法的类型

让我们从一个例子中来理解它:

type rect struct {
    width int
    height int
}

func (r *rect) area() int {
    return r.width * r.height
}

func main() {
    r := rect{width: 10, height: 5}
    fmt.Println("area: ", r.area())
}

(1) 代码的第一块定义了一个叫做rect的struct类型,该struct含有两个int类型的域;

(2) 接下来定义了一个绑定在rect struct类型上的area方法。严格来说,area方法是绑定在指向rectct struct的指针上。如果方法绑定在rect type而非指针上,则在调用方法的时候需要使用该类型的值来调用,即使该值是空值,本例的空值实际是一个nil值;

(3) 代码的最后一块是main函数,main函数第一行创建了一个rect类型的值,当然也有其他的方法来创建一个类型的值,这里给出的是一个地道的方法。main函数的最后一行是打印作用在r值上的area方法的返回结果。

通过上面的描述,可以看出这很像对象的行为,我们可以创建一个结构化的数据类型,然后定义方法和这些数据进行交互。上述的简单例子并没有完成展示面向对象的所有特性,比如继承和多态。需要说明的是go不仅可以在struct上定义方法,在任何命名的类型上同样也可以。比如,可以定义一个名为Counter的新类型,该类型是int型的别名,然后在Counter类型上定义方法。例子详见:http://play.golang.org/p/LGB-2j707c

继承和多态

定义对象间的关系的方法有如下几种,它们之间都有一些差别,但目的都是一样的:复用代码
单继承(Inheritance) 多继承(Multiple Inheritance) 多态(Subtyping/Polymorphism)对象组合(Object composition)
继承:一个对象基于另外一个对象,使用其实现。有两种不同的继承实现:单继承和多继承。它们的不同在于对象是继承自一个对象还是多个对象。单继承关系是一棵树,而多继承关系是一个格状结构。单继承语言包括PHP、C#、Java、Ruby等,多继承语言包括Perl、Python、C++等

多态

多态是is-a的关系,继承是实现的复用。多态定义了两个对象的语义关系,继承定义两个对象的语法关系。

对象组合

对象组合是一个对象包含了其他对象,而非继承,它是has-a的关系,而非is-a。

go语言的继承

go有意得被设计为没有继承语法。但这并不意味go中的对象(struct value)之间没有关系,只不过go的作者选择了另外一种机制来暗含这种特性。实际上go的这种设计是一种非常好的解决方法,它解决了围绕着继承的数十年的老问题和争论。

go语言中的多态和组合(最好不要继承)

go语言严格遵守composition over inheritance principle的原则。go通过在struct和interface上使用组合和多态来实现继承关系。Person和Address之间的关系是这种实现的一个很好的例子:http://play.golang.org/p/LigPIVT2mf

type Person struct {
   Name string
   Address Address    //匿名字段
}

type Address struct {
   Number string
   Street string
   City   string
   State  string
   Zip    string
}

func (p *Person) Talk() {
    fmt.Println("Hi, my name is", p.Name)
}

func (p *Person) Location() {
    fmt.Println("I’m at", p.Address.Number, p.Address.Street, p.Address.City, p.Address.State, p.Address.Zip)
}

func main() {
    p := Person{
        Name: "Steve",
        Address: Address{
            Number: "13",
            Street: "Main",
            City:   "Gotham",
            State:  "NY",
            Zip:    "01313",
        },
    }

    p.Talk()
    p.Location()
}

程序执行结果:


上面的例子需要注意的是, Address仍然是一个不同的对象,只不过存在于Person中

go中的伪多态

我们通过扩展上面的例子来说明go中的伪多态。注意这里“伪”字说明实际上go是没有多态的概念的,只不过伪多态表现得像多态一样。下面的例子中,Person可以说话(Talk),一个Citizen也同时是一个Person,因此他也能说话(Talk)。在上面的例子中加入如下内容,完整代码见:http://play.golang.org/p/eCEpLkQPR3

type Citizen struct {
   Country string
   Person
}

func (c *Citizen) Nationality() {
    fmt.Println(c.Name, "is a citizen of", c.Country)
}

func main() {
    c := Citizen{}
    c.Name = "Steve"
    c.Country = "America"
    c.Talk()
    c.Nationality()
}

上面的例子通过引入匿名域(Person)实现了is-a关系。Person是Citizen的一个匿名域(anonymous field),匿名域只给出了对象类型,而不给出类型的名字。通过匿名域,Citizen可以访问Person中的所有属性(域)和方法。程序执行结果如下所示:


匿名域方法提升

上述例子,Citizen可以和Person执行一样的Talk()方法。但如果想要Citizen的Talk()表现出不同的行为该怎么做呢?我们只需要在Citizen上定义方法Talk()即可。当调用c.Talk()的时候,调用的则是Citizen的Talk()方法而非Person的Talk()方法,http://play.golang.org/p/jafbVPv5H9

func (c *Citizen) Talk() {
    fmt.Println("Hello, my name is", c.Name, "and I'm from", c.Country)
}

程序执行结果:


为何匿名域不是合适的多态实现

有两个原因:

1. 匿名域仍然能被访问,就好像它们是被嵌入的对象一样。

这并不是一件坏事,多继承存在的一个问题就是当多个父类具有相同的方法的时候,会产生歧义。然而go语言可以通过访问跟匿名类型同名的属性来访问嵌入的匿名对象。实际上当使用匿名域的时候,go会创建一个跟匿名类型同名的对象。上面的例子中,修改main方法如下,我们能很清楚得看出这一点:

func main() {
//    c := Citizen{}
    c.Name = "Steve"
    c.Country = "America"
    c.Talk()         // <- Notice both are accessible
    c.Person.Talk()  // <- Notice both are accessible
    c.Nationality()
}
2、真正的多态,派生对象就是父对象
如果匿名对象能实现多态,则外层对象应该等同于嵌入的对象,而实际上并非如此,它们仍然是不同的存在。下面的例子印证了这一点:

package main

type A struct{
}

type B struct {
    A  //B is-a A
}

func save(A) {
    //do something
}

func main() {
    b := B
    save(&b);  //OOOPS! b IS NOT A
}

程序执行报错:

prog.go:17: cannot use b (type *B) as type A in function argument
[process exited with non-zero status]

go中的真正的多态实现

正如我们上面提到的,多态是一种is-a的关系。在go语言中,每种类型(type)都是不同的,一种类型不能完全等同于另外一种类型,但它们可以绑定到同一个接口(interface)上。接口能用于函数(方法)的输入输出中,因而可以在类型之间建立起is-a的关系

go语言定义一个接口并不是使用using关键字,而是通过在对象上定义方法来实现。在Effective Go中指出,这种关系就像“如果某个东西能做这件事,那么就把它应用到这里”(不管黑猫白猫,只要能抓到老鼠,我就养这只猫)。这一点很重要,因为这允许一个定义在package外的类型也能实现该接口。

我们接着上面的例子,增加一个新函数SpeakTo,然后修改main函数,将该方法应用到Citizen和Person上,http://play.golang.org/p/lvEjaMQ25D

func SpeakTo(p *Person) {
    p.Talk()
}

func main() {
    p := Person{Name: "Dave"}
    c := Citizen{Person: Person{Name: "Steve"}, Country: "America"}

    SpeakTo(&p)
    SpeakTo(&c)
}

程序运行结果出错:类型不一致,因此可借助 接口来实现。

Running it will result in
prog.go:48: cannot use c (type *Citizen) as type *Person in function argument
[process exited with non-zero status]

跟预期的结果一样,编译失败。Citizen并不是Person类型,尽管他们拥有同样的属性。然而我们定义一个接口(interface)Human,然后将这个接口作为SpeakTo函数的输入参数,上面的例子就可以正常运行了,http://play.golang.org/p/ifcP2mAOnf

type Human interface {
    Talk()
}

func SpeakTo(h Human) {
    h.Talk()
}

func main() {
    p := Person{Name: "Dave"}
    c := Citizen{Person: Person{Name: "Steve"}, Country: "America"}

    SpeakTo(&p)
    SpeakTo(&c)
}
程序输出结果:
Hi, my name is Dave
Hi, my name is Steve

关于go语言中的多态,有如下两点需要注意。

1. 可以把匿名域绑定到一个接口,也能绑定到多个接口。接口和匿名域一起使用,可以起到和多态同样的效果。

2. go提供了多态的能力。接口的使用能使得实现了该接口方法的不同对象都能作为  函数的输入参数  ,甚至作为 返回结果 ,但它们仍然保持了它们自己的类型。这点从上面的例子能看出来,我们不能直接在初始化Citizen对象的时候设置Name值,因为Name不是Citizen的属性,而是Person的属性,因而不能再初始化Citizen的时候设置Name值。

go,一个没有object和inheritance的面向对象的语言

如上所述,面向对象的基本概念在go中被很好的实现了,虽然术语上存在差别。

(1) go把struct作为数据和逻辑的结合。

(2) 通过组合(composition),has-a关系来最小化代码重用,并且避免了继承的缺陷。

(3) go使用接口(interface)来建立类型(type)之间的is-a关系。

通过上面的论述,Go语言通过其语法特性可以实现面向对象编程。欢迎进入无对象的OO编程模型世界!

讨论

Join the discussion on hacker news and Reddit-Golang

深入阅读

http://nathany.com/good/
http://www.artima.com/lejava/articles/designprinciples.html
http://www.goinggo.net/2014/05/methods-interfaces-and-embedded-types.html

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/li_101357/article/details/80205005
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2019-08-25 16:32:37
  • 阅读 ( 1410 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢