OOP思想
Go语言不是面向对象的语言,可以理解一些面向对象思想,只是通过一些方法模拟面向对象
物以类聚:随着事物的不断发展,需要学会归类。
语句多了:把一些功能性代码,或者一些完成相同类型作业的代码聚集到一起,函数就出现了;
变量多了:把相同类型,功能相近的变量放到一起,结构体就出现了;
方法多了、变量多了、结构体也多了,于是出现了类。
类:模板 -> 具体的对象
大型公司:
1个架构师CTO:拿到业务,分析需求(分类),架构软件(选型),写接口,面向对象解决问题
10个程序员:实现这些接口业务(CRUD) 面向过程解决问题
面向对象:可以解决很多复杂的问题,将现实中的问题拟人化(用人的思维去解决),适合多人协作;
面向过程:适合解决简单的问题,个人就可以完成。
面向对象思想核心:封装、继承、多态
继承
Go语言的继承
继承需要符合的关系是: is-a,父类更通用,子类更具体。
继承就是子类继承父类的特征和行为,使得子类具有父类的属性和方法,使得子类具有父类相同的行为。子类会具有父类的一般特性也会具有自身的特性。
Go语言的继承:结构体的嵌套
/*
Go语言中结构体嵌套
1、模拟继承 is - a
type A struct {
field
}
type B struct {
A // 匿名字段 B.A
field
}
// 这样B是可以直接访问A的属性的
2、聚合关系 has - a
type C struct {
field
}
type D struct {
c C // 聚合关系
field
}
// D是无法直接访问C中的属性的,必须使用D.c.c_field
*/
package main
import "fmt"
// 定义一个父类
type Person struct {
name string
age int
}
// 定义一个子类,Student拥有了父类所有的属性,还有自己的特性
type Student struct {
Person // 匿名字段 实现了继承
school string
}
func main() {
// 1、创建父类对象
p1 := Person{name: "张三", age: 18}
fmt.Println(p1) // {张三 18}
fmt.Println(p1.name, p1.age) // 张三 18
// 2、创建子类对象
s1 := Student{Person: Person{name: "小明", age: 16}, school: "清华"}
fmt.Println(s1) // {{小明 16} 清华}
fmt.Println(s1.Person.name, s1.Person.age) // 小明 16
// 3、创建子类对象
var s2 Student
s2.Person.name = "李四"
s2.Person.age = 20
s2.school = "北大"
fmt.Println(s2) // {{李四 20} 北大}
// 概念:提升字段,只有匿名字段才可以做到
// Person在Student中是一个匿名字段,Person中的属性name age 就是提升字段
// 所有的提升字段可以由子类对象直接访问
var s3 Student
s3.name = "王五"
s3.age = 35
s3.school = "川大"
fmt.Println(s3) // {{王五 35} 川大}
fmt.Println(s3.name, s3.age) // 王五 35
}
匿名字段 + 提升字段:Go语言中的匿名字段
方法
方法不是函数,Go语言中同时拥有函数和方法,一定要将其他只有方法的语言区分开来。
方法:是某个类的动作,需要指定调用者,约定这个方法属于谁的。对象.方法()
函数:是一个特殊的类型,不需要指定调用者,定义了函数就可以直接使用函数名()调用
package main
import "fmt"
// 方法可以理解为函数多了一个调用者
// 方法可以重名,不同的调用者,调用的结果是不同的
type Dog struct {
name string
age int
}
type Cat struct {
name string
age int
}
// 方法定义 func 方法调用者 方法名()
// 1、方法可以重名,只需要调用者不同
// 2、如果调用者相同,则不能重名
func (dog Dog) eat() {
fmt.Println("Dog eating...")
}
func (dog Dog) sleep() {
fmt.Println("Dog sleeping...")
}
func (cat Cat) eat() {
fmt.Println("Cat eating...")
}
func (cat Cat) sleep() {
fmt.Println("Cat sleeping")
}
func main() {
// 创建一个对象
dog := Dog{
name: "旺财",
age: 5,
}
// 方法的调用,通过对应的结构体对象来调用
dog.eat() // Dog eating...
dog.sleep() // Dog sleeping...
cat := Cat{
name: "喵喵",
age: 1,
}
cat.eat() // Cat eating...
cat.sleep() // Cat sleeping
}
方法的重写
需要结合继承来使用,子类可以访问父类属性,重写父类的方法,还可以增加自己的方法
package main
import "fmt"
// 方法重写 建立在父类和子类结构上的
type Animal struct {
name string
age int
}
func (animal Animal) eat() {
fmt.Println(animal.name, "正在吃...")
}
func (animal Animal) sleep() {
fmt.Println(animal.name, "正在睡...")
}
// 子类
type Dog struct {
Animal
color string // 子类可以定义自己的属性
}
func (dog Dog) shout() {
fmt.Println(dog.name, "汪汪汪...")
}
// 子类
type Cat struct {
Animal
color string // 子类可以定义自己的属性
}
// 子类重写父类的方法
func (cat Cat) eat() {
fmt.Println(cat.name, "猫正在吃...")
}
func main() {
// 定义一个子类
dog := Dog{
Animal{name: "旺财", age: 3},
"灰色",
}
// 调用父类的方法
dog.eat() // 旺财 正在吃...
// 调用子类自己扩展的方法
dog.shout() // 旺财 汪汪汪...
// 定义一个子类
cat := Cat{
Animal{name: "喵喵", age: 1},
"白色",
}
// 如果子类重写了父类的方法就调用子类重写的方法
cat.eat() // 喵喵 猫正在吃...
// 调用子类自己的属性
fmt.Println(cat.color)
// 子类可以调用父类的方法,而父类不可以调用子类自己扩展的方法
a := Animal{"大黄", 3}
a.eat() // 大黄 正在吃...
a.sleep() // 大黄 正在睡...
}
多态
接口
接口:USB、typeC、插座
Go语言提供了数据接口类型;
接口就是把一些共性的方法集合在一起定义;
Go语言中子类隐式实现接口,不需要像Java一样显示声明A implements B;
如果A实现了B接口中所有的定义方法,就代表A实现了B接口;
接口时方法的定义集合,所以接口中不需要实现具体的方法内容。
package main
import "fmt"
// 接口的定义 interface 因为方法多了,要归类,形成方法集合
type USB interface {
input()
output()
}
// 因为属性多了,需要结构体
type Mouse struct {
name string
}
// 结构体必须实现接口中全部方法才算实现了这个接口,否则不算
func (mouse Mouse) input() {
fmt.Println(mouse.name, "鼠标输入")
}
func (mouse Mouse) output() {
fmt.Println(mouse.name, "鼠标输出")
}
// 接口测试
func test(usb USB) {
usb.input()
usb.output()
}
func main() {
m1 := Mouse{name: "罗技"}
// 通过传入接口实现类来调用
test(m1)
k1 := KeyBoard{name: "雷蛇"}
test(k1)
// 定义高级类型
var usb USB
// k1向上转型 KeyBoard --> USB
usb = k1
fmt.Println(usb) // {雷蛇}
// 接口是无法使用实现类的属性的
// fmt.Println(usb.name) type USB has no field or method name
}
type KeyBoard struct {
name string
}
func (keyBoard KeyBoard) input() {
fmt.Println(keyBoard.name, "键盘输入")
}
func (keyBoard KeyBoard) output() {
fmt.Println(keyBoard.name, "键盘输出")
}
模拟多态
多态:一个事物拥有多种形态
package main
import "fmt"
// 定义接口
type Animal interface {
eat()
sleep()
}
type Dog struct {
name string
}
func (dog Dog) eat() {
fmt.Println(dog.name, "正在吃...")
}
func (dog Dog) sleep() {
fmt.Println(dog.name, "正在睡...")
}
func test(animal Animal) {
animal.eat() // 旺财 正在吃...
animal.sleep() // 旺财 正在睡...
}
func main() {
// 多态 Dog有两重身份:1.Dog 2.Animal
d1 := Dog{name: "旺财"}
d1.eat() // 旺财 正在吃...
d1.sleep() // 旺财 正在睡...
// Dog也可以是Animal
test(d1)
// 可以定义一个接口类型的变量
var animal Animal
// 所有实现类对象都可以赋给接口对象 模糊 --> 具体化
// 这样才有意义
animal = d1
fmt.Printf("%T\n", animal)
test(animal)
}
接口的实现类都拥有多态特性
空接口
不包含任何的方法。因此,所有结构体都默认实现了这个接口,表明空接口可以存储任何类型
Go语言中 interface{} == any 相当于 Java中的Object
package main
import "fmt"
// 定义空接口
type P interface {
}
type Teacher struct {
name string
}
type Student struct {
name string
}
func test(p P) {
fmt.Println(p)
}
// 可以定义空接口的参数
// any is an alias for interface{} and is equivalent to interface{} in all ways.
// type any = interface{}
func test2(any interface{}) {
}
func main() {
var t = Teacher{name: "小明"}
var s = Student{name: "小李"}
var a int = 1
var str string = "GoGoGo"
test(t) // {小明}
test(s) // {小李}
test(a) // 1
test(str) // GoGoGo
// 空接口的使用
// map
map1 := make(map[string]interface{})
map1["name"] = "小张"
map1["age"] = 18
fmt.Println(map1) // map[age:18 name:小张]
// slice
s1 := make([]any, 0, 10)
s1 = append(s1, 1, "小李", false, t, s)
fmt.Println(s1) // [1 小李 false {小明} {小李}]
var arr [5]interface{}
fmt.Println(arr) // [<nil> <nil> <nil> <nil> <nil>]
}
接口嵌套
package main
import "fmt"
type AA interface {
test1()
}
type BB interface {
test2()
}
// 接口嵌套
// 如果要实现CC这个接口,那么需要实现三个方法,这个对象就有三个接口可以转型
type CC interface {
AA
BB
test3()
}
type SS struct {
}
func (ss SS) test1() {
fmt.Println("test1")
}
func (ss SS) test2() {
fmt.Println("test2")
}
func (ss SS) test3() {
fmt.Println("test3")
}
func main() {
// ss拥有四种形态:SS、AA、BB、CC
var ss SS
ss.test1() // test1
ss.test2() // test2
ss.test3() // test3
// 向上转型之后只能调用自己对应的方法
var a AA = ss
a.test1()
var b BB = ss
b.test2()
var c CC = ss
c.test1()
c.test2()
c.test3()
}
接口断言
检查一个接口类型的变量是否符合预期值。
比如:如果预期调用test3(),传递的是a、b对象,就需要判断这个对象是否是预期对象。
被断言的的对象必须是接口类型,否则会报错。
如果一个对象存在多种形态:接口判断
i.(T) 判断是否是预期接口
switch i.(type) 判断是否是预期的case结果
package main
import "fmt"
// 断言 t := i.(T) t: 就是i接口T类型的
// 语法 t, ok := i.(T) ok 隐藏返回值,如果断言成功,ok就是true,否则就是false
func main() {
assertsString("GoGoGo") // GoGoGo
// 如果断言失败,就会报panic(恐慌)错误,程序就会停止执行
// assertsString(123) // panic: interface conversion: interface {} is int, not string
assertsInt(123) // 是期望的结果: 123
assertsInt("中国") // 不是期望的结果,无法执行预期操作
}
func assertsString(i interface{}) {
s := i.(string)
fmt.Println(s)
}
func assertsInt(i any) {
r, ok := i.(int)
if ok {
fmt.Println("是期望的结果:", r)
} else {
fmt.Println("不是期望的结果,无法执行预期操作")
}
}
package main
import "fmt"
type I interface {
}
func testAsserts(i any) {
// switch i.(type) 接口断言
switch i.(type) {
case string:
fmt.Println("变量为string类型")
case int:
fmt.Println("变量为int类型")
case nil:
fmt.Println("变量为nil类型")
case interface{}:
fmt.Println("变量为interface{}类型")
case I:
fmt.Println("变量为I类型")
default:
fmt.Println("未知类型")
}
}
func main() {
testAsserts("中国") // 变量为string类型
testAsserts(1) // 变量为int类型
var i I // 如果没有赋值,默认值为nil
testAsserts(i) // 变量为nil类型
var i2 I = 2
testAsserts(i2) // 变量为int类型
}
type 别名
type关键字的理解:
type 定义一个类型
type User struct 定义结构体类型
type User interface 定义接口类型
type Diy (int、string、...) 自定义类型,全新的类型
type 起别名
type xxx = 类型 将某个类型赋值给 xxx,相当于这个类型的别名
别名只能在写代码的时候使用,增加代码的可阅读性
别名在真实的编译过程中,还是原来的类型
package main
import "fmt"
// var 变量 type 类型(结构体、接口、别名...)
// type的别名用法,全局变量中
// 这是定义了一个新类型 MyInt,是int转换过来的,和int一样,但是不能和int发生操作,因为类型不同
type MyInt int
func main() {
var a MyInt = 10
var b int = 20
// 如下代码报错:invalid operation: a + b (mismatched types int and MyInt)
//fmt.Println(a + b)
// 类型转换:type(value)
fmt.Println(int(a) + b) // 30
fmt.Printf("%T\n", a) // main.MyInt
fmt.Printf("%T\n", b) // int
// 给int起一个小名,但是他还是int type any
type diyint = int // 和int一样
var c diyint = 30
fmt.Println(b + c) // 50
fmt.Printf("%T\n", c) // int
}
错误和异常
错误:指的是程序中预期会发生的结果,预料之中;(例如,打开一个文件,发现文件被占用,可知的)
异常:不该出现问题的地方出现了问题,预料之外。(例如,调用一个对象,发现对象空指针,未知的)
错误是业务的一部分,而异常不是。
错误
错误在开发中是必须要考虑的问题。
某些系统错误,文件被占用,网络延迟等;
人为错误,用户在输入框会传递一些不正常的参数,sql注入
在实际的项目开发中,希望通过错误信息来快速定位问题,但不又不能让处理错误的代码些的冗余而啰嗦。
在Go语言中没有提供像Java、C#语言中的try...catch异常处理方式,而是通过函数返回值逐层往上抛。在代码中应该显式的检查错误,而非忽略错误。好处就是避免漏掉本应处理的错误,同时带来的弊端就是让代码变得繁琐。
Go语言中的错误也是一种类型,类似于int,float64等,使用error表示。因此错误值可以存储在变量中,通过函数返回、传递参数等。
package main
import (
"fmt"
"os"
)
func main() {
// os 系统包,打开一个文件
// func Open(name string) (*File, error)
file, err := os.Open("a.txt")
// 在开发中,需要考虑错误的类型 PathError
// 1、文件不存在
// 2、文件被占用
// 3、文件易损耗
// 可能出现的错误,需要解决
if err != nil {
fmt.Println(err) // open a.txt: The system cannot find the file specified.
return
}
fmt.Println(file.Name())
}
错误的定义:
errors.New("xxx")
fmt.Errorf()
package main
import (
"errors"
"fmt"
)
// 设置年龄的函数,需要处理用户一些不合法的请求
// 返回值为error类型
func setAge(age int) error {
if age < 0 {
// 方式一
return errors.New("输入的年龄不合法!")
}
// 返回程序正常的结果
fmt.Println("年龄设置成功,age=", age)
return nil
}
func main() {
age_err := setAge(-1)
if age_err != nil {
fmt.Println(age_err)
}
// 错误是一个类型
fmt.Printf("%T\n", age_err) // *errors.errorString
// 方式二
errInfo := fmt.Errorf("我是一个错误信息:%d\n", 500)
if errInfo != nil {
fmt.Println(errInfo) // 我是一个错误信息:500
}
}
错误的类型
error是一个类型,一个接口,里面只有一个方法 Error()
type error interface {
Error() string
}
package main
import (
"fmt"
"net"
)
func main() {
addrs, err := net.LookupHost("www.baidu123456.com")
if err != nil {
fmt.Println(err)
}
// 如果要知道错误的具体类型,可以通过断言操作
dnsErr, ok := err.(*net.DNSError)
if ok {
if dnsErr.Timeout() {
fmt.Println("超时错误...")
} else if dnsErr.Temporary() {
fmt.Println("临时错误...")
} else {
fmt.Println("其它错误...")
}
}
fmt.Println(addrs)
}
自定义错误
在项目开发中,更多的是使用自定义错误。
package main
import "fmt"
type ZgyError struct {
msg string
code int
}
func (e *ZgyError) Error() string {
// fmt.Sprintf 返回string
return fmt.Sprintf("错误信息:%s, 错误代码:%d\n", e.msg, e.code)
}
func (e ZgyError) print() bool {
fmt.Println("print hello, world")
return true
}
// 测试
func test(i int) (int, error) {
if i != 0 {
return i, &ZgyError{msg: "非预期数据", code: 500}
}
// 正常返回
return i, nil
}
func main() {
i, err := test(1)
// 处理错误的代码
if err != nil {
fmt.Println(err)
zgy_err, ok := err.(*ZgyError)
if ok {
if zgy_err.print() {
// 处理错误
}
fmt.Println(zgy_err.msg)
fmt.Println(zgy_err.code)
}
}
fmt.Println(i)
}
异常panic
恐慌painc,程序在运行的时候会发生panic。在一些预感到有异常的情况下,可以手动抛出panic。如果有panic发生,尽可能接受它,并处理。
package main
// panic
func main() {
panic("程序 panic")
}
Java语言中使用try..catch机制处理异常,Go崇尚简洁,使用recover函数接收并处理panic函数抛出的恐慌异常信息。
package main
import "fmt"
// panic
// defer:延迟函数,函数倒序执行
func main() {
defer fmt.Println("main--1")
defer fmt.Println("main--2")
fmt.Println("main--3")
// 如果在函数内部使用recover接收处理了panic,那么此时程序会向下执行,否则不会继续执行
testPanic(1)
defer fmt.Println("main--4")
fmt.Println("main--5")
}
func testPanic(num int) {
// func recover() any
// 接收恐慌抛出来的信息,并处理
defer func() {
if msg := recover(); msg != nil {
fmt.Println("执行recover... panic msg: ", msg)
// 处理逻辑
fmt.Println("------程序已恢复-----")
}
}()
defer fmt.Println("testPanic--1")
defer fmt.Println("testPanic--2")
fmt.Println("testPanic--3")
// 如果在函数中一旦触发了panic,会终止panic后面要执行的代码
// 如果panic前存放defer函数,正常按照逻辑倒序执行
if num == 1 {
panic("出现预定的异常--panic")
}
defer fmt.Println("testPanic--4")
fmt.Println("testPanic--5")
}