面向对象

Go语言:面向对象编程_赋值

如果我们自己来修电脑,应该有哪些步骤呢?

第一步:判断问题的原因

第二步:找工具

第三步:暴力拆卸

这个修理的步骤就是面向过程,所谓的面向过程就是:强调的是步骤、过程、每一步都是自己亲自去实现的。

如果采用面向对象的思想,那么应该怎样修电脑呢?

Go语言:面向对象编程_赋值_02

找维修店的工作人员来帮我们修电脑,但是到底怎么修,我们是不用考虑的,也就是说我们不关心步骤与过程。

大家可以想一下,在生活中还有哪些事情是面向过程,面向对象的。

比如说,做饭,面向过程就是自己做,自己买菜,自己洗,自己炒,整个过程都有自己来完成,但是如果是面向对象,可以叫外卖,不用关心饭是怎么做的。

所以通过以上案例,大家能够体会出,面向过程就是强调的步骤,过程,而面向对象强调的是对象,找个人来做。

在面向对象中,还有两个概念是比较重要的,一是对象,二是类。

什么是对象呢?

万物皆对象,例如小明同学是一个对象,小亮同学也是一个对象

那么我们在生活中怎样描述一个对象呢?

比如,描述一下小明同学:

姓名:小明

性别:男

身高:180cm

体重:70kg

年龄:22岁

吃喝拉撒睡一切正常健康

吃喝嫖赌抽

通过以上的描述,可以总结出在生活中描述对象,可以通过特征(身高,体重,年龄等)和行为(爱好等)来进行描述。

那么在程序中,可以通过属性和方法(函数)来描述对象。属性就是特征,方法(函数)就是行为。所以说,对象必须具有属性和方法。虽然说,万物皆对象,但是在描述一个对象的时候,一定要具体不能泛指,例如,不能说“电灯”是一个对象,而是说具体的哪一台“电灯”。

大家可以思考一下,如果我们现在描述一下教室中某一台电灯,应该有哪些属性(特征)和方法(行为)呢?

下面我们在思考一下,下面这道题:

小明(一个学生)\杨老师\邻居王叔叔\小亮的爸爸\小亮的妈妈

找出这道题中所有对象的共性(所谓共性,指的是相同的属性和方法)。

所以说,我们可以将这些具有相同属性和相同方法的对象进行进一步的封装,抽象出来类这个概念。

类就是个模子,确定了对象应该具有的属性和方法。

对象是根据类创建出来的

例如:上面的案例中,我们可以抽出一个“人”类(都有年龄,性别,姓名等属性,都有吃饭,走路等行为),“小明”这个对象就是根据“人”类创建出来的,也就是说先有类后有对象。

GO语言中的面向对象

前面我们了解了一下,什么是面向对象,以及类和对象的概念。但是,GO语言中的面向对象在某些概念上和其它的编程语言还是有差别的。

严格意义上说,GO语言中没有类(class)的概念,但是我们可以将结构体比作为类,因为在结构体中可以添加属性(成员),方法(函数)。

面向对象编程的好处比较多,我们先来说一下“继承”,

所谓继承指的是,我们可能会在一些类(结构体)中,写一些重复的成员,我们可以将这些重复的成员,单独的封装到一个类(结构体)中,作为这些类的父类(结构体),我们可以通过如下图来理解:

Go语言:面向对象编程_多态_03

当然严格意义上,GO语言中是没有继承的,但是我们可以通过”匿名组合”来实现继承的效果。

匿名组合

匿名字段

一般情况下,定义结构体的时候是字段名与其类型一一对应,实际上Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。

当匿名字段也是一个结构体的时候,那么这个结构体所拥有的全部字段都被隐式地引入了当前定义的这个结构体。

//人
type Person struct {
    name string
    sex byte
    age int
}
//学生
type Student struct {
    Person    //匿名字段,那么默认Student就包含了Person的所有字段
    id int
    addr string
}

初始化

//人
type Person struct {
    name string
    sex byte
    age int
}
//学生
type Student struct {
    Person//匿名字段,那么默认Student就包含了Person的所有字段
    id int
    addr string
}
func main() {
    //顺序初始化
    s1 := Student{Person{"mike",'m',18},1,"sz"}
    //s1 = {Person:{name:mike sex:109 age:18}id:1 addr:sz}
    fmt.Printf("s1=%+v\n",s1)
    //s2 := Student{"mike",'m',18,1,"sz"}//err
    //部分成员初始化1
    s3 := Student{Person:Person{"lily",'f',19},id:2}
    //s3 = {Person:{name:lily sex:102 age:19}id:2 addr:}
    fmt.Printf("s3=%+v\n",s3)
    //部分成员初始化2
    s4 := Student{Person:Person{name:"tom"},id:3}
    //s4 = {Person:{name:tomsex:0age:0}id:3addr:}
    fmt.Printf("s4=%+v\n",s4)
}

成员的操作

var s1 Student//变量声明
//给成员赋值
s1.name = "mike"//等价于s1.Person.name="mike"
s1.sex = 'm'
s1.age = 18
s1.id = 1
s1.addr = "sz"
fmt.Println(s1)    //{{mike 109 18}1 sz}
var s2 Student//变量声明
s2.Person = Person{"lily",'f',19}
s2.id = 2
s2.addr = "bj"
fmt.Println(s2)    //{{lily 102 19}2 bj}

同名字段

//人
type Person struct{
    name string
    sex byte
    age int
}
//学生
type Student struct{
    Person    //匿名字段,那么默认Student就包含了Person的所有字段
    id int
    addr string
    name string    //和Person中的name同名
}
func main(){
    var s Student//变量声明
    //给Student的name,还是给Person赋值?
    s.name = "mike"
    //{Person:{name:sex:0age:0}id:0addr:name:mike}
    fmt.Printf("%+v\n",s)
    //默认只会给最外层的成员赋值
    //给匿名同名成员赋值,需要显示调用
    s.Person.name = "yoyo"
    //Person:{name:yoyosex:0age:0}id:0addr:name:mike}
    fmt.Printf("%+v\n",s)
}

其它匿名字段

非结构体类型

所有的内置类型和自定义类型都是可以作为匿名字段的:

type mystr string//自定义类型
type Person struct {
    name string
    sex byte
    age int
}
type Student struct {
    Person    //匿名字段,结构体类型
    int    //匿名字段,内置类型
    mystr    //匿名字段,自定义类型
}
func main() {
    //初始化
    s1 := Student{Person{"mike",'m',18},1,"bj"}
    //{Person:{name:mikesex:109age:18}int:1mystr:bj}
    fmt.Printf("%+v\n",s1)
    //成员的操作,打印结果:mike,m,18,1,bj
    fmt.Printf("%s,%c,%d,%d,%s\n",s1.name,s1.sex,s1.age,s1.int,s1.mystr)
}

结构体指针类型

type Person struct {    //人
    name string
    sex byte
    age int
}
type Student struct {//学生
    *Person    //匿名字段,结构体指针类型
    id int
    addr string
}
func main() {
    //初始化
    s1 := Student{&Person{"mike",'m',18},1,"bj"}
    //{Person:0xc0420023e0id:1addr:bj}
    fmt.Printf("%+v\n",s1)
    //mike,m,18
    fmt.Printf("%s,%c,%d\n",s1.name,s1.sex,s1.age)
    //声明变量
    var s2 Student
    s2.Person = new(Person)//分配空间
    s2.name = "yoyo"
    s2.sex = 'f'
    s2.age = 20
    s2.id = 2
    s2.addr = "sz"
    //yoyo10220220
    fmt.Println(s2.name,s2.sex,s2.age,s2.id,s2.age)
}
方法

概述

在面向对象编程中,一个对象其实也就是一个简单的值或者一个变量,在这个对象中会包含一些函数,这种带有接收者的函数,我们称为方法(method)。本质上,一个方法则是一个和特殊类型关联的函数。

一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接去操作对象,而是借助方法来做这些事情。

在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法。

⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver),方法的语法如下:

func (receiver ReceiverType) funcName (parameters) (results)
  • 参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名。
  • 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接⼝或指针。
  • 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。

为类型添加方法

基础类型作为接收者

type MyInt int//自定义类型,给int改名为MyInt

//在函数定义时,在其名字之前放上一个变量,即是一个方法
func (a MyInt) Add(b MyInt) MyInt {//面向对象
    return a + b
}

//传统方式的定义
func Add(a, b MyInt) MyInt {//面向过程
    return a + b
}

func main() {
    var a MyInt=1
    var b MyInt=1

//调用func (aMyInt) Add(bMyInt)
fmt.Println("a.Add(b)=",a.Add(b))//a.Add(b)=2

//调用func Add(a,bMyInt)
fmt.Println("Add(a,b)=",Add(a,b))//Add(a,b)=2
}

通过上面的例子可以看出,面向对象只是换了一种语法形式来表达。方法是函数的语法糖,因为receiver其实就是方法所接收的第1个参数。

注意:虽然方法的名字一模一样,但是如果接收者不一样,那么方法就不一样。

结构体作为接收者

方法里面可以访问接收者的字段,调用方法通过点(. )访问,就像struct里面访问字段一样:

type Person struct {
    name string
    sex byte
    age int
}

func (p Person) PrintInfo(){//给Person添加方法
    fmt.Println(p.name,p.sex,p.age)
}

func main() {
    p:=Person{"mike",'m',18}//初始化
    p.PrintInfo()//调用func(pPerson)PrintInfo()
}

值语义和引用语义

type Person struct {
    name string
    sex byte
    age int
}

//指针作为接收者,引用语义
func (p *Person) SetInfoPointer(){
    //给成员赋值
    (*p).name = "yoyo"
    p.sex = 'f'
    p.age = 22
}

//值作为接收者,值语义
func (p Person) SetInfoValue(){
//给成员赋值
    p.name = "yoyo"
    p.sex = 'f'
    p.age = 22
}

func main() {
    //指针作为接收者,引用语义
    p1 := Person{"mike",'m',18}//初始化
    fmt.Println("函数调用前=",p1)//函数调用前={mike10918}
    (&p1).SetInfoPointer()
    fmt.Println("函数调用后=",p1)//函数调用后={yoyo10222}

    fmt.Println("==========================")

    p2 := Person{"mike",'m',18}//初始化
    //值作为接收者,值语义
    fmt.Println("函数调用前=",p2)//函数调用前={mike10918}
    p2.SetInfoValue()
    fmt.Println("函数调用后=",p2)//函数调用后={mike10918}
}

方法集

类型的方法集是指可以被该类型的值调用的所有方法的集合。

用实例实例 value 和 pointer 调用方法(含匿名字段)不受⽅法集约束,编译器编总是查找全部方法,并自动转换 receiver 实参。

类型 *T 方法集

一个指向自定义类型的值的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。

如果在指针上调用一个接受值的方法,Go语言会聪明地将该指针解引用,并将指针所指的底层值作为方法的接收者。

类型 *T ⽅法集包含全部 receiver T + *T ⽅法:

type Person struct{
    name string
    sex byte
    age int
}

//指针作为接收者,引用语义
func (p *Person) SetInfoPointer(){
    (*p).name="yoyo"
    p.sex='f'
    p.age=22
}

//值作为接收者,值语义
func (p Person) SetInfoValue(){
    p.name="xxx"
    p.sex='m'
    p.age=33
}

func main() {
    //p为指针类型
    var p*Person = &Person{"mike",'m',18}
    p.SetInfoPointer()    //func (p)SetInfoPointer()

    p.SetInfoValue()    //func (*p)SetInfoValue()
    (*p).SetInfoValue()    //func (*p)SetInfoValue()
}

类型 T 方法集

一个自定义类型值的方法集则由为该类型定义的接收者类型为值类型的方法组成,但是不包含那些接收者类型为指针的方法。

但这种限制通常并不像这里所说的那样,因为如果我们只有一个值,仍然可以调用一个接收者为指针类型的方法,这可以借助于Go语言传值的地址能力实现。

type Person struct{
    name string
    sex byte
    age int
}

//指针作为接收者,引用语义
func (p *Person) SetInfoPointer(){
    (*p).name="yoyo"
    p.sex='f'
    p.age=22
}

//值作为接收者,值语义
func (p Person)SetInfoValue(){
    p.name="xxx"
    p.sex='m'
    p.age=33
}

func main() {
    //p为普通值类型
    var p Person = Person{"mike",'m',18}
    (&p).SetInfoPointer()    //func(&p)SetInfoPointer()
    p.SetInfoPointer()    //func(&p)SetInfoPointer()

    p.SetInfoValue()    //func(p)SetInfoValue()
    (&p).SetInfoValue()    //func(*&p)SetInfoValue()
}

匿名字段

方法的继承

如果匿名字段实现了一个方法,那么包含这个匿名字段的struct也能调用该方法。

type Person struct {
    name string
    sex byte
    age int
}

//Person定义了方法
func (p *Person) PrintInfo() {
    fmt.Printf("%s,%c,%d\n",p.name,p.sex,p.age)
}

type Student struct {
    Person//匿名字段,那么Student包含了Person的所有字段
    id int
    addr string
}

func main() {
    p := Person{"mike",'m',18}
    p.PrintInfo()

    s := Student{Person{"yoyo",'f',20},2,"sz"}
    s.PrintInfo()
}

方法的重写

type Person struct {
    name string
    sex byte
    age int
}
//Person定义了方法
func (p *Person) PrintInfo() {
    fmt.Printf("Person:%s,%c,%d\n",p.name,p.sex,p.age)
}
type Student struct {
    Person//匿名字段,那么Student包含了Person的所有字段
    id int
    addr string
}
//Student定义了方法
func (s *Student) PrintInfo() {
    fmt.Printf("Student:%s,%c,%d\n",s.name,s.sex,s.age)
}
func main() {
    p:=Person{"mike",'m',18}
    p.PrintInfo()    //Person:mike,m,18
    s:=Student{Person{"yoyo",'f',20},2,"sz"}
    s.PrintInfo()    //Student:yoyo,f,20
    s.Person.PrintInfo()    //Person:yoyo,f,20
}

方法值和方法表达式

类似于我们可以对函数进行赋值和传递一样,方法也可以进行赋值和传递。

根据调用者不同,方法分为两种表现形式:方法值和方法表达式。两者都可像普通函数那样赋值和传参,区别在于方法值绑定实例,⽽方法表达式则须显式传参。

方法值

type Person struct{
    name string
    sex byte
    age int
}
func (p *Person) PrintInfoPointer() {
    fmt.Printf("%p,%v\n",p,p)
}
func (p Person) PrintInfoValue(){
    fmt.Printf("%p,%v\n",&p,p)
}
func main() {
    p:=Person{"mike",'m',18}
    p.PrintInfoPointer()    //0xc0420023e0,&{mike 109 18}
    pFunc1:=p.PrintInfoPointer    //方法值,隐式传递 receiver
    pFunc1()    //0xc0420023e0,&{mike 109 18}
    pFunc2:=p.PrintInfoValue
    pFunc2()    //0xc042048420,{mike 109 18}
}

方法表达式

type Person struct {
    name string
    sex byte
    age int
}
func (p *Person) PrintInfoPointer() {
    fmt.Printf("%p,%v\n",p,p)
}
func (p Person) PrintInfoValue() {
    fmt.Printf("%p,%v\n",&p,p)
}
func main() {
    p:=Person{"mike",'m',18}
    p.PrintInfoPointer()//0xc0420023e0,&{mike 109 18}
    //方法表达式,须显式传参
    //func pFunc1 (p *Person))
    pFunc1:=(*Person).PrintInfoPointer
    pFunc1(&p)    //0xc0420023e0,&{mike 109 18}
    pFunc2:=Person.PrintInfoValue
    pFunc2(p)    //0xc042002460,{mike 109 18}
}
接口

在讲解具体的接口之前,先看如下问题。

使用面向对象的方式,设计一个加减的计算器

代码如下:

package main

import "fmt"

//父类
type Operate struct {
    num1 int
    num2 int
}

//加法子类
type Add struct {
    Operate
}

//减法子类
type Sub struct {
    Operate
}

//加法子类的方法
func (a *Add) Result() int {
    return a.num1 + a.num2
}

//减法子类的方法
func (s *Sub) Result() int {
    return s.num1 - s.num2
}

//方法调用
func main0201() {
    //创建加法对象
    //var a Add
    //a.num1 = 10
    //a.num2 = 20
    //v := a.Result()
    //fmt.Println(v)

    //创建减法对象
    var s Sub
    s.num1 = 10
    s.num2 = 20
    v := s.Result()
    fmt.Println(v)
}

以上实现非常简单,但是有个问题,在main( )函数中,当我们想使用减法操作时,创建减法类的对象,调用其对应的减法的方法。但是,有一天,系统需求发生了变化,要求使用加法,不再使用减法,那么需要对main( )函数中的代码,做大量的修改。将原有的代码注释掉,创建加法的类对象,调用其对应的加法的方法。有没有一种方法,让main( )函数,只修改很少的代码就可以解决该问题呢?有,要用到接下来给大家讲解的接口的知识点。

什么是接口

接口就是一种规范与标准,在生活中经常见接口,例如:笔记本电脑的USB接口,可以将任何厂商生产的鼠标与键盘,与电脑进行链接。为什么呢?原因就是,USB接口将规范和标准制定好后,各个生产厂商可以按照该标准生产鼠标和键盘就可以了。

在程序开发中,接口只是规定了要做哪些事情,干什么。具体怎么做,接口是不管的。这和生活中接口的案例也很相似,例如:USB接口,只是规定了标准,但是不关心具体鼠标与键盘是怎样按照标准生产的.

在企业开发中,如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口告诉开发人员你需要实现那些功能。

接口定义

接口定义的语法如下:

//先定义接口  一般以er结尾  根据接口实现功能
type Humaner interface {
    //方法  方法的声明
    sayhi()
}

怎样具体实现接口中定义的方法呢?

type student11 struct {
    name  string
    age   int
    score int
}

func (s *student11)sayhi()  {
    fmt.Printf("大家好,我是%s,今年%d岁,我的成绩%d分\n",s.name,s.age,s.score)
}

type teacher11 struct {
    name    string
    age     int
    subject string
}

func (t *teacher11)sayhi()  {
    fmt.Printf("大家好,我是%s,今年%d岁,我的学科是%s\n",t.name,t.age,t.subject)
}

具体的调用如下:

func main() {
    //接口是一种数据类型 可以接收满足对象的信息
    //接口是虚的  方法是实的
    //接口定义规则  方法实现规则
    //接口定义的规则  在方法中必须有定义的实现
    var h Humaner

    stu := student11{"小明",18,98}
    //stu.sayhi()
    //将对象信息赋值给接口类型变量
    h = &stu
    h.sayhi()

    tea := teacher11{"老王",28,"物理"}
    //tea.sayhi()
    //将对象赋值给接口 必须满足接口中的方法的声明格式
    h = &tea
    h.sayhi()
}

只要类(结构体)实现对应的接口,那么根据该类创建的对象,可以赋值给对应的接口类型。

接口的命名习惯以er结尾。

多态

接口有什么好处呢?实现多态。

多态就是同一个接口,使用不同的实例而执行不同操作

所谓多态指的是多种表现形式,如下图所示:
Go语言:面向对象编程_多态_04

使用接口实现多态的方式如下:

package main

import "fmt"

//先定义接口  一般以er结尾  根据接口实现功能
type Humaner1 interface {
    //方法  方法的声明
    sayhi()

}


type student12 struct {
    name  string
    age   int
    score int
}

func (s *student12)sayhi()  {
    fmt.Printf("大家好,我是%s,今年%d岁,我的成绩%d分\n",s.name,s.age,s.score)
}

type teacher12 struct {
    name    string
    age     int
    subject string
}

func (t *teacher12)sayhi()  {
    fmt.Printf("大家好,我是%s,今年%d岁,我的学科是%s\n",t.name,t.age,t.subject)
}

//多态的实现
//将接口作为函数参数  实现多态
func sayhello(h Humaner1)  {
    h.sayhi()
}

func main() {

    stu := student12{"小明",18,98}
    //调用多态函数
    sayhello(&stu)

    tea := teacher12{"老王",28,"Go"}
    sayhello(&tea)
}

关于接口的定义,以及使用接口实现多态,大家都比较熟悉了,但是多态有什么好处呢?现在还是以开始提出的计算器案例给大家讲解一下,在开始我们已经实现了一个加减功能的计算器,但是有同学感觉太麻烦了,因为实现加法,就要定义加法操作的类(结构体),实现减法就要定义减法的类(结构体),所以该同学实现了一个比较简单的加减法的计算器,如下所示:

1.使用面向对象的思想实现一个加减功能的计算器,可能有同学感觉非常简单,代码如下:

我们定义了一个类(结构体),然后为该类创建了一个方法,封装了整个计算器功能,以后要使用直接使用该类(结构体)创建对象就可以了。这就是面向对象总的封装性。

也就是说,当你写完这个计算器后,交给你的同事,你的同事要用,直接创建对象,然后调用GetResult()方法就可以,根本不需要关心该方法是怎样实现的.这不是我们前面在讲解面向对象概念时说到的,找个对象来干活吗?不需要自己去实现该功能。

2.大家仔细观察上面的代码,有什么问题吗?

现在让你在改计算器中,再增加一个功能,例如乘法,应该怎么办呢?你可能会说很简单啊,直接在GetResult( )方法的switch中添加一个case分支就可以了。

问题是:在这个过程中,如果你不小心将加法修改成了减法怎么办?或者说,对加法运算的规则做了修改怎么办?举例子说明:

你可以把该程序方法想象成公司中的薪资管理系统。如果公司决定对薪资的运算规则做修改,由于所有的运算规则都在Operation类中的GetResult()方法中,所以公司只能将该类的代码全部给你,你才能进行修改。这时,你一看自己作为开发人员工资这么低,心想“TMD,老子累死累活才给这么点工资,这下有机会了”。直接在自己工资后面加了3000

numA+numB+3000

所以说,我们应该将加减等运算分开,不应该全部糅合在一起,这样你修改加的时候,不会影响其它的运算规则:

具体实现如下:

现在已经将各个操作分开了,并且这里我们还定义了一个父类(结构体),将公共的成员放在该父类中。如果现在要修改某项运算规则,只需将对应的类和方法发给你,进行修改就可以了。

这里的实现虽然将各个运算分开了,但是与我们第一次实现的还是有点区别。我们第一次实现的加减计算器也是将各个运算分开了,但是没有定义接口。那么该接口的意义是什么呢?继续看下面的问题。

3:现在怎样调用呢?

这就是我们一开始给大家提出的问题,如果调用的时候,直接创建加法操作的对象,调用对应的方法,那么后期要改成减法呢?需要做大量的修改,所以问题解决的方法如下:

创建了一个类OperationFactory,在改类中添加了一个方法CreateOption( )负责创建对象,如果输入的是“+”,创建

OperationAdd的对象,然后调用OperationWho( )方法,将对象的地址传递到该方法中,所以变量i指的就是OperationAdd,接下来在调用GetResult( )方法,实际上调用的是OperationAdd类实现的GetResult( )方法。

同理如果传递过来的是“-”,流程也是一样的。

所以,通过该程序,大家能够体会出多态带来的好处。

4:最后调用

这时会发现调用,非常简单,如果现在想计算加法,只要将”-”,修改成”+”就可以。也就是说,除去了main( )函数与具体运算类的依赖。

当然程序经过这样设计以后:如果现在修改加法的运算规则,只需要修改OperationAdd类中对应的方法,

不需要关心其它的类,如果现在要增加“乘法” 功能,应该怎样进行修改呢?第一:定义乘法的类,完成乘法运算。

第二:在OperationFactory类中CrateOption( )方法中添加相应的分支。但是这样做并不会影响到其它的任何运算。

大家可以自己尝试实现“乘法”与“除法”的运算。

在使用面向对象思想解决问题时,一定要先分析,定义哪些类,哪些接口,哪些方法。把这些分析定义出来,然后在考虑具体实现。

最后完整代码如下:

package main

import "fmt"

//定义接口
type Opter interface {
    //方法声明
    Result() int
}

//父类
type Operate struct {
    num1 int
    num2 int
}
//加法子类
type Add struct {
    Operate
}

//加法子类的方法
func (a *Add) Result() int {
    return a.num1 + a.num2
}

//减法子类
type Sub struct {
    Operate
}

//减法子类的方法
func (s *Sub) Result() int {
    return s.num1 - s.num2
}

//创建一个类负责对象创建
//工厂类
type Factory struct {
}

func (f *Factory) Result(num1 int, num2 int, ch string) {
    switch ch {
    case "+":
        var a Add
        a.num1 = num1
        a.num2 = num2
        Result(&a)
    case "-":
        var s Sub
        s.num1 = num1
        s.num2 = num2
        Result(&s)

    }
}

//通过设计模式调用
func main() {
    //创建工厂对象
    var f Factory
    f.Result(10, 20, "+")
}

下面我们将接口其它的知识点再给大家说一下:

4.接口继承与转换(了解)
接口也可以实现继承:

package main

import "fmt"

//先定义接口  一般以er结尾  根据接口实现功能
type Humaner2 interface {   //子集
    //方法  方法的声明
    sayhi()

}

type Personer interface {  //超集
    Humaner2   //继承sayhi()

    sing(string)
}

type student13 struct {
    name  string
    age   int
    score int
}

func (s *student13)sayhi()  {
    fmt.Printf("大家好,我是%s,今年%d岁,我的成绩%d分\n",s.name,s.age,s.score)
}

func (s *student13)sing(name string)  {
    fmt.Println("我为大家唱首歌",name)
}

func main() {
    //接口类型变量定义
    var h Humaner2
    var stu student13 = student13{"小吴",18,59}
    h = &stu
    h.sayhi()

    //接口类型变量定义
    var p Personer
    p = &stu
    p.sayhi()
    p.sing("大碗面")
}

接口继承后,可以实现“超集”接口转换“子集”接口,代码如下:

package main

import "fmt"

//先定义接口  一般以er结尾  根据接口实现功能
type Humaner2 interface {   //子集
    //方法  方法的声明
    sayhi()

}

type Personer interface {  //超集
    Humaner2   //继承sayhi()

    sing(string)
}

type student13 struct {
    name  string
    age   int
    score int
}

func (s *student13)sayhi()  {
    fmt.Printf("大家好,我是%s,今年%d岁,我的成绩%d分\n",s.name,s.age,s.score)
}

func (s *student13)sing(name string)  {
    fmt.Println("我为大家唱首歌",name)
}

func main()  {
    //接口类型变量定义
    var h Humaner2  //子集
    var p Personer    //超集
    var stu student13 = student13{"小吴",18,59}

    p = &stu
    //将一个接口赋值给另一个接口
    //超集中包含所有子集的方法
    h = p  //ok

    h.sayhi()

    //子集不包含超集
    //不能将子集赋值给超集
    //p = h  //err
    //p.sayhi()
    //p.sing("大碗面")
}

空接口

空接口(interface{})不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。

例如:

var i interface{}
//接口类型可以接收任意类型的数据
//fmt.Println(i)
fmt.Printf("%T\n",i)
i = 10
fmt.Println(i)
fmt.Printf("%T\n",i)

当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子是标准库fmt中PrintXXX系列的函数,例如:

func Printf(fmt string, args ...interface{})
func Println(args ...interface{})

如果自己定义函数,可以如下:

func Test(arg ...interface{}) {

}

Test( )函数可以接收任意个数,任意类型的参数。

类型查询

我们知道interface的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:

  • comma-ok断言
  • switch测试

comma-ok断言

Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。

如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。

var i []interface{}

i = append(i, 10, 3.14, "aaa", demo15)

for _, v := range i {

    if data, ok := v.(int); ok {
        fmt.Println("整型数据:", data)
    } else if data, ok := v.(float64); ok {
        fmt.Println("浮点型数据:", data)
    } else if data, ok := v.(string); ok {
        fmt.Println("字符串数据:", data)
    } else if data, ok := v.(func()); ok {
        //函数调用
        data()
    }
}

switch测试

var i []interface{}

i = append(i, 10, 3.14, "aaa", demo15)

for _,data := range i{
    switch value:=data.(type) {
    case int:
        fmt.Println("整型",value)
    case float64:
        fmt.Println("浮点型",value)
    case string:
        fmt.Println("字符串",value)
    case func():
        fmt.Println("函数",value)
    }
}