面向对象的三大特性
面向对象编程中的三大特性指的是:继承、多态和封装。多态是基于接口实现的。
- 继承
- 封装
- 接口
- 多态
当我们定义一个结构体的时候,实际上就是把一类事物的共有属性(字段)和行为(方法)提取出来,形成一个物理模型,这种研究问题的方法就是抽象。
银行存取款
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))
}
继承深入
- 结构体可以使用匿名结构体的所有字段,包含大写和小写都可以
- 匿名结构体字段访问可以简化
- 如果结构体和匿名结构体中含有相同字段,编译器采用的是就近访问原则;如果需要希望访问匿名结构体的字段和方法,可以通过匿名结构体名来进行区分
- 结构体中嵌入两个或者多个匿名结构体,如果两个结构体中有相同的字段或者方法(同时结构体本身没有同名的字段或者方法),在访问的时候,必须指明匿名结构体名字,否则编译报错。
- 如果结构体中嵌套了有名结构体,这种模式就是组合,此时访问结构体中的字段或者属性,必须带上结构体的名字。
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{"尼康"}
}