Golang OOP、继承、组合、接口

http://www.cnblogs.com/jasonxuli/p/6836399.html

传统 OOP 概念

OOP(面向对象编程)是对真实世界的一种抽象思维方式,可以在更高的层次上对所涉及到的实体和实体之间的关系进行更好的管理。

流传很广的OOP的三要素是:封装、继承、多态。

对象:可以看做是一些特征的集合,这些特征主要由 属性 和 方法 来体现。

封装:划定了对象的边界,也就是定义了对象。

继承:表明了子对象和父对象之间的关系,子对象是对父对象的扩展,实际上,子对象“是”父对象。相当于说“码农是人”。从特征的集合这个意义上说,子对象包含父对象,父对象有的公共特征,子对象全都有。

多态:根据继承的含义,子对象在特性上全包围了父对象,因此,在需要父对象的时候,子对象可以替代父对象。

传统的 OOP 语言,例如 Java,C++,C#,对OOP 的实现也各不相同。以 Java 为例: Java 支持 extends,也支持 interface。这是两种不同的抽象方式。

extends 就是继承,A extends B,表明 A 是 B 的一种,是概念上的抽象关系。

Class Human {
    name:string
    age:int
    function eat(){}
    function speak(){}
}

Class Man extends Human {
    function fish(){}
    function drink(){}
}
 

Golang 的 OOP

回到 Golang。Golang 并没有 extends,它类似的方式是 composition(组合)。这种方式并不能实现 Child is Parent 这种定义上的抽象关系,因此 Golang 并没有传统意义上的多态。

package main

import "fmt"

func main(){
    var h Human

    s := Student{Grade: 1, Major: "English", Human: Human{Name: "Jason", Age: 12, Being: Being{IsLive: true}}}
    fmt.Println("student:", s)
    fmt.Println("student:", s.Name, ", isLive:", s.IsLive, ", age:", s.Age, ", grade:", s.Grade, ", major:", s.Major)

    //h = s // cannot use s (type Student) as type Human in assignment
    fmt.Println(h)

    //Heal(s) // cannot use s (type Student) as type Being in argument to Heal
    Heal(s.Human.Being) // true

    s.Drink()
    s.Eat()
}

type Car struct {
    Color string
    SeatCount int
}

type Being struct {
    IsLive bool
}

type Human struct {
    Being
    Name string
    Age int
}

func (h Human) Eat(){
    fmt.Println("human eating...")
    h.Drink()
}

func (h Human) Drink(){
    fmt.Println("human drinking...")
}

func (h Human) Move(){
    fmt.Println("human moving...")
}

type Student struct {
    Human
    Grade int
    Major string
}

func (s Student) Drink(){
    fmt.Println("student drinking...")
}

type Teacher struct {
    Human
    School string
    Major string
    Grade int
    Salary int
}

func (s Teacher) Drink(){
    fmt.Println("teacher drinking...")
}

type IEat interface {
    Eat()
}

type IMove interface {
    Move()
}

type IDrink interface {
    Drink()
}

func Heal(b Being){
    fmt.Println(b.IsLive)
}

输出结果:

student: {{{true} Jason 12} 1 English}
student: Jason , isLive: true , age: 12 , grade: 1 , major: English
{{false}  0}
true
student drinking...
human eating...
human drinking...

这里有一点需要注意,Student 实现了 Drink 方法,覆盖了 Human 的 Drink,但是没有实现 Eat 方法。因此,Student 在调用 Eat 方法时,调用的是 Human 的 Eat();而 Human 的 Eat() 调用了 Human 的 Drink(),于是我们看到结果中输出的是 human drinking...。这一点有些不同。

Golang 的接口

 

interface 是一种或多种功能性特征的集合,侧重于某个方面的抽象。比如,只有你通过了英语等级考试,达到了考试规定的相关技能的标准,你才可以说你会英语。因此,一个类实现一个接口是强制性的,必须实现接口所有的方法,即使是空方法。

interface IEnglishSpeaker {
     ListenEnglish()
     ReadEnglish()
     SpeakEnglish()
     WriteEnglish()
}

代码处理的是各种数据。对于强类型语言来说,非常希望一批数据都是单一类型的。但世界是复杂的,很多时候数据可能包含不同的类型,却有一个或多个共同点。这是单一继承关系所不能解决的,单一继承构造的是树状结构,而现实世界中更常见的是网状结构。

于是,强类型语言就补充了接口。类似的还有重载、泛型。

因此,接口实际上是对单一继承的补充。每个事物都有很多个标签 ,接口就好像标签一样,可以在多个维度对数据进行抽象,找出它们的共同点,使用同一套逻辑来处理。

Golang 的 interface 的实现并不像 Java 那样需要先声明然后再实现接口内容。它更像是动态语言中的 Duck Type,也就是实现决定了概念:如果一个人在学校(有School、Grade、Class 这些属性),还会学习(有Study()方法),那么这个人就是个学生。

Duck Type 更符合人类对现实世界的认知过程:我们总是通过认识不同的个体来进行总结归纳,然后抽象出概念和定义。这基本上就是在软件开发的前期工作,抽象建模。

相比较而言, Java 的方式是先定义了关系(接口),然后去实现,这更像是从上帝视角先规划概念产生定义,然后进行造物。

因为 interface 和 object 之间的松耦合,Golang 有 type assertion 这样的方式来判断一个接口是不是某个类型:

value, b := interface.(Type),value 是 Type 的默认实例;b 是 bool 类型,表明断言是否成立。

// 接上面的例子

v1, b := interface{}(s).(Car)
fmt.Println(v1, b)

v2, b := interface{}(s).(Being)
fmt.Println(v2, b)

v3, b := interface{}(s).(Human)
fmt.Println(v3, b)

v4, b := interface{}(s).(Student)
fmt.Println(v4, b)

v5, b := interface{}(s).(IDrink)
fmt.Println(v5, b)

v6, b := interface{}(s).(IEat)
fmt.Println(v6, b)

v7, b := interface{}(s).(IMove)
fmt.Println(v7, b)

v8, b := interface{}(s).(int)
fmt.Println(v8, b)

输出结果:

{ 0} false
{false} false
{{false}  0} false
{{{true} Jason 12}  1 English} true
{{{true} Jason 12}  1 English} true
{{{true} Jason 12}  1 English} true
<nil> false
0 false

上面的代码中,使用空接口 interface{} 对 s 进行了类型转换,因为 s 是 struct,不是 interface,而类型断言表达式要求点号左边必须为接口。

常用的方式应该是类似泛型的使用方式:

s1 := Student{Grade: 1, Major: "English", Human: Human{Name: "Jason", Age: 12, Being: Being{IsLive: true}}}
s2 := Student{Grade: 1, Major: "English", Human: Human{Name: "Tom", Age: 13, Being: Being{IsLive: true}}}
s3 := Student{Grade: 1, Major: "English", Human: Human{Name: "Mike", Age: 14, Being: Being{IsLive: true}}}
t1 := Teacher{Grade: 1, Major: "English", Salary: 2000, Human: Human{Name: "Michael", Age: 34, Being: Being{IsLive: true}}}
t2 := Teacher{Grade: 1, Major: "English", Salary: 3000, Human: Human{Name: "Tony", Age: 31, Being: Being{IsLive: true}}}
t3 := Teacher{Grade: 1, Major: "English", Salary: 4000, Human: Human{Name: "Ivy", Age: 40, Being: Being{IsLive: true}}}
drinkers := []IDrink{s1, s2, s3, t1, t2, t3}

for _, v := range drinkers {
    switch t := v.(type) {
    case Student:
        fmt.Println(t.Name, "is a Student, he/she needs more homework.")
    case Teacher:
        fmt.Println(t.Name, "is a Teacher, he/she needs more jobs.")
    default:
        fmt.Println("Invalid Human being:", t)
    }
}

输出结果:

Jason is a Student, he/she needs more homework.
Tom is a Student, he/she needs more homework.
Mike is a Student, he/she needs more homework.
Michael is a Teacher, he/she needs more jobs.
Tony is a Teacher, he/she needs more jobs.
Ivy is a Teacher, he/she needs more jobs.

这段代码中使用了 Type Switch,这种 switch 判断的目标是类型。

Golang:接口为重

了解了 Golang 的 OOP 相关的基本知识后,难免会有疑问,为什么 Golang 要用这种“非主流”的方式呢?

Java 之父 James Gosling 在某次会议上有过这样一次问答:

I once attended a Java user group meeting where James Gosling (Java’s inventor) was the featured speaker.

During the memorable Q&A session, someone asked him: “If you could do Java over again, what would you change?”

“I’d leave out classes,” he replied. After the laughter died down, he explained that the real problem wasn’t classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible.

大意是:

问:如果你重新做 Java,有什么是你想改变的?

答:我会把类(class)丢掉。真正的问题不在于类本身,而在于基于实现的继承(the extends relationship)。基于接口的继承(the implements relationship)是更好的选择,你应该在任何可能的时候避免使用实现继承。

我的理解是:实现之间应该少用继承式的强关联,多用接口这种弱关联。接口已经可以在很多方面替代继承的作用,比如多态和泛型。而且接口的关系松散、随意,可以有更高的自由度、更多的抽象角度。

以继承为特点的 OOP 只是编程世界的一种抽象方式,在 Golang 的世界里没有继承,只有组合和接口,这看起来更符合 Gosling 的设想。借用那位老人的话:黑猫白猫,捉住老鼠就是好猫。让我来继续探索吧。

注:刚刚学习 Golang 不久,后面可能会发现也许某些理解是错误的。随时修正。

时间: 2024-08-06 03:06:18

Golang OOP、继承、组合、接口的相关文章

Python基础day-18[面向对象:继承,组合,接口归一化]

继承: 在Python3中默认继承object类.但凡是继承了object类以及子类的类称为新式类(Python3中全是这个).没有继承的称为经典类(在Python2中没有继承object以及他的子类都是经典类.) 继承是类与类的关系,解决了代码重用的问题,减少冗余代码.在Python中是先定义父类,然后在定义子类.最后使用 子类来实例化对象. "继承是一种什么是什么的关系.例如:下面的s1是obj的子类." #父类的定义:class obj: #定义一个obj类 pass class

Python 继承和组合 接口

#解决代码重用的问题,减少代码冗余 #继承是一种什么'是'什么的关系 class People: def __init__(self, name, age): # print('People.__init__') self.name = name self.age = age def walk(self): print('%s is walking' %self.name) class Teacher(People): pass class Student(People): pass # t=T

c++ 继承,组合

1.什么是继承 A继承B,说明A是B的一种,并且B的所有行为对A都有意义 eg:A=WOMAN B=HUMAN A=鸵鸟 B=鸟 (不行),因为鸟会飞,但是鸵鸟不会. 2.什么是组合 若在逻辑上A是B的“一部分”(a part of),则不允许B从A派生,而是要用A和其它东西组合出B. 例如眼(Eye).鼻(Nose).口(Mouth).耳(Ear)是头(Head)的一部分,所以类Head应该由类Eye.Nose.Mouth.Ear组合而成,不是派生而成 3.继承的优点和缺点 优点: 容易进行新

组合接口时名字冲突问题

之前从没注意到实现多个接口可能会存在方法冲突的问题,在<Thinking in Java>中看到一个有趣的例子,其本质是重载和重写的问题,关于重载和重写的概念和区别可参看另一篇文章Java基础一:面向对象的特征.首先看例子: 1 interface I1 { 2 void f(); 3 } 4 5 interface I2 { 6 int f(int i); 7 } 8 9 interface I3 { 10 int f(); 11 } 12 13 class C { 14 public in

面向对象,继承和接口的使用理解

面向对象: 将你要将各种小类组合成一个大类的时候,面向对象的思想只需要在大类中添加一个小类的实例.这样可以提升代码的重用率. 继承: 当需要对父类进行扩张来得到一个拥有一些额外方法或者属性的时候使用.当父类的一些东西需要重写时使用也相当重要.抽象类:抽象类.中的抽象方法必须被实现,而可以有非abstract的方法和数据.这就区别于接口使得它更加灵活.在某些特定方法只会在这个类的子类中需要使用的时候,就没有必要特地为这个方法书写一个接口.抽象类用于抽象一个实际物品的时候特别有效. 接口: 因为接口

继承 与 接口

继承(上): 1. 提高了代码的重用性 2. 让类与类之间产生关系,有了这个关系,才有多态性 注意:千万不要为了获取其他功能,简化代码而继承: 必须是类与类之间有所属关系才可以继承,所属关系 is a. 在java语言中只能是单继承,不支持多继承. 因为:多继承容易带来安全隐患,当多个父类有相同的功能, 当功能内容不同时,子对象不确定运行哪一个. 但是java保留这种机制,用另一种形式表示:多实现. 并且存在多层继承,也就是一个继承体系. 如何使用一个继承体系中的功能? 要想使用体系,先查阅体系

12.面向对象(继承/super/接口/抽象类)

面向对象继承与派生继承继承顺序继承原理子类调用父类的方法(super)组合接口接口的概念:接口的概念解释和使用:python中的接口:抽象类 面向对象 继承与派生 继承 什么是继承?继承是一种创建新的类的方式 class A: pass class B(A): pass 在python中,新建的类可以继承自一个或者多个父类,原始类称为基类或者超类,新建的类称为派生类或者子类 python中类的继承分为,单继承和多继承. 查看继承的方法B.__bases__ 如果没有指定基类,python的类会默

继承与接口1

下面程序输出什么: #include<iostream> using namespace std; class A { public: void virtual f(){ cout<<"A"<<endl; } }; class B:public A{ public: void virtual f(){ cout<<"B"<<endl; } }; int main() { A* pa=new A();//定义

Javascript面向对象特性实现封装、继承、接口详细案例——进级高手篇

Javascript面向对象特性实现(封装.继承.接口) Javascript作为弱类型语言,和Java.php等服务端脚本语言相比,拥有极强的灵活性.对于小型的web需求,在编写javascript时,可以选择面向过程的方式编程,显得高效:但在实际工作中,遇到的项目需求和框架较大的情况下,选择面向对象的方式编程显得尤其重要,Javascript原生语法中没有提供表述面向对象语言特性的关键字和语法(如extends.implement).为了实现这些面向对象的特性,需要额外编写一些代码,如下.

JavaScript-原型&amp;原型链&amp;原型继承&amp;组合函数

小小的芝麻之旅: 今天学习了js的原型,要说原型,我们先简单说一下函数创建过程. 原型 每个函数在创建的时候js都自动添加了prototype属性,这就是函数的原型,原型就是函数的一个属性,类似一个指针.原型在函数的创建过程中由js编译器自动添加. <script type="text/javascript"> function Flower(name,area) { this.name=name; this.area=area; this.showName=myName;