面向对象

Golang语言面向对象编程说明

  1. Golang支持面向对象(OOP),但是和传统的面向对象编程有区别,并不是存粹的面向对象语言。
  2. Golang中没有类(class),Go语言的结构体(struct)和其他编程语言的类(class)有同等的地位,你可以理解Golang是基于struct来实现OOP特性的
  3. Golang面向对象编程非常简洁,去掉了传统OOP语言的继承、方法重载、析构函数、构造函数、隐藏的this指针等
  4. Golang仍然有面向对象编程的继承,封装和多态,只是实现的方式和其他OOP语言不一样,比如继承:Golang中没有extends关键字,继承是通过匿名字段来实现的
  5. Golang面向对象很优雅,OOP本身就是语言类型系统的一部分,通过接口关联,耦合性低,也非常灵活。在Golang中面向接口编程是非常重要的特性

结构体

Go中没有类的概念,只能使用结构体来模拟类,并且go中的结构体只能有属性不能定义方法

结构体的所有字段在内存中是连续分布的

// 如果结构体的名称是大写,表示该结构体可以被其他包访问
// 反之不能被访问
type Person struct {
   // 如果字段的名称是大写,表示该字段可以被其他包访问
   // 反之不能访问
   name string
   age  int
}

注意

并且在函数传递结构体参数时,结构体是值传递,也就是说在方法中对结构体进行修改不会影响外部的修改,如果需要修改外部则需要使用指针

func main() {
	var p = Person{}
	fmt.Println("修改前:", p)
	modify(p)
	fmt.Println("直接修改:", p)
	modifyByPointer(&p)
	fmt.Println("通过指针修改:", p)

}
func modify(p Person) {
	p.name = "sy"
	p.age = 100
}
func modifyByPointer(p *Person) {
	p.name = "沈洋"
	p.age = 1000
}

type Person struct {
	name string
	age  int
}

下面这个函数可以看到,结构体变量直接赋值是进行了只拷贝的,p1和p2并没有指向同一片空间

func main() {
	var p1 = Person{name: "sy", age: 12}
	p2 := p1
	p2.name = "沈洋"
	fmt.Println(p1)
	fmt.Println(p2)
}
/**
	输出
	{sy 12}
	{沈洋 12}
*/

创建结构体

// 方法一:直接声明结构体,此时结构体会被初始化,字段全部为默认值
var p Person
// 方法二:在声明变量时设置字段初值
var p Person = Person{name:"sa",age: 10}
// 方法三:通过new函数可以创建结构体变量的指针
var p *Person = new(Person)
// 方法四:创建变量并获取指针
var p*Person = &Person{name: "asa",age: 190}

结构体转换

结构体之间是可以转换的,要求两个结构体之间字段名、字段个数、字段类型完全相同

func main() {
	var p Person
	var u User
	p = Person(u)
    fmt.Println(p)
}

type Person struct {
   name string
   age int
}
type User struct {
   name string
   age  int
}

结构体转JSON字符串

// 这里Person的字段只有是大写的才能被读取,否者不会被转到json中
p := Person{Name: "沈洋", Age: 100}
jsonStr, err := json.Marshal(p)
if err != nil {
   println("转换失败")
}
// 返回的jsonstr是一个byte数组,需要使用string()转成字符串
fmt.Println("结果:", string(jsonStr))

使用Tag来实现字段小写

因为GO中使用字段大小写来区分访问级别,但是Json在转换时会直接转换成大写的字段名,而大部分使用Json采用的是小写字段。Go中提供了Tag方式来实现转JSON字段小写。

底层是通过反射实现的

type Person struct {
   Name string `json:"name"`
   Age  int    `json:"age"`
}

方法

在某些情况下,我们需要声明方法,比如Person结构体:除了有一些字段外(年龄、姓名…)Person结构体还有一些行为比如:说话、跑步…

Golang中的方法是作用在指定的数据类型上的

type Person struct {
   Name string `json:"name"`
   Age  int    `json:"age"`
}

func (p Person) sayHello() {
	println("Hello,my name is ", p.Name)
}
func main() {
	p := Person{Name: "沈洋", Age: 100}
	fmt.Println("结果:", string(jsonStr))
	p.sayHello()
}

方法调用者在调用方法时其实是值拷贝,这一点需要注意。在函数中对其进行修改时不会影响外部的。这一点和方法参数传参相同。使用指针可以影响外部

func (p Person) modify() {
   p.Name = "asdasdas"
}
func (p *Person) modify() {
   p.Name = "asdasdas"
}

不仅自定义的方法

构造函数和Get-Set

Golang的结构体没有构造函数,通常使用工厂模式来解决问题

又因为go中结构体名字必须大写才能在包外被访问,可以使用工厂模式来解决

一个基本的实体类在Golang中的定义如下

package main

type User struct {
   age int
   name string
}
// 构造函数
func NewUser(age int, name string) *User {
   return &User{age: age, name: name}
}
// get
func (u *User) Age() int {
   return u.age
}

// set
func (u *User) SetAge(age int) {
   u.age = age
}

func (u *User) Name() string {
   return u.name
}

func (u *User) SetName(name string) {
   u.name = name
}

面向对象三特性

Golang仍然有面向对象编程的继承、封装、多态的特性,只是实现的方式和其他OOP语言不一样

封装

实现封装的步骤

  • 将结构体、字段的首字母小写
  • 给结构体所在的包提供一个工厂模式的函数、首字母大写。

继承

继承可以解决代码复用,让编程更加靠,近人类思维

当多个结构体存在相同属性和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义属性和方法

其他结构体不需要重新定义这些属性和方法,只需要嵌套一个Student的匿名结构体即可

也就是说在Go中是通过组合的方式实现继承的

type Student struct {
	Name  string
	Age   int
	Score float64
}

func (student Student) testing() {
	println("学生:", student.Name, "  正在考试")
}

// 通过这种方式继承Student类的属性和方法
type Pupil struct {
   Student
}

func (student Pupil) testing() {
	println("小学生:", student.Name, "  正在考试")
}

func main() {
	var p = Pupil{}
	p.Name = "沈洋"
	p.testing()
}

p.Name = "沈洋"就近访问原则A.匿名结构体.方法
A.int = 10

Golang中支持多继承的,但不推荐,

多态-接口

Golang中的多态是通过接口实现的,由于Golang中是依靠组合实现继承的,所以不属于多态

type 接口名 interface{

​ 方法名(参数列表) 返回值列表

​ 方法名(参数列表)返回值列表

}

// Usb 声明一个接口
type Usb interface {
   len() int
   time() int
}

type TypeC struct {
   name  string
   price int
}

func (c TypeC) len() int {
   return c.price * 2
}

func (c *TypeC) time() int {
   return time.Now().Second()
}

在Golang中不需要显示的指定一个结构体具体实现了哪些接口,一个结构体如果包含一个接口中所有方法则视该结构体实现了该接口,Go会根据结构体实现的方法来自动推导该结构体实现了哪些接口

time()

接口之间的继承

接口也可以继承其他多个接口,实现类在实现接口时需要将父接口一并实现

package main

import "fmt"

type AInterface interface {
   Test1()
}
type BInterface interface {
   Test2()
}
type CInterface interface {
   AInterface
   BInterface
   Test()
}
type Student struct {
   Name string
}

func (s Student) Test1() {
   fmt.Println("Test1")
}

func (s Student) Test2() {
   fmt.Println("Test2")
}

func (s Student) Test() {
   s.Test1()
   s.Test2()
}

类型断言

对象.(子类名)
package main

type Point struct {
   x int
   y int
}

func main() {
   var a interface{}
   var b Point
   a = Point{}
   // 向下转型
   b = a.(Point)
   println(b)
}

带检查的类型断言

如果类型转换指定的类型是不是变量的实际类型,则会引发恐慌,实际使用时需要加上对类型的检查。类型转换时其实返回了两个参数 转换后的变量和一个bool值表示是否转换成功

func main() {
   var a interface{}
   a = Point{}
   b, ok := a.(Point)
   if ok {
      fmt.Println("类型转换正常")
      fmt.Println(b)
   } else {
      fmt.Println("类型转换异常,该变量不是Point类型")
   }
}

Switch-Case搭配类型断言

func TypeJudge(data ...interface{}) {
   for _, v := range data {
      switch v.(type) {
      case bool:
         println("该变量是bool类型")
      case int:
         println("该变量是int类型")
      default:
         println("未知数据类型")
      }
   }
}