Go基础面向对象和反射机制

Posted Ricky_0528

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go基础面向对象和反射机制相关的知识,希望对你有一定的参考价值。

文章目录

一、面向对象

1. 面向对象的概念

洗衣服过程剖析:

  • 给洗衣机里加脏衣服和洗衣粉
  • 启动洗衣机
  • 洗衣机自动注水,然后滚动
  • 脏衣服从黑颜色变成白颜色
  • 洗衣机自动停止

用面向过程的思想实现代码

// 准备洗衣服
// 输入参数:
// 	powder 洗衣机里放多少洗衣粉
// 	closes 洗衣机里放多少衣服
// 	clean 衣服是否是干净的
// 返回值:
// 	洗衣机是否开启
// 	准备洗多少衣服
func prepare(powder int, closes int, clean bool) (bool, int) 
	if powder <= 0 || closes <= 0 || clean == true 
		return false, 0
	
	return true, closes


// 开始洗衣服
// 输入参数:
// 	washer_state 洗衣机是否开启
// 	closes 准备洗多少衣服
// 返回值:
// 	衣服是否是干净的
// 	洗了多少衣服
// 	洗衣机是否开启
func wash(washer_state bool, closes int) (bool, int, bool) 
	if washer_state == false 
		return false, 0, false
	 else 
		fmt.Println("注水")
		fmt.Println("滚动")
		fmt.Println("关机")
		return true, closes, false
	


// 检查最终状态
// 输入参数:
// 	clean 衣服是否是干净的
// 	closes 洗了多少衣服
// 	washer_state 洗衣机是否开启
func check(clean bool, closes int, washer_state bool) 
	if clean && closes > 0 
		fmt.Printf("洗干净了%d件衣服\\n", closes)
		if washer_state 
			fmt.Println("你忘关洗衣机了")
		
	 else 
		fmt.Println("洗衣失败")
	


// 整个洗衣服的过程
func WashProcedure(powder, closes int) 
	washer_state := false
	clean := false

	washer_state, closes = prepare(powder, closes, clean)
	clean, closes, washer_state = wash(washer_state, closes)
	check(clean, closes, washer_state)

面向过程编程整个过程分为若干步,每一步对应一个函数,函数之间要传递大量的参数
面向对象编程把大量参数封装到一个结构体里面,给结构体赋予方法,方法里面去修改结构体的成员变量,go语言面向对象的好处:打包参数、继承、面向接口编程

// 洗衣机
type Washer struct 
	State  bool
	Powder int


// 衣服
type Closes struct 
	Clean bool


func (washer *Washer) prepare(closes []*Closes) error 
	if washer.State == true || washer.Powder <= 0 || len(closes) <= 0 
		return errors.New("请确保在关机的状态下加入适量衣物和洗衣粉")
	
	return nil


func (washer *Washer) wash(closes []*Closes) error 
	if err := washer.prepare(closes); err != nil 
		return err
	

	fmt.Println("开机")
	washer.State = true

	// 检查是否有脏衣服
	clean := true
	for _, ele := range closes 
		if ele.Clean == false 
			clean = false
			break
		
	
	if clean 
		washer.State = false
		return errors.New("所有衣服都是干净的,不需要洗")
	

	// 开始洗衣服
	fmt.Println("注水")
	fmt.Println("滚动")
	fmt.Println("关机")
	washer.State = false
	for _, ele := range closes 
		ele.Clean = true
	
	return nil


func (washer *Washer) check(err error, closes []*Closes) 
	if err != nil 
		fmt.Printf("洗衣失败:%v\\n", err)
	 else 
		fmt.Printf("洗干净了%d件衣服\\n", len(closes))
		if washer.State == true 
			fmt.Println("你忘关洗衣机了")
		
	

2. 构造函数

定义User结构体

type User struct 
    Name string // ""表示未知
    Age int // -1表示未知
    Sex byte // 1男,2女,3未知

  • u := User构造一个空的User,各字段都取相应数据类型的默认值
  • up := new(User)构造一个空的User,并返回其指针

自定义构造函数

func NewDefaultUser() *User 
    return &User
        Name: "",
        Age: -1,
        Sex: 3,
    

func NewUser(name string, age int, sex byte) *User 
    return &User
        Name: name,
        Age: age,
        Sex: sex,
    

单例模式,确保在并发的情况下,整个进程里只会创建struct的一个实例

var (
    sUser *User
    uOnce sync.Once
)
func GetUserInstance() *User 
	// 确保即使在并发的情况下,下面的3行代码在整个go进程里只会被执行一次
    uOnce.Do(func() 
    if sUser == nil 
        sUser = NewDefaultUser()
        
    )
    return sUser


// 调用GetUserInstance()得到的是同一个User实例
su1 := GetUserInstance()
su2 := GetUserInstance()
// 修改su1会影响su2

3. 继承与重写

通过嵌入匿名结构体,变相实现“继承”的功能,因为访问匿名成员时可以跳过成员名直接访问它的内部成员

type Plane struct 
	color string

type Bird struct 
	Plane 

bird := Bird 
bird.Plane.color
bird.color

重写

func (plane Plane) fly() int 
	return 500


//重写父类(Plane)的fly方法
func (bird Bird) fly() int 
	return bird.Plane.fly()+100 // 调用父类的方法

正规来讲,Go语言并不支持继承,它只是支持组合

type Plane struct 
type Car struct
// Bird组合了Plane和Car的功能
type Bird struct 
	Plane 
	Car

4. 泛型

在有泛型之前,同样的功能需要为不同的参数类型单独实现一个函数

func add4int(a, b int) int 
	return a + b

func add4float32(a, b float32) float32 
	return a + b

func add4string(a, b string) string 
	return a + b

使用泛型

type Addable interface
type int, int8, int16, int32, int64,
	uint, uint8, uint16, uint32, uint64, uintptr,
	float32, float64, complex64, complex128,string

func add[T Addable](a,b T)T
	return a+b

在go1.17中泛型默认没有开启,如果想用运行时命令行需要加-gcflags=-G=3,或者设置环境变量export GOFLAGS=“-gcflags=-G=3”,泛型正式版将在go 1.18中发布,但是Go语言之父Rob Pike建议不在Go 1.18的标准库中使用泛型

二、反射

1. 反射介绍

反射就是在运行期间(不是编译期间)探知对象的类型信息和内存结构、更新变量、调用它们的方法

反射的使用场景:

  • 函数的参数类型是interface,需要在运行时对原始类型进行判断,针对不同的类型采取不同的处理方式。比如json.Marshal(v interface)
  • 在运行时根据某些条件动态决定调用哪个函数,比如根据配置文件执行相应的算子函数

Go标准库里的json序列化就使用了反射

type User struct 
    Name string
    Age int
    Sex byte `json:"gender"`

user := User
    Name: "钱钟书",
    Age: 57,
    Sex: 1,

json.Marshal(user) // 返回 "Name":"钱钟书","Age":57,"gender":1

反射的弊端:

  • 代码难以阅读,难以维护
  • 编译期间不能发现类型错误,覆盖测试难度很大,有些bug需要到线上运行很长时间才能发现,可能会造成严重用后果
  • 反射性能很差,通常比正常代码慢一到两个数量级。在对性能要求很高,或大量反复调用的代码块里建议不要使用反射

2. 反射的基础数据类型

reflect.Type用于获取类型相关的信息

type Type interface 
	Method(int) Method // 第i个方法
	MethodByName(string) (Method, bool) // 根据名称获取方法
	NumMethod() int // 方法的个数
	Name() string // 获取结构体名称
	PkgPath() string // 包路径
	Size() uintptr // 占用内存的大小
	String() string // 获取字符串表述
	Kind() Kind //数据类型
	Implements(u Type) bool //判断是否实现了某接口
	AssignableTo(u Type) bool // 能否赋给另外一种类型
	ConvertibleTo(u Type) bool // 能否转换为另外一种类型
	Elem() Type // 解析指针
	Field(i int) StructField // 第i个成员
	FieldByIndex(index []int) StructField // 根据index路径获取嵌套成员
	FieldByName(name string) (StructField, bool) // 根据名称获取成员
	FieldByNameFunc(match func(string) bool) (StructField, bool) // 根据匹配函数匹配需要的字段
	Len() int // 容器的长度
	NumIn() int // 输出参数的个数
	NumOut() int // 返回参数的个数

通过reflect.Value获取、修改原始数据类型里的值

type Value struct 
	// 代表的数据类型
	typ *rtype
	// 指向原始数据的指针
	ptr unsafe.Pointer

3. 反射API

3.1 reflect.Type

①如何得到Type

通过TypeOf()得到Type类型

typeI := reflect.TypeOf(1)
typeS := reflect.TypeOf("hello")
fmt.Println(typeI) // int
fmt.Println(typeS) // string

typeUser := reflect.TypeOf(&common.User)
fmt.Println(typeUser) // *common.User
fmt.Println(typeUser.Kind()) // ptr
fmt.Println(typeUser.Elem().Kind()) // struct

②指针Type转为非指针Type

typeUser := reflect.TypeOf(&common.User)
typeUser2 := reflect.TypeOf(common.User)
assert.IsEqual(typeUser.Elem(), typeUser2)

③获取struct成员变量的信息

typeUser := reflect.TypeOf(common.User) // 需要用struct的Type,不能用指针的Type
fieldNum := typeUser.NumField() // 成员变量的个数
for i := 0; i < fieldNum; i++ 
	field := typeUser.Field(i)
	fmt.Printf("%d %s offset %d anonymous %t type %s exported %t json tag %s\\n", i,
		field.Name,            // 变量名称
		field.Offset,          // 相对于结构体首地址的内存偏移量,string类型会占据16个字节
		field.Anonymous,       // 是否为匿名成员
		field.Type,            // 数据类型,reflect.Type类型
		field.IsExported(),    // 包外是否可见(即是否以大写字母开头)
		field.Tag.Get("json")) // 获取成员变量后面``里面定义的tag

fmt.Println()

// 可以通过FieldByName获取Field
if nameField, ok := typeUser.FieldByName("Name"); ok 
	fmt.Printf("Name is exported %t\\n", nameField.IsExported())

// 也可以根据FieldByIndex获取Field
thirdField := typeUser.FieldByIndex([]int2) // 参数是个slice,因为有struct嵌套的情况
fmt.Printf("third field name %s\\n", thirdField.Name)

④获取struct成员方法的信息

typeUser := reflect.TypeOf(common.User)
methodNum := typeUser.NumMethod() // 成员方法的个数,接收者为指针的方法【不】包含在内
for i := 0; i < methodNum; i++ 
	method := typeUser.Method(i)
	fmt.Printf("method name:%s ,type:%s, exported:%t\\n", method.Name, method.Type, method.IsExported())

fmt.Println()

typeUser2 := reflect.TypeOf(&common.User)
methodNum = typeUser2.NumMethod() // 成员方法的个数,接收者为指针或值的方法【都】包含在内,也就是说值实现的方法指针也实现了(反之不成立)
for i := 0; i < methodNum; i++ 
	method := typeUser2.Method(i)
	fmt.Printf("method name:%s ,type:%s, exported:%t\\n", method.Name, method.Type, method.IsExported())

⑤获取函数的信息

func Add(a, b int) int 
	return a + b


typeFunc := reflect.TypeOf(Add) // 获取函数类型
fmt.Printf("is function type %t\\n", typeFunc.Kind() == reflect.Func)
argInNum := typeFunc.NumIn() // 输入参数的个数
argOutNum := typeFunc.NumOut() // 输出参数的个数
for i := 0; i < argInNum; i++ 
	argTyp := typeFunc.In(i)
	fmt.Printf("第%d个输入参数的类型%s\\n", i, argTyp)

for i := 0; i < argOutNum; i++ 
	argTyp := typeFunc.Out(i)
	fmt.Printf("第%d个输出参数的类型%s\\n", i, argTyp)

⑥判断类型是否实现了某接口

// 通过reflect.TypeOf((*<interface>)(nil)).Elem()获得接口类型,因为People是个接口不能创建实例,所以把nil强制转为*common.People类型
typeOfPeople := reflect.TypeOf((*common.People)(nil)).Elem()
fmt.Printf("typeOfPeople kind is interface %t\\n", typeOfPeople.Kind() == reflect.Interface)
t1 := reflect.TypeOf(common.User)
t2 := reflect.TypeOf(&common.User)
// 如果值类型实现了接口,则指针类型也实现了接口,反之不成立
fmt.Printf("t1 implements People interface %t\\n", t1.Implements(typeOfPeople))

3.2 reflect.Value

①如果获得Value

通过ValueOf()得到Value

iValue := reflect.ValueOf(1)
sValue := reflect.ValueOf("hello")
userPtrValue := reflect.ValueOf(&common.User
	Id:     7,
	Name:   "Ricky",
	Weight: 60,
	Height: 1.80,
)
fmt.Println(iValue)       // 1
fmt.Println(sValue)       // hello
fmt.Println(userPtrValue) // &7 Ricky  60 1.80

②Value转为Type

iType := iValue.Type()
sType := sValue.Type()
userType := userPtrValue.Type()
// 在Type和相应Value上调用Kind()结果一样的
fmt.Println(iType.Kind() == reflect.Int, iValue.Kind() == reflect.Int, iType.Kind() == iValue.Kind())  
fmt.Println(sType.Kind() == reflect.String, sValue.Kind() 以上是关于Go基础面向对象和反射机制的主要内容,如果未能解决你的问题,请参考以下文章

Java基础面试题-第一集

Java基础面试题-第一集

Java基础面试题-第一集

Java基础面试题-第一集

Java基础:Java的反射机制

Go基础:方法