面向对象编程思想-抽象

抽象的介绍

我们在前面去定义一个结构体时候,实际上就是把一类事物的共有的属性(字段)行为(方法)提取出来,形成一个物理模型(结构体)。这种研究问题的方法称为抽象

面向对象编程三大特性-封装

基本介绍

Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一样

封装介绍

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作

封装的理解和好处

  1. 隐藏实现细节
  2. 可以对数据进行验证,保证安全合理

    如何体现封装

  3. 对结构体中的属性进行封装

  4. 通过方法,包 实现封装

    封装的实现步骤

  5. 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private)

  6. 给结构体所在包提供一个工厂模式的函数,首字母大写,类似一个构造函数
  7. 提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值

    1. func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {
    2. //加入数据验证的业务逻辑var.字段 = 参数
    3. }
  8. 提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值

    func (var 结构体类型名) GetXxx() {
     return var.xxx;
    }
    

    💡特别说明:在 Golang 开发中并没有特别强调封装,这点并不像 Java. 所以提醒学过java 的朋友,不用总是用 java 的语法特性来看待 Golang, Golang 本身对面向对象的特性做了简化的

    快速入门案例

    声明一个person结构体,不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证。 设计: model 包(person.go) main 包(main.go 调用 Person 结构体)

package model
import "fmt"

type person struct {
    Name string
    age int   //其它包不能直接访问..
    sal float64
}

//写一个工厂模式的函数,相当于构造函数
func NewPerson(name string) *person {
    return &person{
        Name : name,
    }
}

//为了访问age 和 sal 我们编写一对SetXxx的方法和GetXxx的方法
func (p *person) SetAge(age int) {
    if age >0 && age <150 {
        p.age = age
    } else {
        fmt.Println("年龄范围不正确..")
        //给程序员给一个默认值
    }
}

func (p *person) GetAge() int {
    return p.age
}


func (p *person) SetSal(sal float64) {
    if sal >= 3000 && sal <= 30000 {
        p.sal = sal
    } else {
        fmt.Println("薪水范围不正确..")
    }
}

func (p *person) GetSal() float64 {
    return p.sal
}
package main
import (
    "fmt"
    "go_code/chapter11/encapsulate/model"
)

func main() {
    p := model.NewPerson("smith")
    p.SetAge(18)
    p.SetSal(5000)
    fmt.Println(p)
    fmt.Println(p.Name, " age =", p.GetAge(), " sal = ", p.GetSal())
}

面向对象编程三大特性-继承

继承基本介绍和示意图

继承可以解决代码复用,让我们的编程更加靠近人类思维。
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。
其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个 匿名结构体即可
也就是说:在 Golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性
面向对象编程(下) - 图1

嵌套匿名结构体的基本语法

type Goods struct { 
    Name string 
    Price int
}
type Book struct {
    Goods    //这里就是嵌套匿名结构体 Goods
    Writer string
}

快速入门案例

package main

import (
    "fmt"
)

//编写一个学生考试系统

type Student struct {
    Name string
    Age int
    Score int
}

//将Pupil 和 Graduate 共有的方法也绑定到 *Student
func (stu *Student) ShowInfo() {
    fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n", stu.Name, stu.Age, stu.Score)
}
func (stu *Student) SetScore(score int) {
    //业务判断
    stu.Score = score
}

//给 *Student 增加一个方法,那么 Pupil 和 Graduate都可以使用该方法
func (stu *Student) GetSum(n1 int, n2 int) int {
    return n1 + n2
}

//小学生
type Pupil struct { 
    Student //嵌入了Student匿名结构体
}

//显示他的成绩

//这时Pupil结构体特有的方法,保留
func (p *Pupil) testing() {
    fmt.Println("小学生正在考试中.....")
}

//大学生, 研究生。。


//大学生
type Graduate struct {
    Student //嵌入了Student匿名结构体
}

//显示他的成绩
//这时Graduate结构体特有的方法,保留
func (p *Graduate) testing() {
    fmt.Println("大学生正在考试中.....")
}

//代码冗余.. 高中生....

func main() {

    //当我们对结构体嵌入了匿名结构体使用方法会发生变化
    pupil := &Pupil{}
    pupil.Student.Name = "tom~"
    pupil.Student.Age = 8
    pupil.testing() 
    pupil.Student.SetScore(70)
    pupil.Student.ShowInfo()
    fmt.Println("res=", pupil.Student.GetSum(1, 2))


    graduate := &Graduate{}
    graduate.Student.Name = "mary~"
    graduate.Student.Age = 28
    graduate.testing() 
    graduate.Student.SetScore(90)
    graduate.Student.ShowInfo()
    fmt.Println("res=", graduate.Student.GetSum(10, 20))
}
小学生正在考试中.....
学生名=tom~ 年龄=8 成绩=70
res= 3
大学生正在考试中.....
学生名=mary~ 年龄=28 成绩=90
res= 30

继承给编程带来的便利

  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了

    继承的深入讨论

  3. 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法, 都可以使用。

  4. 匿名结构体字段访问可以简化
  5. 结构体匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分 ```go type A struct { Name string age int }

func (a *A) SayOk() { fmt.Println(“A SayOk”, a.Name) }

func (a *A) hello() { fmt.Println(“A hello”, a.Name) }

type B struct { A Name string }

func (b *B) SayOk() { fmt.Println(“B SayOk”, b.Name) }

func main() { var b B b.A.Name = “tom” b.A.age = 19 b.A.SayOk()//A SayOk tom b.A.hello()//A hello tom //当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.Name //编译器会先看 b 对应的类型有没有 Name, 如果有,则直接调用 B 类型的 Name 字段 //如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用,如果没有继续查找..如果都找不到就报错. b.Name = “smith” b.age = 20 b.SayOk()//B SayOk smith b.hello()//A hello tom

// var b B
// b.Name = "jack" // ok
// b.A.Name = "scott"//ok
// b.age = 100  //ok
// b.SayOk()  // B SayOk  jack
// b.A.SayOk() //  A SayOk scott
// b.hello() //  A hello scott

}


4. 结构体嵌入两个(或多个)匿名结构体,如**两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法)**,在访问时,就必须明确指定匿名结构体名字,否则编译报错
```go
type A struct {
    Name string
    age int
}
type B struct {
    Name string
    Score float64
}
type C struct {
    A
    B
    //Name string
}

func main() {
    var c C
    //如果c 没有Name字段,而A 和 B有Name, 这时就必须通过指定匿名结构体名字来区分
    //所以 c.Name 就会包编译错误, 这个规则对方法也是一样的!
    //c.Name = "tom" // error
    c.A.Name = "tom" // ok
    fmt.Println("c")
}
  1. 如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字 ```go type A struct { Name string age int } type D struct { a A //有名结构体 }

func main() { //如果D 中是一个有名结构体,则访问有名结构体的字段时,就必须带上有名结构体的名字 //比如 d.a.Name var d D d.a.Name = “jack”//ok //d.Name = “tom” // error }


6. 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接**指定各个匿名结构体字段的值**
```go
type Goods struct {
    Name string
    Price float64
}

type Brand struct {
    Name string
    Address string
}

type TV struct {
    Goods
    Brand    
}

type TV2 struct {
    *Goods
    *Brand    
}

func main() {
    //嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
    tv := TV{ Goods{"电视机001", 5000.99},  Brand{"海尔", "山东"}, }

    //演示访问Goods的Name
    fmt.Println(tv.Goods.Name)
    fmt.Println(tv.Price) 

    tv2 := TV{ 
        Goods{
            Price : 5000.99,
            Name : "电视机002", 
        },  
        Brand{
            Name : "夏普", 
            Address :"北京",
        }, 
    }

    fmt.Println("tv", tv)
    fmt.Println("tv2", tv2)

    tv3 := TV2{ &Goods{"电视机003", 7000.99},  &Brand{"创维", "河南"}, }

    tv4 := TV2{ 
            &Goods{
                Name : "电视机004", 
                Price : 9000.99,
            },  
            &Brand{
                Name : "长虹", 
                Address : "四川",
            }, 
        }

    fmt.Println("tv3", *tv3.Goods, *tv3.Brand)
    fmt.Println("tv4", *tv4.Goods, *tv4.Brand)
}
tv {{电视机001 5000.99} {海尔 山东}}
tv2 {{电视机002 5000.99} {夏普 北京}}
tv3 {电视机003 7000.99} {创维 河南}
tv4 {电视机004 9000.99} {长虹 四川}
  1. 结构体的匿名字段可以是基本数据类型 ```go type Monster struct { Name string Age int }

type E struct { Monster int //基本数据类型匿名字段,相同数据类型的匿名字段只能存在一个 n int //基本数据类型具名字段 } func main() { //演示一下匿名字段时基本数据类型的使用 var e E e.Name = “狐狸精” e.Age = 300 e.int = 20 e.n = 40 fmt.Println(“e=”, e)//e= {{狐狸精 300} 20 40} }

<a name="kaG0f"></a>
## 多重继承
<a name="ayL7p"></a>
### 多重继承说明
如一个 **struct 嵌套了多个匿名结构体**,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。
<a name="U8Smj"></a>
### 案例演示
```go
type Goods struct {
    Name string
    Price float64
}

type Brand struct {
    Name string
    Address string
}
//此处TV struct 同时继承了Goods 和 Brand
type TV struct {
    Goods
    Brand    
}

多重继承细节说明

  1. 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。
  2. 为了保证代码的简洁性,建议大家尽量不使用多重继承

    接口(interface)

    基本介绍

    在 Golang 中 多态特性主要是通过接口来体现的,interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个自定义类型(比如结构体 Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)

    接口快速入门

    ```go //声明/定义一个接口 type Usb interface { //声明了两个没有实现的方法 Start() Stop() }

type Phone struct {

}

//让Phone 实现 Usb接口的方法 func (p Phone) Start() { fmt.Println(“手机开始工作。。。”) } func (p Phone) Stop() { fmt.Println(“手机停止工作。。。”) }

type Camera struct {

} //让Camera 实现 Usb接口的方法 func (c Camera) Start() { fmt.Println(“相机开始工作~~~。。。”) } func (c Camera) Stop() { fmt.Println(“相机停止工作。。。”) }

//计算机 type Computer struct {

}

//编写一个方法Working 方法,接收一个Usb接口类型变量 //只要是实现了 Usb接口 (所谓实现Usb接口,就是指实现了 Usb接口声明所有方法) func (c Computer) Working(usb Usb) {

//通过usb接口变量来调用Start和Stop方法
usb.Start()
usb.Stop()

}

func main() { //测试 //先创建结构体变量 computer := Computer{} phone := Phone{} camera := Camera{}

//关键点
computer.Working(phone)
computer.Working(camera) //

}

```go
手机开始工作。。。
手机停止工作。。。   
相机开始工作~~~。。。
相机停止工作。。。

基本语法

type 接口名 interface{
    method1(参数列表) 返回值列表
    method2(参数列表) 返回值列表
    ...
}
//实现接口所有方法
func(t 自定义类型) method1(参数列表) 返回值列表{
    //方法实现
}
func(t 自定义类型) method2(参数列表) 返回值列表{
    //方法实现
}

小结:

  1. 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态高内聚低偶合的思想。
  2. Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang 中没有 implement 这样的关键字

    注意事项和细节

  3. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例) ```go type AInterface interface { Say() }

type Stu struct { Name string }

func (stu Stu) Say() { fmt.Println(“Stu Say()”) }

func main() { var stu Stu //结构体变量,实现了 Say() 实现了 AInterface var a AInterface = stu //指向一个实现了该接口的自定义类型的变量(实例) a.Say()//Stu Say() }


2. 接口中所有的方法都没有方法体,即都是没有实现的方法
2. 在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
2. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
2. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
```go
type AInterface interface {
    Say()
}

type integer int

func (i integer) Say() {
    fmt.Println("integer Say i =" ,i )
}

func main() {
    var i integer = 10
    var b AInterface = i
    b.Say() // integer Say i = 10
}
  1. 一个自定义类型可以实现多个接口 ```go type AInterface interface { Say() }

type BInterface interface { Hello() } type Monster struct {

} func (m Monster) Hello() { fmt.Println(“Monster Hello()~~”) }

func (m Monster) Say() { fmt.Println(“Monster Say()~~”) }

func main() { //Monster实现了AInterface 和 BInterface var monster Monster var a2 AInterface = monster var b2 BInterface = monster a2.Say() b2.Hello() }


7. Golang 接口中不能有任何变量
```go
type AInterface interface {
    Name string //error
    Say()
}
  1. 一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必须将 B,C 接口的方法也全部实现 ```go type BInterface interface { test01() }

type CInterface interface { test02() }

type AInterface interface { BInterface CInterface test03() }

//如果需要实现AInterface,就需要将BInterface CInterface的方法都实现 type Stu struct { } func (stu Stu) test01() {

} func (stu Stu) test02() {

} func (stu Stu) test03() {

}

func main() { var stu Stu var a AInterface = stu a.test01() }


9. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
9. 空接口  interface{}    没有任何方法,**所以所有类型都实现了空接口**,    即我们可以**把任何一个变量赋给空接口**。
```go
type T  interface{

}

type Stu struct {
    Name string
}

func main() {
    var t T = stu //ok
    fmt.Println(t)
    var t2 interface{}  = stu
    var num1 int = 1
    t2 = num1
    t = num1
    fmt.Println(t2, t)//1 1
}

实现接口 vs 继承

package main
import (
    "fmt"
)

//Monkey结构体
type Monkey struct {
    Name string
}

func (this *Monkey) climbing() {
    fmt.Println(this.Name, " 生来会爬树..")
}

//声明接口
type BirdAble interface {
    Flying()
}

type FishAble interface {
    Swimming()
}

//LittleMonkey结构体
type LittleMonkey struct {
    Monkey //继承
}

//让LittleMonkey实现BirdAble
func (this *LittleMonkey) Flying() {
    fmt.Println(this.Name, " 通过学习,会飞翔...")
}

//让LittleMonkey实现FishAble
func (this *LittleMonkey) Swimming() {
    fmt.Println(this.Name, " 通过学习,会游泳..")
}

func main() {
    //创建一个LittleMonkey 实例
    monkey := LittleMonkey{
        Monkey {
            Name : "悟空",
        },
    }
    monkey.climbing()
    monkey.Flying()
    monkey.Swimming()
}

说明:

  1. 当 A 结构体继承了 B 结构体,那么 A 结构就自动的继承了 B 结构体的字段和方法,并且可以直接使用
  2. 当 A 结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我们可以认为:实现接口是对继承机制的补充

接口和继承解决的解决的问题不同

  • 继承的价值主要在于:解决代码的复用性可维护性
  • 接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。

接口比继承更加灵活

  • 继承是满足 is - a 的关系
  • 接口只需满足 like - a 的关系

接口在一定程度上实现代码解耦

面向对象编程-多态

基本介绍

变量(实例)具有多种形态。面向对象的第三大特征,在 Go 语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。

快速入门

在前面的 Usb 接口案例,Usb usb ,既可以接收手机变量,又可以接收相机变量,就体现了 Usb 接口 多态特性。

接口体现多态的两种形式

  1. 多态参数

在前面的 Usb 接口案例,Usb usb ,即可以接收手机变量,又可以接收相机变量,就体现了 Usb 接口 多态。

  1. 多态数组 ```go //声明/定义一个接口 type Usb interface { //声明了两个没有实现的方法 Start() Stop() }

type Phone struct { name string }

//让Phone 实现 Usb接口的方法 func (p Phone) Start() { fmt.Println(“手机开始工作。。。”) } func (p Phone) Stop() { fmt.Println(“手机停止工作。。。”) }

type Camera struct { name string } //让Camera 实现 Usb接口的方法 func (c Camera) Start() { fmt.Println(“相机开始工作。。。”) } func (c Camera) Stop() { fmt.Println(“相机停止工作。。。”) }

func main() { //定义一个Usb接口数组,可以存放Phone和Camera的结构体变量 //这里就体现出多态数组 var usbArr [3]Usb usbArr[0] = Phone{“vivo”} usbArr[1] = Phone{“小米”} usbArr[2] = Camera{“尼康”}

fmt.Println(usbArr)//[{vivo} {小米} {尼康}]

}

<a name="Oed9L"></a>
# 类型断言
<a name="UQ1c7"></a>
## 基本介绍
类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言, 具体的如下:
```go
    var x interface{}
    var b2 float32 = 1.1
    x = b2  //空接口,可以接收任意类型
    // x=>float32 [使用类型断言]

    //类型断言(不带检测的)
    //y := x.(float32)//如果类型不匹配,就会报 panic
    //fmt.Printf("y 的类型是 %T 值是=%v", y, y)

    //类型断言(带检测的)
    if y, ok := x.(float32); ok {
        fmt.Println("convert success")
        fmt.Printf("y 的类型是 %T 值是=%v", y, y)
    } else {
        fmt.Println("convert fail")
    }
    fmt.Println("继续执行...")

说明:

  1. 在进行类型断言时,如果类型不匹配,就会报 panic, 因此进行类型断言时,要确保原来的空接口指向的就是断言的类型
  2. 在进行断言时,带上检测机制,如果成功就 ok,否则也不要报 panic

    案例演示

    ```go package main import ( “fmt” )

//声明/定义一个接口 type Usb interface { //声明了两个没有实现的方法 Start() Stop() }

type Phone struct { name string }

//让Phone 实现 Usb接口的方法 func (p Phone) Start() { fmt.Println(“手机开始工作。。。”) } func (p Phone) Stop() { fmt.Println(“手机停止工作。。。”) }

func (p Phone) Call() { fmt.Println(“手机 在打电话..”) }

type Camera struct { name string } //让Camera 实现 Usb接口的方法 func (c Camera) Start() { fmt.Println(“相机开始工作。。。”) } func (c Camera) Stop() { fmt.Println(“相机停止工作。。。”) }

type Computer struct {

}

func (computer Computer) Working(usb Usb) { usb.Start() //如果usb是指向Phone结构体变量,则还需要调用Call方法 //类型断言..[注意体会!!!] if phone, ok := usb.(Phone); ok { phone.Call() } usb.Stop() }

func main() { //定义一个Usb接口数组,可以存放Phone和Camera的结构体变量 //这里就体现出多态数组 var usbArr [3]Usb usbArr[0] = Phone{“vivo”} usbArr[1] = Phone{“小米”} usbArr[2] = Camera{“尼康”}

//遍历usbArr
//Phone还有一个特有的方法call(),请遍历Usb数组,如果是Phone变量,
//除了调用Usb 接口声明的方法外,还需要调用Phone 特有方法 call. =》类型断言
var computer Computer
for _, v := range usbArr{
    computer.Working(v)
    fmt.Println()
}
//fmt.Println(usbArr)

}

```go
//定义Student类型
type Student struct {

}

//编写一个函数,可以判断输入的参数是什么类型
func TypeJudge(items... interface{}) {
    for index, x := range items {
        switch x.(type) {
            case bool :
                fmt.Printf("第%v个参数是 bool 类型,值是%v\n", index, x)
            case float32 :
                fmt.Printf("第%v个参数是 float32 类型,值是%v\n", index, x)
            case float64 :
                fmt.Printf("第%v个参数是 float64 类型,值是%v\n", index, x)
            case int, int32, int64 :
                fmt.Printf("第%v个参数是 整数 类型,值是%v\n", index, x)
            case string :
                fmt.Printf("第%v个参数是 string 类型,值是%v\n", index, x)
            case Student :
                fmt.Printf("第%v个参数是 Student 类型,值是%v\n", index, x)
            case *Student :
                fmt.Printf("第%v个参数是 *Student 类型,值是%v\n", index, x)
            default :
                fmt.Printf("第%v个参数是  类型 不确定,值是%v\n", index, x)
        }
    }
}

func main() {

    var n1 float32 = 1.1
    var n2 float64 = 2.3
    var n3 int32 = 30
    var name string = "tom"
    address := "北京"
    n4 := 300

    stu1 := Student{}
    stu2 := &Student{}

    TypeJudge(n1, n2, n3, name, address, n4, stu1, stu2)
}