07 面向对象编程-结构、封装、继承、多态、接口

  1. Golang支持面向对象编程,但是和传统面向对象有区别,并不是纯粹面向对象的语言,只能说Golang支持面向对象编程特性
  2. Golang没有类,通过结构体struct 来实现OOP,非常简洁。

Struct定义

  1. Struct字段声明语法同变量,创建一个结构体后如果没有为字段赋值 则字段取值为默认值
  2. 结构体可以进行type 重新定义,Golang默认是新类型
  3. 结构体是用户单独定义的类型,和其他类型转换时需要有完全相同的字段(名字、个数、类型)
  4. struct 每个字段 都可以写上一个tag,该tag 可以通过反射机制获取
  5. 结构体所有字段在内存中是连续的

示例代码

//声明一个结构体
type Cat struct{
    
    
	Name string
	Age int
	Color string
	Hobby string
}
func test01(){
    
    
	
	//创建结构体并赋值1
	var cat1 Cat
	cat1.Name = "小白"
	cat1.Age = 10
	cat1.Color = "白色"
	cat1.Hobby = "吃 鱼"
	var cat2 Cat
	fmt.Println(cat1,cat2)//{小白 10 白色 吃 鱼} { 0  }
    //创建结构体并赋值2
	c3 := Cat{
    
    "小黑",20,"黑色","shuijiao"}
	 //创建结构体并赋值3
	var c4 *Cat = new(Cat)
	(*c4).Name = "小花" //通过索引访问值 再访问属性
	c4.Age = 10 //也可以直接通过索引访问属性(goLang做了优化)
    //创建结构体并赋值4
	var c5 *Cat  = &Cat{
    
    }
    c5.Name = "c5"
	fmt.Println(cat1,cat2,c3,c4,c5)
	c6 := c3
	c6.Name ="c6"
	//赋值后会直接克隆一个变量给C6,修改c6的值 c3 不受影响
	//{小黑 20 黑色 shuijiao} {c6 20 黑色 shuijiao}
	fmt.Println(c3,c6)
	//创建结构体变量时直接指定字段值
	var c7 = Cat{
    
    
				Name:"小绿",
				Age:40,
				}
	fmt.Println(c7)	

}
type Point struct{
    
    
	x int
	y int
}
type Rect struct{
    
    
	leftUp, rightDown Point
}
type Rect2 struct{
    
    
	leftUp, rightDown *Point
}


func test02(){
    
    

	//结构体所有字段在内存中是连续的
	r1 := Rect{
    
    Point{
    
    1,2} , Point{
    
    3,4}}
	fmt.Printf("r1.rd.x地址= %p r1.rd.y地址= %p r1.lu.x地址= %p r1.lu.y地址= %p  \n",
	&r1.rightDown.x,&r1.rightDown.y,&r1.leftUp.x,&r1.leftUp.y)
	//	fmt.Printf("r1.rd.x地址= %p r1.rd.y地址= %p r1.lu.x地址= %p r1.lu.y地址= %p  \n",
	//&r1.rightDown.x,&r1.rightDown.y,&r1.leftUp.x,&r1.leftUp.y)
	r2 := Rect2{
    
    &Point{
    
    1,2} , &Point{
    
    3,4}}
	fmt.Printf("r2.rd.x地址= %p r2.rd.y地址= %p r2.lu.x地址= %p r2.lu.y地址= %p  \n",
	&r2.rightDown.x,&r2.rightDown.y,&r2.leftUp.x,&r2.leftUp.y)

	//结构体是用户单独定义的类型,和其他类型转换时需要有完全相同的字段(名字、个数、类型)
	
	type A struct{
    
    
		Num int
	}
	type B struct{
    
    
		Num int
	}
	var a A
	var b B
	a = A(b)
	a.Num =10
	fmt.Println(a,b)
	
	//结构体进行type 重新定义,Golang默认是新类型,
	type AA A
	var aa AA
	//var a A = aa 报错需要强转
	var a3 A = A(aa)
	fmt.Println(aa,a3)
	//struct 每个字段 都可以写上一个tag,该tag 可以通过反射机制获取
	//常见的场景就是 序列化和反序列化
	type Monster struct {
    
    
		Name string `json:"name"`
		Age int `json:age`
	}
	m1 := Monster{
    
    "牛魔王",300}
	jsonstr ,err := json.marshal(m1)
	if err != nil {
    
    
		fmt.Println("json 字符串处理错误")
	}
	fmt.Println("json 字符串=",jsonstr)
}

struct 方法

struct 方法声明语法 表示 A结构体有一个test 方法 (a A)标识这个方式是和 A类型绑定的

func( a A)test(){
    
    } 
func( a A)test2(参数列表)(返回值列表){
    
    
		方法体
	return 返回值
} 

注意点

  1. 在通过一个结构变量去调用方法时,调用机制和函数相同,只不过调用结构体方法时,结构体变量也会作为一个参数传递到方法中。
  2. Golang中的方法是指定在数据类型上的,因此所有自定义类型都可以有方法,包括 struct、int 、float32
  3. 如果一个类型实现了 String() 这个方法,那么 fmt.Println 默认会调用变量的这个方法作为结果进行输出
  4. 通过变量去调用方法时,其(参数传递)机制和函数一样,不一样的地方是变量本身也会作为参数传递给该方法(如果变量是值类型则进行值拷贝,如果是引用类型则进行地质拷贝,如果希望在方法中修改结构体的值可以使用指针来传递)。
  5. 方法的访问范围控制和函数一样,方法名首字母小写只在本包访问,大写可以在包外访问。

示例代码

//定义结构体
type Person struct{
    
    
	Name string
}
//定义方法
func (p Person) speak(){
    
    
	fmt.Println(p.Name,"是一个好人")
}
func test03(){
    
    
	p := Person{
    
    "zhangsan"}
	p.speak()
}

工厂模式

工厂模式用来通过指定的方法来创建(只在包内可用的/首字母小写的结构体)实例。
同时如果结构体的属性 首字母小写也可以通过方法类获取(类似于java中的set/get 方法)
实例代码

func test04(){
    
    
	var stu = mode.NewStudent("tom",12.0)
	fmt.Println(*stu)
	fmt.Println(stu.Name,stu.GetScore())
}
package model

type student struct{
    
    
	Name string
	score float64
}
//通过工厂方法获取 隐藏结构的实例
func NewStudent(n string,s float64) *student{
    
    
	return &student{
    
    Name:n,score:s}
}
//通过方法或去被封装的属性
func( stu *student) GetScore () float64{
    
    
	return stu.score
}

面向对象三大特性

Golang也有面向对象编程的三大特性,只不过实现方式和其他OOP语言不同

封装

将抽象出来的字段封装在一起
好处:隐藏实现细节 同时借助统一访问方法,可以对数据进行验证,保证数据合理性
实现方式

  1. 将结构体、字段首字母小写(类似java private)
  2. 为封装的结构体提供工厂模式函数,首字母大写,类似一个构造函数
  3. 为结构体提供首字母大写的Set/Get 方法 用于属性读写
    示例代码
func test05(){
    
    
	var per = mode.NewPerson("TOM")
	per.SetAge(50)
	per.SetSal(10000.0)
	fmt.Println(per)
	fmt.Println(per.GetAge(),per.GetSal(),per.Name)

}
package model
import (
	"fmt"
)
type person struct{
    
    
	Name string
	age int
	sal float64
}
// 封装工厂方法 创建结构体实例
func NewPerson(name string) *person{
    
    
	return &person{
    
    
		Name:name,
	}
}
func (p *person)SetAge(age int){
    
    
	if age>0 && age<150{
    
    
		p.age = age 
	} else{
    
    
		fmt.Println("年龄不在合法范围内")
	}

}

func (p *person)SetSal(sal float64){
    
    
	if sal>3000 && sal<30000{
    
    
		p.sal = sal 
	} else{
    
    
		fmt.Println("薪资不在合法范围内")
	}
}
func (p *person)GetAge() int {
    
    
	fmt.Println("====>",p.age)
	return p.age
}

func (p *person)GetSal() float64 {
    
    
	fmt.Println("====>",p.sal)
	return p.sal
}

继承

继承可以解决代码的复用,当多个结构体存在相同的字段和方法时,可以抽象出一个结构体包含公有的属性和方法。其他结构体不用定义这些属性和方法,只需要嵌套这个结构体即可。
就是说如果一个结构体嵌套了另外一个匿名结构体,那么这个结构体可以直接访问匿名结构体的方法和字段,如此就实现了继承。
基本语法形式

type Goods struct{
    
    
	Name string
	Price int
}
type Book struct{
    
    
	Goods //嵌套匿名结构体
	Writer string
}

示例代码

func test06(){
    
    
	pu := &Pupil{
    
    }
	pu.Student.Name = "TOM"
	pu.Student.Age =12
	pu.Age=13//对继承的字段可以简化访问
	pu.testing();
	pu.Student.ShowInfo()
	pu.ShowInfo()//方法简化访问
	//声明结构体时直接为嵌套结构体赋值
	pu2 := &Pupil{
    
    
		Student{
    
    Name: "Jack",
			Age:19,
			Score:100,
		},
	}
	pu2.ShowInfo()
}
//定义学生类
type Student struct{
    
    
	Name string
	Age int
	Score int
}
//显示学生信息
func (stu *Student)ShowInfo(){
    
    
	fmt.Println(stu)
}
func (stu *Student) SetCore(score int){
    
    
	stu.Score = score
}
//定义小学生类
type Pupil struct{
    
    
	Student//嵌入 学生结构体
}
func (p *Pupil) testing(){
    
    
	fmt.Println("小学生正在考试...")
}

注意点

  1. 结构体可以使用匿名结构体的所有字段和方法,无论首字母大写还是小写
  2. 结构体访问匿名结构体方法和属性可以简化,简化访问时访问逻辑:如果当前结构体有访问属性/方法则直接调用本结构体的该属性/方法,如果当前结构体没有该属性/方法,则到嵌套的匿名结构体中找该属性/方法,找到就调用,如果找不到就报错。
  3. 当结构体和嵌套结构体有相同的属性或方法时,编译器采用就近访问原则。如果想调用匿名结构体中的属性/方法这个时候就必须 通过匿名结构体的名字进行调用(不能进行简化调用)
  4. 如果一个结构体嵌套了多个结构体,且多个嵌套结构体包含相同的属性/方法,那么在调用时就必须指明匿名结构体的名字。否则编译器报错
  5. 如果一个结构体嵌套了一个有名字的结构体,那么二者是组合关系,这个时候访问组合结构体的属性/方法时必须带上 结构体的名字
  6. 嵌套结构体变量后,可以在创建结构体变量时直接为嵌套结构体赋值。
  7. 如果一个结构体中嵌套了基本数据类型如int,访问方式 A.int=12, 如果要有多个int字段必须制定名字

多重继承
如果一个struct 嵌套了多个匿名结构体,那么它可以访问所有结构体的字段和方法,这就是Golang的多重继承
*如果嵌套的多个结构体中包含相同的属性/方法,那么在调用方法时必须明确嵌套结构体的名字

接口

interface 类型可以定义一组方法,但是不需要实现,并且interface 不能包含任何变量,在具体类型中根据实际情况实现这些方法。

type 接口名 interface{
    
    
	method1()
	method2()
}

示例程序

type Gun interface{
    
    
	Fire()
}
type Gun51 struct{
    
    
	
}
func(g Gun51) Fire(){
    
    
	fmt.Println("Gun51连续发射7mm子弹...")
}
type GunRPG struct{
    
    
	
}
func(g GunRPG) Fire(){
    
    
	fmt.Println("RPC发射火箭弹...")
}
func test07(){
    
    
	var g51 Gun51
	var rpg GunRPG
	var gun Gun = g51
	gun.Fire();
	gun = rpg
	gun.Fire()
}

注意点

  1. 接口里所有的方法都没有方法体,体现了程序 高内聚低耦合的思想
  2. Golang 中不显式的实现接口,只要一个变量中包含接口声明的所有方法,就认为这个变量实现了这个接口。
  3. 接口本身不创建实例,但是可以指向一个实现了该接口的实例
  4. 一个自定义类型只有实现了某个接口,才可以将该类型变量赋值给这个接口。
  5. 任何自定义类型都可以实现接口 不一定是结构体,且一个变量可以实现多个接口
  6. 接口A可以继承多个其他接口B、C,这个时候如果变量实现A接口也必须同时实现B和C接口。如果接口(包括继承的接口)中包含重复的方法,那么编译器报错
  7. interface 默认是一个指针类型,如果没有初始化默认输出nil
  8. 空接口 没有任何方法,所有类型都实现了空接口,即所有类型都可以赋值给空接口

继承和接口比较
继承主要解决代码的复用性和可维护性。强调共性复用。
接口主要是设计规范,让其他类型遵守规范 。强调共性能力 各自实现。
接口一定程度上实现代码解耦

多态

变量具有多种形态,Golang中多态通过接口实现。按照同一个接口在不同变量中做不同实现,这时接口就呈现多种不同状态。(见接口示例)
一般通过方法参数体现多态

类型断言
如何将一个接口变量赋值给自定义类型变量?需要先确定变量的类型,即类型断言
示例代码

func test08(){
    
    
	var x interface{
    
    }
	var b2 int = 1
	x = b2
	// 将x 转换为int  
	y, ok := x.(int); 
	if ok {
    
    
		fmt.Printf("转换成功 y的类型是%T 值是=%v \n",y,y);
	}else{
    
    
		fmt.Println("转换失败")
	}
	fmt.Println("x.(int)==>",x.(int))
	//fmt.Println("x.(type)==>",x.(type)) 只能在switch 中使用 这里使用报错
	//调用断言方法
	TypeJudge(b2)
}

func TypeJudge(items... interface{
    
    }){
    
    
	for index,x := range items{
    
    
		switch x.(type){
    
    
		case bool :
			fmt.Printf("第%v个参数是 bool 类型,值是 %v \n",index,x)
		case float32:
			fmt.Printf("第%v个参数是 float32 类型,值是 %v \n",index,x)
		case float64:
			fmt.Printf("第%v个参数是 float64 类型,值是 %v \n",index,x)
		case int,int32,int64:
			fmt.Printf("第%v个参数是 int 类型,值是 %v \n",index,x)
		case string:
			fmt.Printf("第%v个参数是 string 类型,值是 %v \n",index,x)
		default:
			fmt.Printf("第%v个参数 类型不确定,值是 %v \n",index,x)

		}
	}
}

猜你喜欢

转载自blog.csdn.net/zhangxm_qz/article/details/114681802
今日推荐