当前位置: 首页 > article >正文

Go父类调用子类方法(虚函数调用)

前言

在Go语言中,支持组合而不是继承。网上都说可以通过接口和结构体内嵌来模拟面向对象编程中的子类和父类关系。但给的例子或写法感觉都不是很好,难以达到我的目的(比如通过模板模式实现代码的重用等)。因此调查了一下实现方式。

结论

  • 可以通过定义 interface + 组合接口变量 + NewXxx 函数中对接口变量赋值(类似于给 this 变量赋值) 的方式实现 Go 的继承和虚函数调用。废话少说, 直接上代码。
// 定义一个表示形状的 Shape 接口
type Shape interface {
	Name() string
	Area() float64      //求面积
	Perimeter() float64 //求周长
	String() string
}

// 定义实现了部分业务逻辑的抽象基类
type BaseShape struct {
	Shape // 组合了接口,因此有了其接口的方法声明, 注意: 其值为 nil, 需要在子类的 NewXxx 中对其赋值(相当于设置为 this)
	name  string
}

func (bs BaseShape) Name() string {
	return bs.name
}

func (bs BaseShape) AbstractMethod() string {
	return "base"
}

// 此处在 BaseShape 的 String() 方法中调用子类会重载的 Area()/Perimeter() 方法
func (bs BaseShape) String() string {
	if bs.Shape == nil {
	    // 由于需要手动设置 Shape 的值(不像 C++ 可以在 new 时自动设置), 为了在 nil panic 的时候有更好的提示, 此处主动 panic,提醒这种编码错误
		panic(fmt.Sprintf("must set Shape in child struct 's new function, name=%s", bs.name))
	}
	//这里既可以通过 bs.Xxx() 调用, 也可以通过 bs.Shape.Xxx() 调用.
	return fmt.Sprintf("name=%s: absMethod=%s, area=%f, perimeter=%f",
		bs.Name(), bs.AbstractMethod(), bs.Shape.Area(), bs.Shape.Perimeter())
}

type Rect struct {
	BaseShape
	Width  float64
	Height float64
}

func (r *Rect) Area() float64 {
	return r.Width * r.Height
}
func (r *Rect) Perimeter() float64 {
	return 2 * (r.Width + r.Height)
}

func (r *Rect) AbstractMethod() string {
	return "abs_rect"
}

func NewRect(width, height float64) *Rect {
	rect := &Rect{
		BaseShape: BaseShape{name: "rect"},
		Width:     width,
		Height:    height,
	}
	rect.Shape = rect // 将 BaseShape 中的 Shape 变量设置为 this,父类才可以调用
	return rect
}

type Square struct {
	Rect
}

func NewSquare(sides float64) *Square {
	square := &Square{Rect{
		BaseShape: BaseShape{name: "square"},
		Height:    sides,
		Width:     sides,
	}}
	square.Shape = square
	return square
}
type Circle struct {
	BaseShape
	Radius float64
}

func (c *Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}
func (c *Circle) Perimeter() float64 {
	return 2 * math.Pi * c.Radius
}

func (c *Circle) AbstractMethod() string {
	return "abs_circle"
}

func NewCircle(radius float64) *Circle {
	circle := &Circle{
		BaseShape: BaseShape{name: "circle"},
		Radius:    radius,
	}
	circle.Shape = circle //将 BaseShape 中的 Shape 变量设置为 this,父类才可以调用
	return circle
}

以上代码实现了如下的继承结构,可以将 String() 方法看成业务方法,其中调用子类提供实现的抽象方法。
在这里插入图片描述

测试代码

通过 shape 变量的 String() 方法调用了子类的 Name()/Area()/Perimeter() 等方法.

func TestVirtualFunctionCall(t *testing.T) {
	var rect Shape = NewRect(10, 20)
	var circle Shape = NewCircle(10)
	var square Shape = NewSquare(10)

	assert.Equal(t, "name=rect: absMethod=base, area=200.000000, perimeter=60.000000", rect.String())
	assert.Equal(t, "name=square: absMethod=base, area=100.000000, perimeter=40.000000", square.String())
	assert.Equal(t, "name=circle: absMethod=base, area=314.159265, perimeter=62.831853", circle.String())
}

补充说明

  • 通过这种方法,可以成功的在 Go 语言中实现 多态,虚函数 等 C++/Java 才有的概念,从而可以想办法实现各种设计模式,大大提高项目质量。
  • 但也存在一些限制,比如:
    • 无法在 BaseShape 中再次定义一些虚函数由子类实现并调用(参考 AbstractMethod),如想要其也是虚函数的样子,必须也定义到 interface 中.

http://www.kler.cn/news/288476.html

相关文章:

  • stm32之I2C通信外设
  • 提升RAG检索回答质量: Shortwave的 4 大优化指南
  • 使用 Milvus Lite、Llama3 和 LlamaIndex 搭建 RAG 应用
  • 住宅IP与机房IP:哪种更适合业务应用?
  • 51单片机-第十节-独立按键及数码管优化
  • shell脚本—————局域网IP扫描
  • 开放式耳机漏音有多大?五大超值爆款推荐!
  • 【数据可视化技术】使用Matplotlib、Seaborn进行数据可视化
  • 9、Django Admin优化查询
  • 【2025考研英语高分写作:写作核心词汇】三、人生哲理类
  • Django中的第一个自动化测试编写
  • VS Code CMake tool 配置
  • Apple LLM: 智能基础语言模型(AFM)
  • Apache Struts2开发模式漏洞解析与修复
  • 温湿度传感器和IOT网关的连接 485接口
  • 嵌入式OTG硬件电路分析
  • 矩阵性质简介
  • 51单片机波特率的计算方法
  • kafka单条消息过大发送失败
  • CTF Show逆向4reserve wp--mingyue
  • 滑动窗口系列(不定长滑动窗口长度) 9/2
  • 09--kubernetes持久化存储和StatefulSet控制器
  • Ubuntu最新镜像下载,国内镜像源地址大全
  • RocketMQ集群搭建,及RocketMQ-Dashboard部署(前RocketMQ-Console)
  • vscode远程连接服务器并根据项目配置setting.json
  • 四、基本电路设计笔记——4.1 DC-DC稳压电路
  • 【Python123题库】#研究生录取数据分析A #图书数据分析(A)
  • 【算法每日一练及解题思路】判断数字是否为偶数
  • Vue实现步骤条(el-step)+Popover弹出框
  • Oracle 网络安全产品安全认证检索