面向对象的三大特性

面向对象编程中的三大特性指的是:继承、多态和封装。多态是基于接口实现的。

  • 继承
  • 封装
  • 接口
  • 多态

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

银行存取款

package main
import "fmt"

// 定义账户结构体
// 账户、密码、余额
type Account struct{
  Number string
  Pwd string
  Balance float64
}

// 存款
func (account *Account) Deposite(money float64, pwd string){
  if pwd != account.Pwd{
    fmt.Println("输入的密码不正确")
  }
  if money < 0{
    fmt.Println("余额不足")
  }
  account.Balance += money   // 余额+money
  fmt.Println("存款成功")
}

// 取款
func (account *Account) WithDraw(money float64, pwd string){
  // 下面的步骤有体现封装对数据校验特性
  if pwd != account.Pwd{
    fmt.Println("输入的密码不正确")
  }
  if money <= 0 || money > account.Balance{  // 所取的钱不能小于等于0,或者大于余额
    fmt.Println("余额不足")
  }
  account.Balance -= money   // 余额 - money
  fmt.Println("取款成功")
}

// 查询
func (account *Account) Query(pwd string){
  if pwd != account.Pwd{
    fmt.Println("输入的密码不正确")
    return
  }
  fmt.Printf("你的账号是=%v 余额=%v \n", account.Number, account.Balance)
}

func main(){
  account := &Account{
    Number: "gs12345",
    Pwd: "666666",
    Balance: 100
  }

  account.Query("666666")   //100
  account.Deposite(200, "666666")
  account.Query("666666")  //300
  account.WithDraw(150, "666666")
  account.Query("666666")  //150
}

继承

继承入门

继承可以解决代码复用,当结构体中存在相同的属性和方法时,可以从这些结构体中抽象出结构体,其他的结构体中不需要重新定义这些相同的属性和方法。

如果一个结构体中嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

type Goods struct{
  Name string
  Price float64
}

type Book struct {
  Goods    // 嵌套上面的匿名结构体
  Writer string
}
// 关于学生的继承栗子

package main
import "fmt"

type Student struct{
  Name string
  Age int
  Score int
}

// 将共有的方法进行绑定
func (stu *Student) ShowInfo(){
  fmt.Println(stu.Name, stu.Age, stu,Score)
}
func (stu *Student) SetScore(score int){
  stu.Socre = score
}

// 给 *student 增加一个方法
func (stu *Student) GetSum(a, b int) int{
  return a + b
}


// 小学生
type Pupil struct{
  Student   // 匿名结构体
}
// 独有的方法进行保留
func (p *Pupil) testing{
  fmt.Println("小学生正在考试...")
}

//大学生
type Graduate struct{
  Student   // 匿名结构体
}
func (g *Graduate) testing{
  fmt.Println("大学生正在考试...")
}

func main(){
  // 小学生
  pupil := &Pupil{}
  pupil.Student.Name = "小明"
  pupil.Student.Age = 18
  pupil.Student.SetScore(70)
  pup.Student.ShowIfno()
  fmt.Println("res=", pupil.Student.GetSum(1, 2))

  // 大学生
  graduate := &Graduate{}
  pupil.Student.Name = "小红"
  pupil.Student.Age = 24
  pupil.Student.SetScore(89)
  pup.Student.ShowIfno()
  fmt.Println("res=", graduate.Student.GetSum(1, 2))
}
继承深入
  1. 结构体可以使用匿名结构体的所有字段,包含大写和小写都可以
  2. 匿名结构体字段访问可以简化
  3. 如果结构体和匿名结构体中含有相同字段,编译器采用的是就近访问原则;如果需要希望访问匿名结构体的字段和方法,可以通过匿名结构体名来进行区分
  4. 结构体中嵌入两个或者多个匿名结构体,如果两个结构体中有相同的字段或者方法(同时结构体本身没有同名的字段或者方法),在访问的时候,必须指明匿名结构体名字,否则编译报错。
  5. 如果结构体中嵌套了有名结构体,这种模式就是组合,此时访问结构体中的字段或者属性,必须带上结构体的名字。
package main
import "fmt"

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)
}

type C struct{
  A
  B
  // Name string
}

type D struct{
  a A // 有名结构体
  Name string
}

func main(){
  var b B
  b.A.Name = "tom"
  b.A.age = 18
  b.A.Sayok()  // 大小写均可访问
  b.A.hello()

  // 第二点:上面的简化写法
  b.Name = "smith"
  b.A.Name = "jackson"
  b.age = 27
  b.Sayok()   // 第三点:访问的是本身的Sayok()方法
  b.hello()   // A Sayok jackson

  var c C
  c.A.Name = "john"
  fmt.Println("c")

  var d D
  d.a.Name = "mike"   // 如果D中没有,则必须带上结构体的名字
  d.Name = "jack"   // 先找D本身中有没有 Name 字段
}

封装encapsulation

把抽象的字段和对字段的操作封装在一起,数据被保护在内部。程序的其他包只能通过被授权的方式才能对其进行操作。电视机的操作就是典型的封装

  • 隐藏实现细节
  • 可以对数据进行验证,保证安全合理
  • 对结构体的属性进行封装
  • 通过方法和包等实现封装
// model/person.go

package model

import "fmt"

type person struct{
	Name string
	age int   // 不可导出的字段
	sal float64
}

// 写一个工厂函数,类似构造函数
func NewPerson(name string) *person{
	return &person{
		Name: name,
	}
}

// 访问age和sal
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
}
// main/main.go

package main

import (
	"code/char27-encapsulation/model"
	"fmt"
)

func main(){
	p := model.NewPerson("xiaoming")

	p.SetAge(18)
	p.SetSal(5000)
	fmt.Println(p)   // xiaoming 0 0

	fmt.Println(p.Name, "age=", p.GetAge(), "sal=", p.GetSal())
}

接口interface

简介
interfacemethod

Go语言提倡面向接口编程。每个接口由数个方法组成,接口的定义格式如下:

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    …
}

其中:

typeerWriterStringer

举个例子:

type writer interface{
    Write([]byte) error
}
package main
import "fmt"

// 定义接口
// 在接口中定义方法
type Usber interface{
  Start()
  Stop()
}

// 定义两个结构体
type Phone struct{}

type Camera struct{}

// 给 phone 实现接口中全部的方法
func (p Phone) Start(){
  fmt.Println("手机开始工作")
}
func (p Phone) Stop(){
  fmt.Println("手机停止工作")
}

// 给 camera 实现接口中全部的方法
func (c Camera) Start(){
  fmt.Println("相机开始工作")
}
func (c Camera) Stop(){
  fmt.Println("相机停止工作")
}

// computer 结构体
type Computer struct{}

// 只要实现了接口,就实现了该接口中声明的全部方法
func (c Computer) Working(usb Usber){
  // 通过接口变量 usb 来调用接口 Usber 中的方法
  usb.Start()
  usb.Stop()
}

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

  // usb随着传入的变量不同,执行不同的工作,体现多态
  computer.Working(phone)
  computer.Working(camera)
}
注意事项和细节
interface
package main
import "fmt"

type Writer interface{
  Say()
}

type Stu struct{
  Name string
}

func (stu Stu) Say(){
  fmt.Println("Stu Say()")
}

type integer int

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

type T interface {}

func main(){
  var stu Stu   // 结构体实现了接口的方法
  var a Writer = stu
  a.Say()

  var i integer = 10
  var b Writer = i
  b.Say()  // integer Say i = 10

  // 空接口使用
  var t T = stu  // 空接口
  fmt.Println(t)
  var t2 inteface{} = stu   // 空接口
  var num1 float64 = 8.8
  t2 = num1
  t = num1
  fmt.Println(t2, t)
}
package main
import "fmt"

type Usb interface{
  Say()
}

type Stu struct{}

func (this *Stu) Say(){   // 用指针实现Say()方法
  fmt.Println("Say()")
}

func main(){
  var stu Stu = Stu{}
  // Stu 类型没有实现 Usb 接口
  var u Usb = &stu   // 带上&
  u.Say()
  fmt.Println(u)
}
结构体切片排序
package main
import (
  "fmt"
  "sort"
  "math/rand"
)

// 1. 声明 Hero 结构体
type Hero struct{
  Name string
  Age int
}

// 2. 声明一个Hero结构体切片类型
type HeroSlice []Hero

// 3. 实现 Interface 接口:文档中有三个方法都需要实现
func (hs HeroSlice) Len() int{
  return len(hs)
}

// 决定是使用哪种方法进行排序
func (hs HeroSlice) Less(i, j int) bool{
  return hs[i].Age < hs[j].Age
}

func (hs HeroSlice) Swap(i, j int){
  //temp := hs[i]
  //hs[i] = hs[j]
  //hs[j] = temp
  hs[i], hs[j] = hs[j], hs[i]
}

func  main(){
  var intSlice = []int{0,-1,9,4,2,10}
  // 冒泡排序
  // 内置的sort函数
  sort.Ints(intSlice)
  fmt.Println(intSlice)

  // 结构体切片进行排序
  var heros HeroSlice
  for i := 0;i < 10;i++{
    hero := Hero{
      Name: fmt.Sprintf("英雄~%d", rand.Intn(100)),
      Age: rand.Intn(100),  // 如何生成随机数
    }
    // 将hero append到heros 切片中
    heros = append(heros, hero)
  }
  // 排序前
  for _, v := range heros {
    fmt.Println(v)
  }
  // 排序后
  // 调用sort.Sort
  sort.Sort(heros)
}

接口和继承关系

当结构体需要扩展功能,同时不破坏继承关系,可以使用接口去实现。接口是对继承的一个补充

继承:解决代码的复用性和可维护性

接口:设计好各种规范,让其他自定义类型去实现这些方法;接口在一定程度上能实现代码解藕。

package main
import "fmt"

type Monkey struct{
  Name string
}

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

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

type FishAble interface{
  Swimming()
}

func LittleMonkey struct{
  Monkey  // 匿名结构体,继承
}

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

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

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

多态poly

变量具有多种形态,多态特征是通过接口来实现的

usb
package main
import "fmt"

// 定义接口
// 在接口中定义方法
type Usber interface{
  Start()
  Stop()
}

// 定义两个结构体
type Phone struct{}

type Camera struct{}

// 给 phone 实现接口中全部的方法
func (p Phone) Start(){
  fmt.Println("手机开始工作")
}
func (p Phone) Stop(){
  fmt.Println("手机停止工作")
}

// 给 camera 实现接口中全部的方法
func (c Camera) Start(){
  fmt.Println("相机开始工作")
}
func (c Camera) Stop(){
  fmt.Println("相机停止工作")
}

// computer 结构体
type Computer struct{}

// 只要实现了接口,就实现了该接口中声明的全部方法
func (c Computer) Working(usb Usber){
  // 通过接口变量 usb 来调用接口 Usber 中的方法
  usb.Start()
  usb.Stop()
}

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

  // usb随着传入的变量不同,执行不同的工作,体现多态
  computer.Working(phone)
  computer.Working(camera)

  // 多态数组
  var usbArr [3]Usb
  usbArr[0] = Phone{"vivo"}
  usbArr[1] = Phone{"小米"}
  usbArr[2] = Camera{"尼康"}

}