Go 抽象特性

本贴最后更新于 1203 天前,其中的信息可能已经沧海桑田

一个有用的程序,往往是对真实世界抽象的描述,描述一个事物的属性,描述一个行为的功能流程。

结构体

Go 语言并不是传统意义上的 面向对象语言,它没有像 Java 中的 class,而是使用 struct 来对真实世界的事物进行抽象。struct 之间没有继承,而是使用 组合 来达到类型 Java 继承的效果。

Java

// 在Java中使用class类进行一类事物的抽象
public class Person {
  String name;
  Integer age;

  public Person(String name, Integer age) {
    this.name = name;
    this.age = age;
  }
}

// 使用exetds关键字来表示类与类之间的继承关系 --- 即父子关系
public class Student extends Person {
  Float grade;
  
  public Student(String name, Integer age, Float grade) {
    this.grade = grade;
    super(name, age);
  }
}

public class Main {
  public static void main(String[] agrs) {
    Student stu = new Student("feiwo", 18, 90.8);
    System.out.println(stu.name) // out: feiwo
  }
}

Go

// 使用struct来对一类事物抽象的描述
type Person struct {
	Name string
	Age  int
}

type Student struct {
	Person // 使用组合的方式,复用Person struct来达到继承类似的效果
	Grade  float32
}

func main() {
	// Go和Java不同,没有构造函数,一般会采用字面量的形式初始化struct或自己创建一个函数用来初始化一个struct
	stu := Student{
		Person: {
			Name: "feiwo",
			Age:  18,
		},
		Grade: 90.8,
	}

	fmt.Println(stu.Name) // out: feiwo
}

方法定义

Go 的方法定义,与其他面向对象语言不同,一般的,面向对象语言的方法都是定义在类中的,而 Go 采用的是 struct,且只能在 struct 中定义字段,而 struct 的方法则是采用函数挂载的形式进行结构体与方法的绑定,匿名组合的 struct 方法是会传递类被嵌入的 struct,如果有同名的方法,则会调用被嵌入的 struct 所绑定的方法。

Java

public class Person {
  String name;
  
  public Person(String name) {
    this.name = name;
  }
  // Java方法定义在类代码块中
  public void say() {
    System.out.println("hello "+ this.name);
  }
}

public class Main {
  
  public static void main(String[] args) {
    Person p = new Person("feiwo");
    p.say(); // out: hello feiwo
  }
  
}

Go

type Person struct {
	Name string
}

// go使用struct挂载函数的方式,将函数绑定到struct上
// 挂载在struct指针类型上
// 这里的p是Person结构体的指针,是Person实例中内存的地址
// 在这个方法里面修改Person属性的值,是会影响Person实例的属性值
func (p *Person) Say() {
	fmt.Println("hello " + p.Name)
}

// 也可以挂载在struct类型
// 因为go里面所有参数都是只拷贝,这里的p变量,是Person结构体的拷贝
// 在这个方法里面修改Person结构体里的属性,并不会影响实例属性的值
func (p Person) Sleep() {

}

func main() {
	p := Person{
		Name: "feiwo",
	}

	p.Say() // out: hello feiwo
}

方法变量

Gostruct 方法是可以赋值给一个变量的,这个变量就是 方法值(Method Value),方法值其实是一个带有闭包的函数变量,接收值被隐式地绑定到方法值的闭包环境中,后续调用不需要再显式地传递调用者。

type Person struct {
	Name string
}

func (p *Person) Say() {
	fmt.Println("hello " + p.Name)
}

func main() {
	p := &Person{Name: "feiwo"}

	f := p.Say

	f() // out: hello feiwo
}

接口

接口是一种编程规约,也是一组方法签名的集合。在 Java 中,我们通常需要面向接口编程,这样更加抽象,而不至于依赖某个具体的实现类,减少类与类之间的耦合,采用接口解耦,只要一个 实现了这个接口,就可以将原先的 实现类 替换掉。

Java

public interface Reader {
  int Read(byte[] content)
}

public class FileRead implements Reader {
  
  @Overrid
  public int Read(byte[] content) {
    // TODO
  }
}

public class NetRead implements Reader {
  
  @Overrid
  public int Read(byte[] content) {
    // TODO
  }
}

public class Main {
  public static void main(String[] args) {
  
    FileRead f = new FileRead();
    R(f);
  
    NetRead n = new NetRead();
    R(n)
  
  }
  
  // 将接口类型作为参数,达到解耦具体的类型实例
  // 只要类实现了Reader的接口,都可作为参数传入
  public static void R(Reader reader) {
    // TODO
  }
}

Go

Go 中,接口变量没有初始化时,默认值为 nilnil 这个关键字类似于 Java 语言中的 null,接口变量也只有在初始化之后采用使用的意义。

// 使用interface关键字来定义接口
type Reader interface {
  Read(p byte[]) (int, error)
}

type Writer interface {
  Write()
}

// 和struct一样,接口也支持组合,将多个接口组合成一个接口
// Go官方鼓励使用小接口定义,然后组合成大接口,而不是定一个大而全的接口
type ReadWriter interface {
  Reader
  Writer
}

type FileRead struct {
  
}

// 只要方法名签名和接口中的方法签名一样
// 并且接口实现了接口中全部的方法,则表示这个结构体实现了这个接口
func (f *FileRead) Read(p []byte) (int, error) {
  // TODO
}

type NetRead struct {
  
}

func (n *NetRead) Read(p []byte) (int, error) {
  // TODO
}

func main() {
  fr := new(FileRead)
  nr := new(NetRead)
  
  R(fr)
  R(nr)
}

// 只要实现了Reader接口的结构体指针都可以作为参数传入
func R(r Reader) {
  // TODO
}

空接口

因为 Go 不是一个纯的面向对象语言,所以没有最终的父类,也就是 Object,也没有泛型,在 Go 中则使用空接口(interface{})来表示一个任意类型,由于空接口的方法集为空,所以任意类型都可以认为实现了这个 空接口,任意类型的实例都可赋值或传递给空接口。通常空接口,搭配类型断言来使用。

func Response(data interface{}) interface{} {
	switch data.(type) { // 类型断言
	case NoErrorData:
		return buildData(data)
	case PageData:
		return buildPageData(data)
	case ErrorData:
		return buildErrorData(data)
	}
}

接口的优点

  1. 解耦: 复杂系统进行垂直和水平的分割是常用的设计手段,在层与层之间使用接口进行抽象和解耦是一种很好的编程策略
  2. 实现泛型:使用空接口可以一定层度上解决 Go 没有泛型的窘境

接口内部的实现

非空接口的数据结构

type iface struct {
	tab  *itab          // itab 是存放类型及方法指针信息,主要是自身类型和绑定的实例类型以及实例相关的函数指针
	data unsafe.Pointer // 数据信息, 指向的是接口绑定的实例的副本,接口的初始化也是一种值拷贝
}

// itab存放.rodata只读存储段中,存放在静态分布的存储空间中,不受GC的限制,其内存不会被回收
type itab struct {
	inter *interfacetype // 接口自身的静态类型
	_type *_type         // _type就是接口存放的具体实例的类型(动态类型),iface里的data就是指向该类型的值,一个是类型信息,一个是类型的值
	hash  uint32         // 存放具体类型的Hash值
	_     [4]byte        // 内存对齐
	fun   [1]uintptr     // 函数指针,可以理解为C++中的虚拟函数指针,提供一种动态绑定的机制,这里的数组大小不是固定的,编译器负责填充
}

type _type struct {
	size       uintptr  // 大小
	ptrdata    uintptr  // size of memory prefix holding all pointers
	hash       uint32   // 类型的Hash
	tflag      tflag    // 类型的特征标记
	align      uint8    // _type作为整体变量存放时的对齐字节数
	fieldalign uint8    // 当前结构字段的对齐字节数
	kind       uint8    // 基础类型枚举值和反射中的Kind一直,Kind决定了如何解析该类型
	alg        *typeAlg // 指向一个函数指针表,该表有两个函数,一个是计算类型Hash函数,一个是比较两个类型是否相同的equle函数
	// gcdata stores the GC type data for the garbage collector.
	// If the KindGCProg bit is set in kind, gcdata is a GC program.
	// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
	gcdata    *byte   // GC相关信息
	str       nameOff // 用来表示类型名称字符串在编译后二进制文件中的某个section的偏移量,由链接器负责填充
	ptrToThis typeOff // 表示类型元信息的指针,在编译后二进制文件中某个scetion的偏移量,由链接器负责填充
}

// 描述接口的类型
type interfacetype struct {
	typ     _type     // 类型信息
	pkgpath name      // 接口所属包的名字信息,name内存存放的不仅有名称还有描述信息
	mhdr    []imethod // 接口的方法元信息集合
}

// 接口方法元信息
type imethod struct {
	name nameOff // 方法名在编译后的secetion里面的偏移量
	ityp typeOff // 方法类型在编译后在secetion里面的偏移量
}

接口的动态调用

接口调用分为两个阶段:

  1. 第一阶段是构建 iface 动态数据结构,这个阶段是在接口实例化的时候完成。fr := new(FileRead)
  2. 第二阶段是通过函数指针间接调用接口绑定的实例方法的过程。fr.Read()
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    492 引用 • 1383 回帖 • 375 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...