面向对象的三大特性:封装/继承/多态
- 定义一个结构体Account有账号,密码,余额三个字段
- 实现查询,取款,存款三个功能
package main
import "fmt"
type Account struct{
username string
pwd string
balance float64
}
// 查询功能方法
func (query *Account)query(uname *string,pw *string){
if *pw == query.pwd && *uname == query.username{
fmt.Println("查询成功!余额为: ",query.balance)
fmt.Println("ctrl+c停止服务")
}else{
fmt.Println("账号或密码错误请重新选择服务!ctrl+c停止服务")
}
}
// 存款功能
func (deposit *Account)deposit(uname *string,pw *string,bce *float64){
if *pw == deposit.pwd && *uname == deposit.username{
deposit.balance += *bce
fmt.Println("存款完成余额为: ",deposit.balance)
}else{
fmt.Println("账号或密码错误请重新选择服务!ctrl+c停止服务")
}
}
// 取款功能
func (drawmoney *Account)drawmoney(uname *string,pw *string,bce *float64){
if *pw == drawmoney.pwd && *uname == drawmoney.username{
drawmoney.balance -= *bce
fmt.Println("取款完成,余额为: ",drawmoney.balance)
}else{
fmt.Println("账号或密码错误请重新选择服务!ctrl+c停止服务")
}
}
func main(){
var unames,pwds,querys string
var bace float64
var money = Account{
username : "admin",
pwd : "123456",
balance : 100,
}
for i:=0;i<10;i++{
fmt.Println("请选择需要的服务: 查询余额(query)/存款(deposit)/取款(drawomney)")
fmt.Scanln(&querys)
if querys == "查询余额" || querys == "query"{
fmt.Println("请输入账号:")
fmt.Scanln(&unames)
fmt.Println("请输入密码:")
fmt.Scanln(&pwds)
money.query(&unames,&pwds)
}else if querys == "存款" || querys == "deposit"{
fmt.Println("请输入账号:")
fmt.Scanln(&unames)
fmt.Println("请输入密码:")
fmt.Scanln(&pwds)
fmt.Println("请输入存款金额: ")
fmt.Scanln(&bace)
money.deposit(&unames,&pwds,&bace)
}else if querys == "取款" || querys == "drawomney"{
fmt.Println("请输入账号:")
fmt.Scanln(&unames)
fmt.Println("请输入密码:")
fmt.Scanln(&pwds)
fmt.Println("请输入取款金额: ")
fmt.Scanln(&bace)
money.drawmoney(&unames,&pwds,&bace)
}
}
}
封装的好处:
- 隐藏实现细节
- 可以对数据进行验证保证安全合理(Age)
type contens struct{
Age int
}
如何体现封装:
- 对结构体中的属性进行封装
- 通过 方法/包 实现封装
练习题:
- Account结构体:账号(6-10),余额必须>20,密码必须六位
- 通过set进行赋值
package main
import "fmt"
type Account struct{
username string
balance float64
password string
}
// 定义一个工厂模式函数
func person(u string,b float64,p string)*Account{
if len(u) < 6 || len(u)>10{
fmt.Println("长度请保持6-10")
return nil
}
if b < 20{
fmt.Println("余额不足请大于20")
return nil
}
if len(p) != 6{
fmt.Println("密码必须六位")
return nil
}
return &Account{
username : u,
balance : b,
password : p,
}
}
// 定义set方法赋值
func (setpon *Account)Setperson(username string,balance float64,password string){
if len(username) >= 6 && len(username) <=10{
setpon.username = username
}else{
fmt.Println("账号长度请输入6-10之间")
}
if balance > 20{
setpon.balance = balance
}else{
fmt.Println("余额必须大于20")
}
if len(password) == 6{
setpon.password = password
}else{
fmt.Println("密码长度不符合规定!必须为六位")
}
}
// 定义get方法进行查询
func (getpon *Account)Getperson(){
fmt.Println("账号为: ",getpon.username)
fmt.Println("余额为: ",getpon.balance)
}
func main(){
// 实例化工厂函数
p := person("admins",500,"123456")
p.Setperson("1242344",1800,"12341r")
p.Getperson()
fmt.Println(*p)
}
面向对象编程—继承
- 演示继承
package main
import "fmt"
type person struct{
name string
age int
}
type inherit struct{
person
}
func main(){
p := person{"麦哲伦",20}
fmt.Println(p)
d := inherit{person{"新加坡",70}}
fmt.Println(d)
d.person.name = "菲律宾"
fmt.Println(d)
}
- 继承指针类型的结构体
- 匿名基本数据类型也可以作为字段
- 多重继承
继承访问总结:
- 继承结构体可以访问被继承结构体所有的字段及方法
- 继承不同结构体并且结构体与被继承结构体之间无相同字段,可以进行简写
- 如果继承结构体和被继承结构体之间有相同字段,不进行简写则就近原则只修改最近的字段
- 访问有名结构体必须带上有名结构体名字进行访问
- 实例化结构体可以直接指定被继承结构体字段值
- 继承指针类型的结构体传值取值都遵循指针传递取值
- 匿名基本数据类型也可以作为字段,一个struct只能有一个名字相同匿名基本数据类型
- 一个strcut中有多个被继承struct就实现了多重继承
interface接口
课前思考:
- golang中可以基于struct创建方法,然后实例化strcut,通过这个实例化对象使用方法,实现对应功能是不是类似于接口功能
- golang还有一个工厂模式函数,作用是为了给private类型struct给别的包文件使用,工厂模式函数原理是定义一个public函数返回struct各个字段,可以向匿名函数传参数,再把传入的参数赋值给struct各个对应字段,最后把赋值后的struct返回给调用这个工厂模式函数者,从而实现了其他包访问private类型struct,这就是golang的封装
快速入门:
// 演示接口interface
package main
import "fmt"
// 定义一个接口,里边有两个方法open(),save()
type usb interface{
open()
save()
}
// 定义一个空结构体
type phone struct{
}
// 创建一个方法绑定上边的结构体实现接口方法
func (p phone)open(){
fmt.Println("打开手机...")
}
func (p phone)save(){
fmt.Println("关闭手机...")
}
// 创建一个结构体并实现一个方法
type pgsql struct{
}
// 创建一个方法绑定上struct,并传入usb类型的interface,并调用接口方法
func (pg pgsql)element(u usb){
u.open()
u.save()
}
func main(){
// 实例化struct以便进行调用
var phone phone
var pgsql pgsql
pgsql.element(phone)
}
注意事项和细节:
- 接口本身不能实例化但是可以指向实现了该接口的自定义类型的变量(实例)
- 接口中所有的方法都没有方法体,即没有实现的方法
- 在golang中一个自定义数据类型需要将接口方法全部实现我们称实现了这个接口
- 一个自定义数据类型只有全部实现了这个接口,才能将该自定义数据类型实例(变量)赋值给接口类型
- 只要是自定义数据类型都可以实现接口,不仅仅是struct类型
- 一个自定义数据类型可以实现多个接口
- golang接口中不能有任何变量
- 一个接口可以继承多个别的接口,此时如果要实现这个接口则需要将继承来的接口方法全部实现
// 接口可以继承多个别的接口,如果需要实现这个接口则需要将继承来的接口方法
// 全部实现
package main
import "fmt"
type person interface{
eat()
}
type person2 interface{
drink()
}
type person3 interface{
person
person2
}
type contents struct{
}
func (con contents)eat(){
fmt.Println("吃饭")
}
func (con contents)drink(){
fmt.Println("喝酒")
}
type ppd struct{
}
func (pd ppd)like(p3 person3){
p3.eat()
p3.drink()
}
func main(){
var contents contents
var ppd ppd
ppd.like(contents)
}
- interface类型默认是一个指针(引用类型),如果没有对interface进行初始化就使用,那么会输出nil
- 空接口interface{}没有任何方法,所以所有类型都实现了空接口
// 空接口没有任何方法,所有类型都实现了空接口
package main
import "fmt"
type person interface{
}
func main(){
var a int
var p person = a
fmt.Println(p)
}
- 接口继承如果有相同名字的方法则编译不通过
练习题:
接口编程:
sort包下有一个Sort函数里边有一个接口 Interface 实现这个接口三个方法后,调用sort.Sort函数传入实现这个接口的对应参数就可以实现升序/降序排序
type Interface
type Interface interface {
// Len方法返回集合中的元素个数
Len() int
// Less方法报告索引i的元素是否比索引j的元素小
Less(i, j int) bool
// Swap方法交换索引i和j的两个元素
Swap(i, j int)
}
一个满足sort.Interface接口的(集合)类型可以被本包的函数进行排序。方法要求集合中的元素可以被整数索引。
接口实现应用代码:
// 最后写一遍
package main
import (
"fmt"
"math/rand"
"sort"
)
// 定义一个结构体
type person struct{
name string
age int
}
// 根据这个结构体定义一个 person类型的自定义数据类型用来实现方法
type pson []person
// 实现方法
func (p pson)Len() int{
return len(p)
}
func (p pson) Less(i, j int) bool{
return p[i].age < p[j].age
}
func (p pson) Swap(i, j int){
p[i],p[j] = p[j],p[i]
}
func main(){
// 实例化自定义数据类型
var p pson
// 循环生成十个person类型的数据追加到p中
for i:=0;i<10;i++{
per := person{
name : fmt.Sprintf("剑圣 %d",rand.Intn(100)),
age : rand.Intn(100),
}
p = append(p,per)
}
/** 调用sort.Sort()函数,并把p传入,由于p的数据类型pson实现了sort
方法,所以将会调用成功进行排序
*/
sort.Sort(p)
// 调用完毕排序完毕后遍历这个slice类型的struct
for k,v := range p{
fmt.Println(k,v)
}
}
接口和继承的关系:
- 接口就是对继承的补充
- 接口可以在不破坏继承关系情况下实现功能扩展,可扩解耦
继承总结:
- 继承基于struct
- 继承主要解决了代码复用,提高了可维护性
- 一个struct继承了另一个struct就可以使用这个struct的所有字段及方法
- 继承多个struct如果有相同的字段方法访问则需指定struct名否则会报错
- 继承strcut和被继承struct如果有相同字段秉承就近原则,如果要访问被继承struct的字段语法为:
被继承struct . 字段
进行访问使用
接口总结:
- 接口中只定义方法,不应出现任何变量
- 只有实现了接口中所有方法称之为实现了这个接口
- 任何自定义类型都可以实现接口
- 接口相较于继承更灵活,可扩展性高,解耦
- 接口可以理解为对继承的完善扩展
面向对象-多态
课前思考:
多态顾名思义多种形态,基本数据类型,值类型,引用类型,函数,方法,interface,struct多种形态展示,继续学习
课中思考:
- 多态是通过接口实现的,也就是多态基于接口
- 接口实现规则是实现接口中所有方法即可实现该接口,不同的struct,不同的方法都可以实现同一个interface就叫做多态,可达鸭变身超级模式~~~
类型断言:
对上边文章说明:
b := i.(string) 判断i是否为string类型,是就赋值给b,不是就报错
将会报以下错:
string,hellopanic: interface conversion: interface {} is string, not int
带判断的断言:
类型断言最佳实践:
// 类型断言最佳实践
package main
import "fmt"
// 定义一个接口
type monkey interface{
car()
}
// 定义一个结构体
type person struct{
}
// 实现接口
func (per person)car(){
fmt.Println("猴子开火车")
}
func (per person)cat(){
fmt.Println("猴子开飞机")
}
type date struct{
}
func (da date)memo(m monkey){
m.car()
c,tmp := m.(person)
if tmp == true{
fmt.Println("断言成功")
}else{
fmt.Println("断言失败")
}
c.cat()
}
func main(){
var p person
var d date
d.memo(p)
}
对上面代码总结:
phone接口中只有scool()一个方法,person结构体中有两个方法,其中一个方法实现了接口scool,重新定义一个结构体方法,参数为接口类型,进行调用则只能调用接口中的方法,如果想要调用person结构体中接口外的方法则可以使用类型断言,判断参数p类型是否为person结构体类型,是就把p转换为person结构体类型,此时就可以调用person结构体中的所有方法
类型断言最佳实践二:
对上面代码总结:
- 类型断言不仅可以判断基本数据类型,也可以判断自定义数据类型,指针类型等