前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang之旅37-继承、封装和多态

Golang之旅37-继承、封装和多态

作者头像
皮大大
发布2021-03-02 16:03:38
6520
发布2021-03-02 16:03:38
举报
文章被收录于专栏:机器学习/数据可视化

面向对象的三大特性

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

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

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

银行存取款
代码语言:javascript
复制
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
}
继承
继承入门

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

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

代码语言:javascript
复制
type Goods struct{
  Name string
  Price float64
}

type Book struct {
  Goods    // 嵌套上面的匿名结构体
  Writer string
}
代码语言:javascript
复制
// 关于学生的继承栗子

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. 如果结构体中嵌套了有名结构体,这种模式就是组合,此时访问结构体中的字段或者属性,必须带上结构体的名字。
代码语言:javascript
复制
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

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

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

代码语言:javascript
复制
// 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
}
代码语言:javascript
复制
// 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
简介

接口是一种抽象的类型,基于方法实现的。interface是一组method的集合。接口做的事情就像是定义了一个协议(规则)。高内聚低耦合特性。接口是引用类型

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

代码语言:javascript
复制
type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    …
}

其中:

  • 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
  • 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。

举个例子:

代码语言:javascript
复制
type writer interface{
    Write([]byte) error
}
代码语言:javascript
复制
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)
}
注意事项和细节
  1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
  2. 一个自定义类型需要实现接口中全部的方法
  3. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
  4. 一个自定义类型可以实现多个接口
  5. 接口之间可以存在继承关系
  6. interface默认是一个指针(引用类型)
  7. 空接口没有任何方法,所有类型都实现了空接口,可以把任何一个变量赋值给空接口。
代码语言:javascript
复制
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)
}
代码语言:javascript
复制
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)
}
结构体切片排序
代码语言:javascript
复制
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)
}
接口和继承关系

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

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

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

代码语言:javascript
复制
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就是多态参数
  • 多态数组:
代码语言:javascript
复制
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{"尼康"}

}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-11-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 面向对象的三大特性
    • 银行存取款
      • 继承
        • 继承入门
        • 继承深入
      • 封装encapsulation
        • 接口interface
          • 简介
          • 注意事项和细节
          • 结构体切片排序
        • 接口和继承关系
          • 多态poly
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档