golang面向对象和interface接口

一、 golang面向对象介绍

1、golang也支持面向对象编程,但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。
2、golang没有类(class),golang语言的结合体(struct)和其它编程语言的类有同等的地位。
3、golang面向对象编程,去掉了传统语言面向对象的继承、方法重载、结构函数和析构函数、隐藏的this指针等。
4、goalng仍然有面向对象编程的继承,封装和多态的特性,。
5、golang面向接口编程非常重要

二、结构体创建注意事项

1、字段申明语法同变量
2、字段的类型可以为:基本类型、数组或应用类型
3、在创建一个结构体变量后,如果没有给字段赋值,都对应改类型的默认值(如bool为false,int为0,string为"")
4、不同结构体变量的字段是独立互不影响的,一个结构体变量字段的更改不会影响另一个结构体的值类型。

三、创建struct语法

type 结构体名称 struct{
field type
}

创建例子:

type stru struct{
    a1 string
    a2 int
    a3 [5]float64
    a4 []int //切片
    a5 *int //指针
    a6 map[string]string //map
}

  

使用例子:

package main
import "fmt"

func main(){
    type ss struct{
        Name string
        Age int
    } 
    //1、直接申明
    var ss1 ss 
    ss1.Name = "张三"
    ss1.Age = 20
    fmt.Println(ss1)
   //2、使用{}
    var ss2 ss = ss{"李四",22}
    fmt.Println(ss2)
   //3、&
    var ss3 *ss = new(ss)
    (*ss3).Name = "王五"
    (*ss3).Age = 30
    fmt.Println(ss3,*ss3)
   //4、{}
    var ss4 *ss = &ss{}
    ss4.Name = "韩梅梅"
    ss4.Age = 66
    fmt.Println(ss4,*ss4)

}



###结果###
{张三 20}
{李四 22}
&{王五 30} {王五 30}
&{韩梅梅 66} {韩梅梅 66}

  

 四、结构体使用注意事项

1、结构体的所有字段在内存中是连续的

例子:

package main
import "fmt"


type Point struct {
    x int
    y int
}

type Test1 struct {
    s1,s2 Point  
}

func main(){
    a1 := Test1{Point{1,2},Point{3,4}}
    //打印地址
    fmt.Printf("%p,%p,%p,%p",&a1.s1.x,&a1.s1.y,&a1.s2.x,&a1.s2.y)

}

##结果##
0xc4200141e0,0xc4200141e8,0xc4200141f0,0xc4200141f8

  

2、结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型

例子:

package main
import "fmt"

type A struct {
    x int
}

type B struct {
    x int
}

func main(){
    var a A 
    var b B
    b.x = 2
    a = A(b) //可以转换,但是结构体的字段和类型必须完全一样
    fmt.Println(a,b)
}

###结果###
{2} {2}

  

 3、结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转

例子:

package main
import "fmt"

func main(){
    type A struct {
        x int
    }
    type B A
    var s1 A
    var s2 B
    s2 = B(s1) //强转
    fmt.Println(s1,s2)

}

##结果##
{0} {0}

  

4、struct 的每个字段上,可以写上一个 tag, 该 tag 可以通过反射机制获取,常见的使用场景就是序列化和反序列化

序列化的使用场景:

例子:

package main
import (
    "fmt"
    "encoding/json"
)
type A struct {
    Name string `json:"name"` //`json:"name"`就是struct的tag
    Age int `json:"age"`
}   

func main(){
    s1 := A{"张三",20}
    //将s1变量序列化为json格式字符串
    jsonStr,err := json.Marshal(s1)
    if err != nil {
        fmt.Println("json错误处理",err)
    }
    fmt.Println(string(jsonStr))
}

###结果###
{"name":"张三","age":20}

  

五、结构体的方法

方法的声明和调用
type A struct {
Num int
}
func (a A) test() {
fmt.Println(a.Num)
}
语法的说明
1) func (a A) test() {} 表示 A 结构体有一方法,方法名为 test
2) (a A) 体现 test 方法是和 A 类型绑定的

例子:

package main
import "fmt"

type A struct {
    Name string
}
//test方法只能公告A类型的变量类调用,不能直接调用,也不能使用其它类型变量来调用
func (a A) test(){  //将test方法绑定到A类型中,a的名字是随意指定的
    fmt.Println("test()",a.Name)    
}

func (b A) add(n1,n2 int) int {
    return n1 + n2
}

func main(){
    var x A
    x.Name = "zhang"
    x.test()
    fmt.Println(x.add(1,2))
}
####结果####
test() zhang
3

  

 六、golang面向对象的三大特性

1、封装

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


1、隐藏实现细节
2、提可以对数据进行验证,保证安全合理

2、封装的实现步骤


1) 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private)
2) 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
3) 提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值
func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {
//加入数据验证的业务逻辑
var.字段 = 参数
}
4) 提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值func (var 结构体类型名) GetXxx() {return var.age

例子:

[root@localhostgo_test]#cat src/fz/fz.go 
package fz
import "fmt"

type T1 struct {
    Name string
    age int //开头是小写,其它包不能直接访问
}

func Ss(name string) *T1 {
    return &T1{
        Name:name,
    }
}

func (a *T1) Getage() int {
    fmt.Println(a.age)
    return a.age
}

[root@localhostgo_test]#cat class6.go 
package main
import (
    "fmt"
    "fz"
)

func main(){
    x := fz.Ss("root")
    fmt.Println(x)
    x.Getage()
}
[root@localhostgo_test]#go run class6.go 
&{root 0}
0

  

2、继承

继承可以解决代码复用,让我们的编程更加靠近人类思维。
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法
在 Golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性

继承给编程带来的便利


1) 代码的复用性提高了
2) 代码的扩展性和维护性提

 

嵌套匿名结构体的基本语法
type Goods struct {
Name string
Price int
}
type Book struct {
Goods //这里就是嵌套匿名结构体 Goods
Writer string
}

例子:

package main
import "fmt"


type A struct {
    name string
    age int
}

func (p *A) Run() {
    fmt.Println("Run...")
}

type B struct {
    A
    b1 int
}

type C struct {
    A
}

func (p *C) Crun() {
    fmt.Println(p.name,p.age)
}

func main(){
    var q B
    q.name = "qq"
    q.age = 3
    q.b1 = 20
    q.Run()
    fmt.Println(q)
    fmt.Println("==========") 
    var w C
    w.name = "ww"
    w.age = 222
    w.Run()
    fmt.Println(&w)
    
}

####结果######
Run...
{{qq 3} 20}
==========
Run...
&{{ww 222}}

  

3、多态(多态基于接口interface)

介绍:

变量(实例)具有多种形态。面向对象的第三大特征,在 Go 语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态

接口体现多态的两种形式

1、在下面interface的 Usb 接口案例,Usb usb ,即可以接收手机变量,又可以接收相机变量,就体现了Usb接口多态

 2、多态数组

例子:给 Usb 数组中,存放 Phone 结构体 和 Camera 结构体变量

package main
import ( "fmt"
)
//声明/定义一个接口
type Usb interface {
//声明了两个没有实现的方法
    Start()
    Stop()
}
type Phone struct {
    name string
}
//让 Phone 实现 Usb 接口的方法
func (p Phone) Start() {
    fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() {
    fmt.Println("手机停止工作。。。")
}
type Camera struct {
    name string
}
//让 Camera 实现 Usb 接口的方法
func (c Camera) Start() {
    fmt.Println("相机开始工作。。。")
}
func (c Camera) Stop() {
    fmt.Println("相机停止工作。。。")
}
func main() {
//定义一个 Usb 接口数组,可以存放 Phone 和 Camera 的结构体变量
//这里就体现出多态数组
    var usbArr [3]Usb
    usbArr[0] = Phone{"vivo"}
    usbArr[1] = Phone{"小米"}
    usbArr[2] = Camera{"尼康"}
    fmt.Println(usbArr)
}

#####结果#######
[{vivo} {小米} {尼康}]

  

七、接口interface

简介

1、interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个自定义类型要使用的时候,在根据具体情况把这些方法写出来实现

2、 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。
3、 Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang 中没有 implement 这样的关键字

基本语法

例子:

package main
import ( "fmt"
)
//声明/定义一个接口
type Usb interface {
    //声明了两个没有实现的方法
    Start()
    Stop()
}
type Phone struct {
}
    //让 Phone 实现 Usb 接口的方法
func (p Phone) Start() {
    fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() {
    fmt.Println("手机停止工作。。。")
}
type Camera struct {
}
    //让 Camera 实现 Usb 接口的方法
func (c Camera) Start() {
    fmt.Println("相机开始工作。。。")
}
func (c Camera) Stop() {
    fmt.Println("相机停止工作。。。")
}
//计算机
type Computer struct {
}
//编写一个方法 Working 方法,接收一个 Usb 接口类型变量
//只要是实现了 Usb 接口 (所谓实现 Usb 接口,就是指实现了 Usb 接口声明所有方法)
func (c Computer) Working(usb Usb) { //usb 变量会根据传入的实参,来判断到底是 Phone,还是 Camera
    //通过 usb 接口变量来调用 Start 和 Stop 方法
    usb.Start()
    usb.Stop()
}
func main() {
    //测试
    //先创建结构体变量
    computer := Computer{}
    phone := Phone{}
    camera := Camera{}
    //关键点
    computer.Working(phone)
    computer.Working(camera) 
}


#####结果####
手机开始工作。。。
手机停止工作。。。
相机开始工作。。。
相机停止工作。。。

  

使用接口注意事项

1、接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
2、接口中所有的方法都没有方法体,即都是没有实现的方法。
3、在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
4、一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
5、只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
6、一个自定义类型可以实现多个接口
7、Golang 接口中不能有任何变量
8、一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必须将 B,C 接口的方法也全部实现。
9、interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
10、空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口

 

接口最佳实践例子:

package main
import ( "fmt"
    "sort"
    "math/rand"
)
//1.声明 Hero 结构体
type Hero struct{
    Name string
    Age int
}
//2.声明一个 Hero 结构体切片类型
type HeroSlice []Hero

//3.实现 Interface 接口
func (hs HeroSlice) Len() int {
    return len(hs)
}
//Less 方法就是决定你使用什么标准进行排序
//1. 按 Hero 的年龄从小到大排序!!
func (hs HeroSlice) Less(i, j int) bool {
    return hs[i].Age < hs[j].Age
//修改成对 Name 排序
//return hs[i].Name < hs[j].Name
}
func (hs HeroSlice) Swap(i, j int) {
//交换
    hs[i], hs[j] = hs[j], hs[i]
}
//1.声明 Student 结构体
type Student struct{
    Name string
    Age int
    Score float64
}
//将 Student 的切片,安 Score 从大到小排序!!
func main() {
    //先定义一个数组/切片
    var intSlice = []int{0, -1, 10, 7, 90}
    //要求对 intSlice 切片进行排序
    //1. 冒泡排序...
    sort.Ints(intSlice)
    fmt.Println(intSlice)
    //测试看看我们是否可以对结构体切片进行排序
    var heroes HeroSlice
    for i := 0; i < 10 ; i++ {
        hero := Hero{
        Name : fmt.Sprintf("英雄|%d", rand.Intn(100)), Age : rand.Intn(100), }
        //将 hero append 到 heroes 切片
        heroes = append(heroes, hero)
    }
    //看看排序前的顺序
    for _ , v := range heroes {
        fmt.Println(v)
    }
    sort.Sort(heroes)
    fmt.Println("-----------排序后------------")
    //看看排序后的顺序
    for _ , v := range heroes {
        fmt.Println(v)
    }
    i := 10
    j := 20
    i, j = j, i
    fmt.Println("i=", i, "j=", j) // i=20 j = 10
}


######运行结果######
[-1 0 7 10 90]
{英雄|81 87}
{英雄|47 59}
{英雄|81 18}
{英雄|25 40}
{英雄|56 0}
{英雄|94 11}
{英雄|62 89}
{英雄|28 74}
{英雄|11 45}
{英雄|37 6}
-----------排序后------------
{英雄|56 0}
{英雄|37 6}
{英雄|94 11}
{英雄|81 18}
{英雄|25 40}
{英雄|11 45}
{英雄|47 59}
{英雄|28 74}
{英雄|81 87}
{英雄|62 89}
i= 20 j= 10

  

原文地址:https://www.cnblogs.com/zhangb8042/p/10557098.html