golang 面向对象的三大特性

golang有面向对象是三大特征:继承,封装和多态。

1.抽象

把一类事务的共有属性(字段)和行为(方法)提取出来,形成一个物理模型(结构体),这种研究问题的方法称为抽象。

示例:银行存取款

package main

import "fmt"

type Account struct {
    AccountNo string
    Pwd       string
    Balance   float64
}

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

//1.取款
func (account *Account) WithDraw(money float64, pwd string) {
    //看输入的密码是否正确
    if pwd != account.Pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
    //看看取款金额是否正确
    if money <= 0 || money > account.Balance {
        fmt.Println("你输入的存款金额不正确")
        return
    }
    account.Balance -= money
    fmt.Println("取款成功")
}

//1.查询余额
func (account *Account) Query(pwd string) {
    //看输入的密码是否正确
    if pwd != account.Pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
    fmt.Printf("你的账户为=%v,余额为=%v\n", account.AccountNo, account.Balance)
}
func main() {
    account := Account{
        "gs1111",
        "6666",
        100.0,
    }
    account.Query("6666")
    account.Deposite(20, "6666")
    account.Query("6666")
    account.WithDraw(10, "6666")
    account.Query("6666")

}

用户端输入示例

package main

import (
    "fmt"
)

type Account struct {
    AccountNo string
    Pwd       string
    Balance   float64
}

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

//1.取款
func (account *Account) WithDraw(money float64, pwd string) {
    //看输入的密码是否正确
    if pwd != account.Pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
    //看看取款金额是否正确
    if money <= 0 || money > account.Balance {
        fmt.Println("你输入的存款金额不正确")
        return
    }
    account.Balance -= money
    fmt.Println("取款成功")
}

//1.查询余额
func (account *Account) Query(pwd string) {
    //看输入的密码是否正确
    if pwd != account.Pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
    fmt.Printf("你的账户为=%v,余额为=%v\n", account.AccountNo, account.Balance)
}
func (account *Account) Operator(s2 string, pwd string, money float64) {
    switch s2 {
    case "存款":
        account.Deposite(money, pwd)
    case "取款":
        account.WithDraw(money, pwd)
    case "查询":
        account.Query(pwd)
    }
}
func main() {
    account := Account{
        "gs1111",
        "6666",
        100.0,
    }
    s2 := ""
    money := 0.0
    fmt.Println("请输入查询操作类型:")
    fmt.Scanln(&s2)
    fmt.Println("请输入银行密码:")
    fmt.Scanln(&account.Pwd)
    fmt.Println("请输入操作金额:")
    fmt.Scanln(&money)
    account.Operator(s2, account.Pwd, money)
    fmt.Println("请输入查询操作类型:")
    fmt.Scanln(&s2)
    account.Operator(s2, account.Pwd, money)
}

 

2.封装

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

封装的好处:

1)隐藏实现细节

2)可以对数据进行验证,保证安全合理

如何实现封装:

1)对结构体中的属性进行封装

2)通过方法,包 进行封装

封装的实现步骤

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

2)给结构体所在的包提供一个工厂模式的函数,首字母大写。类似一个构造函数

3)提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值

func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {

  //加入数据验证的业务逻辑

  var.字段 = 参数

}

4)提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值

func (var 结构体类型名) GetXxx() {

  return var.字段

}

示例1:

编写一个程序person.go,不能随便查看人的年龄,工资等隐私,并对输入的年龄薪水进行合理的验证

person.go

package person

import "fmt"

type person struct {
    Name string
    age  int
    sal  float64
}

//只有Name是公有的
func NewPerson(name string) *person {
    return &person{
        Name: name,
    }
}
func (p *person) SetPersonAge(age int) {
    if age > 0 && age < 150 {
        p.age = age
    } else {
        fmt.Println("年纪范围不正确")
    }
}
func (p *person) GetPersonAge() int {

    return p.age
}
func (p *person) SetPersonSal(sal float64) {
    if sal >= 3000 && sal <= 30000 {
        p.sal = sal
    } else {
        fmt.Println("薪水范围不对")
    }
}
func (p *person) GetPersonSal() float64 {
    return p.sal
}

main.go

package main

import (
    "fmt"
    "gotest1/src/test/test47/person"
)

func main() {
    p := person.NewPerson("王1")
    p.SetPersonAge(15)
    p.SetPersonSal(16567.1)
    fmt.Printf("%v的年龄为%v,工资为%v\n", p.Name, p.GetPersonAge(), p.GetPersonSal())
}

示例2:

1)创建程序,在model包中定义account结构体,在main函数中体会golang的封装性

2)account结构体要求有字段:账号(长度在6-10之间)、余额(必须>20)、密码(必须是六位数)

3)通过SetXxx给account的字段赋值。

4)在main函数中测试

model.go

package model

import "fmt"

type account struct {
    accountNo string
    pwd       string
    balance   float64
}

//工厂模式-构造函数
func NewAccount() *account {
    return &account{}
}

//给no赋值
func (acn *account) SetNo(no string) {
    if len(no) <= 10 && len(no) >= 6 {
        acn.accountNo = no
    } else {
        fmt.Println("银行卡长度不正确")
    }
}

//返回no
func (acn *account) GetNo() string {
    return acn.accountNo
}

//给pwd赋值
func (acn *account) SetPwd(pwd string) {
    if len(pwd) == 6 {
        acn.pwd = pwd
    } else {
        fmt.Println("密码长度不正确")
    }
}

//返回pwd
func (acn *account) GetPwd() string {
    return acn.pwd
}

//给balance赋值
func (acn *account) SetBalance(balance float64) {
    if balance > 20 {
        acn.balance = balance
    } else {
        fmt.Println("余额不正确")
    }
}

//返回balance
func (acn *account) GetBalance() float64 {
    return acn.balance
}

main.go

package main

import (
    "fmt"
    "gotest1/src/test/test48/model"
)

func main() {
    account := model.NewAccount()
    account.SetNo("18fsfs")
    account.SetPwd("664346")
    account.SetBalance(25)
    fmt.Println(account.GetNo(), account.GetPwd(), account.GetBalance())
}

 3.继承

继续可以解决代码复用,让编程更靠近人类思维。

当多个结构体存在相同的属性(字段和方法时),可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。

其他的结构体不需要重新定义这些属性(字段)和方法,只需要嵌套一个student匿名结构体即可。

也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特效。

package main

import "fmt"

type Student struct {
    Name  string
    Age   int
    Score int
}
type Pupil struct {
    Student //继承Student结构体
    heignt  int
}

type Graduate struct {
    Student //继承Student结构体
}

//获取信息
func (stu *Student) GetInfo() {
    fmt.Printf("%v的年龄是%v,分数是%v\n", stu.Name, stu.Age, stu.Score)
}

//放置分数
func (stu *Student) SetScore(sorce int) {
    stu.Score = sorce
}

//小学生特殊方法,获得身高
func (pupil *Pupil) GetHeigint() {
    fmt.Printf("%v的身高是%v\n", pupil.Name, pupil.heignt)
}

//小学生特殊方法,考试
func (pupil *Pupil) testing() {
    fmt.Println("小学生正在考试")

}

//大学生特殊方法,考试
func (graduate *Graduate) testing() {
    fmt.Println("大学生正在考试")

}
func main() {
    pupil := Pupil{}
    pupil.Name = "tom"
    pupil.Age = 8
    pupil.heignt = 152
    pupil.testing()
    pupil.SetScore(74)
    pupil.GetInfo()
    pupil.GetHeigint()

    graduate := Graduate{}
    graduate.Name = "Lili"
    graduate.Age = 24
    graduate.testing()
    graduate.SetScore(98)
    graduate.GetInfo()

}

3.1继承使用的细节

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

2)匿名结构体字段访问可以简化,如b是B的实例,B继承A,A中有Name字段,则b.A.Name 的写法可以简化为b.Name

3)当结构体和匿名结构体有相同字段或者方法时,编译器采用就近访问原则,如希望访问匿名结构体的字段和方法,可以通过匿名结构体来区分。

4)结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则就报错。

5)如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合结构体的字段或方式时,必须带上结构体的名字。

B结构体中有有名结构体a A  所以不能直接用b.Name来调用,会报错,而应该带上有名结构体的名字 b.a.Name

type A struct {
    Name string
}
type B struct {
    a A
}

func main() {
    b := B{}
    b.a.Name = "tom"
    fmt.Println(b.a.Name)
}

6)嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。

package main

import "fmt"

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() {
    tv1 := TV{Goods{"电视机001", 1132.2}, Brand{"天虹", "四川"}}
    tv2 := TV{
        Goods{
            Name:  "电视机002",
            Price: 754.2,
        },
        Brand{
            Name:    "乐视",
            Address: "香港",
        },
    }
    fmt.Println(tv1)
    fmt.Println(tv2)
    tv3 := TV2{&Goods{"电视机003", 1132.2}, &Brand{"微软", "美国"}}
    tv4 := TV2{
        &Goods{
            Name:  "电视机004",
            Price: 754.2,
        },
        &Brand{
            Name:    "三星",
            Address: "韩国",
        },
    }
    fmt.Println(*tv3.Goods, *tv3.Brand)
    fmt.Println(*tv4.Goods, *tv4.Brand)
}

 7)多重继承

如上面的tv2就是多重继承,继承的Goods和Brand中都有Name字段。若嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。为了保持代码的简洁性,尽量不使用多重继承

posted @ 2023-03-02 17:20  潇潇暮鱼鱼  阅读(84)  评论(0)    收藏  举报