Golang类型转换与类型丢失问题探讨
Golang类型转换与类型丢失问题探讨
package main
import (
"fmt"
)
type IComponent interface {
GetParent() IComponent
SetParent(parent IComponent)
Tick()
}
type IUpdatable interface {
Update()
}
type Component struct {
Name string
Parent IComponent
}
func (c *Component) GetParent() IComponent {
return c.Parent
}
func (c *Component) SetParent(parent IComponent) {
c.Parent = parent
}
func (c *Component) AddChild(child IComponent) {
child.SetParent(c)
//其他操作
}
func (c *Component) Tick() {
fmt.Println("基础 Tick")
}
type ComponentA struct {
Component
}
type ComponentB struct {
Component
}
func (c *ComponentB) Update() {
fmt.Println("ComponentB Update")
}
func (c *ComponentB) Tick() {
fmt.Println("ComponentB Tick")
c.Update()
}
func init() {
}
func RunComponent1() {
fmt.Println("######################RunComponent1##################################")
child := new(ComponentA)
child.Name = "child"
parent := new(ComponentB)
parent.Name = "parent"
parent.AddChild(child)
parent.Tick()
var updater1 IUpdatable = parent
updater1.Update()
a1Parent := child.GetParent()
if a1Parent == parent {
fmt.Println("==")
}
a1Parent.Tick()
updater2, ok := a1Parent.(IUpdatable)
if ok {
updater2.Update()
}
}
func RunComponent2() {
fmt.Println("######################RunComponent2##################################")
child := new(ComponentA)
child.Name = "child"
parent := new(ComponentB)
parent.Name = "parent"
parent.Tick()
child.SetParent(parent)
var updater1 IUpdatable = parent
updater1.Update()
a1Parent := child.GetParent()
if a1Parent == parent {
fmt.Println("==")
}
a1Parent.Tick()
updater2, ok := a1Parent.(IUpdatable)
if ok {
updater2.Update()
}
}
func main() {
RunComponent1()
RunComponent2()
}
更多关于Golang类型转换与类型丢失问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我个人认为这是一个编译器错误,Go编译器应该将Component替换为ComponentB,因为嵌入的目的是为了减少代码复制。
ComponentB 是 IComponent ComponentB 是 IUpdatable ComponentB.AddChild(child)但 child.GetParent() 不是 (IUpdatable, ComponentB) 但 child.SetParent() 和 GetParent 是 IUpdatable 和 ComponentB
我的问题是关于嵌入,
func (c *Component) AddChild(child IComponent)
c 是 Component,ComponentB 已经丢失,没有面向对象的编程语言是这样的
p:=c.(*ComponentB)
崩溃
p:=c.(*IUpdatable)
崩溃
c 只能是 Component 或 IComponent
func (c *Component) AddChild(child IComponent) {
child.SetParent(c)
// 其他代码
}
c 是 ComponentB 吗?但是 child.getParent 不是 ComponentB p:=child.getParent().(*ComponentB)
嵌入可以减少代码复制,而不是继承,我理解这一点 但是 c 不是 ComponentB 吗?为什么
现在,我没有使用嵌入
将 Component 复制到 ComponentB,结果是正确的
type ComponentB struct {
Name string
Parent IComponent
}
func (c *ComponentB) GetParent() IComponent {
return c.Parent
}
func (c *ComponentB) SetParent(parent IComponent) {
c.Parent = parent
}
func (c *ComponentB) AddChild(child IComponent) {
child.SetParent(c)
//其他
}
我的英语不好,但我这几天一直在思考你的代码和建议。
我一直在寻找我能接受的解决方案:
第一种:像“C”语言那样,将 addChild(parent, child) 改为全局函数。
第二种:像你说的那样,不调用 addchild,而是调用 SetParent(parent ParentComponent),这类似于“Qt”的做法。
调用 child.SetParent(parent) {
child.parent = parent
parent.addchild(child)
}
func (c *ComponentChild) SetParent(parent Component) {
fmt.Printf("ComponentChild.SetParent()\n")
c.BaseComponent.SetParent(parent)
parent.addchild(child)
}
这个问题让我很困扰,感谢你的帮助!!!
感谢您做了大量工作,请查看以下“C”代码。
#include <stdio.h>
//父结构体
struct BaseComponent
{
struct BaseComponent * parent
};
void add_child(struct BaseComponent * this,struct BaseComponent * child)
{
//add to map/list
child->parent=this;
}
struct BaseComponent * get_parent(struct BaseComponent * this)
{
return this->parent
}
struct ComponentParent
{
struct BaseComponent base_struct;
int a;
};
struct ComponentChild
{
struct BaseComponent base_struct;
int b;
};
int main(void)
{
struct ComponentParent parent;
struct ComponentChild child;
add_child(&parent,&child);
struct ComponentParent * child_parent=(struct ComponentParent *)get_parent(&child);
if(child_parent==&parent)
{
printf("ok");
}
return 0;
}
我可能会在Go中这样写:
package main
import "fmt"
type Component interface {
Parent() Component
SetParent(parent Component)
}
func AddChild(parent, child Component) {
//add to map/list
child.SetParent(parent)
}
// 父结构体
type BaseComponent struct {
parent Component
}
func (c *BaseComponent) Parent() Component { return c.parent }
func (c *BaseComponent) SetParent(parent Component) { c.parent = parent }
type ComponentParent struct {
BaseComponent
a int
}
type ComponentChild struct {
BaseComponent
b int
}
func main() {
var parent ComponentParent
var child ComponentChild
AddChild(&parent, &child)
var childParent *ComponentParent = child.Parent().(*ComponentParent)
if childParent == &parent {
fmt.Println("ok")
}
var childParent2 = child.Parent()
if childParent2 == &parent {
fmt.Println("also ok")
}
}
很好的例子。
我在末尾添加了这段代码:
fmt.Printf("%v\n", child.parent == &parent)
fmt.Printf("%v\n", child.BaseComponent.parent == &parent)
这表明 ComponentChild “继承”了 BaseComponent 的 parent 字段,因此可以像这样使用:child.parent,它等同于 child.BaseComponent.parent。
我还添加了这段代码,它仍然可以正常工作:
func (c *ComponentChild) SetParent(parent Component) {
fmt.Printf("ComponentChild.SetParent()\n")
c.BaseComponent.SetParent(parent)
}
ComponentParent 和 ComponentChild 从 BaseComponent “继承”了方法(Parent()、SetParent())。如果 ComponentParent 定义了自己的 SetParent() 方法,那么当 *ComponentParent 作为 Component 接口类型传递时,将使用它自己的方法。
请注意,Go 语言没有像 Java 或 C++ 等其他语言中定义的“虚”方法或“抽象”方法。在 Go 语言中,等效的做法是定义并使用一个显式的接口(在本例中是 Component)。你可以在需要接口类型的地方使用结构体指针,并且可以将 interface{} 类型转换回其关联的结构体指针,但不能将一个结构体指针转换为另一个结构体指针。Go 将多态性(接口类型)与继承(在一个结构体中嵌入另一个结构体)分离开来。我认为这是一件好事。
看起来你试图在Go中使用继承,但Go没有继承;只有嵌入。
当你这样写:
type ComponentB struct {
Component
}
ComponentB 并没有“扩展” Component。ComponentB 包装了 Component。
在这一行:
parent := new(ComponentB)
parent.Name = "parent"
parent.AddChild(child) // <<<
parent.Tick()
其实现是这样的:
// 注意这个 ↓ 是 `Component`,不是 `ComponentB`
func (c *Component) AddChild(child IComponent) {
child.SetParent(c)
// 其他代码
}
所以当你调用 parent.AddChild(child) 时,它实际上是 parent.Component.AddChild(child) 的语法糖,然后会将 parent.Component 作为 child 的父级添加;而不是 parent。
正确的解决方案取决于上下文。在你的例子中,我没有看到 AddChild 的目的是什么。
看起来直接调用 child.SetParent(parent) 并去掉 AddChild 是合理的。如果你实际代码中的 AddChild 除了调用 SetParent 之外还做了其他事情,你能展示一下那段代码吗?如果它依赖于父级的实现,那么你可能需要在父级上实现 AddChild。如果所有组件实现的代码都相同,那么就把它做成一个顶层函数:
func AddChild(parent, child IComponent) {
// 你想要的任何公共初始化
child.SetParent(parent)
// 也许这里还有额外的代码?
}
附注:在我看来,你试图用Go的语法来写C#代码,但它们是两种不同的语言,通常不能逐行翻译,就像C#和JavaScript或TypeScript等一样……
我建议查看 Go语言之旅 - 方法和接口。前面的一些幻灯片你可以快速浏览,但我建议从 幻灯片 #9 开始阅读关于接口的部分,以了解它们与C#的不同之处。
这不是编译器错误。Go 语言是故意这样设计的。
dqpmchtx: c 是 Component,ComponentB 丢失了,没有面向对象的编程语言是这样的
有些人说 Go 不是面向对象的语言。我个人认为它是面向对象的,但它没有继承。与你的 Go 代码等价的 C# 代码(减去 RunComponent2 的实现)是这样的:
C# 代码
using System;
RunComponent1();
//RunComponent2();
public interface IComponent
{
IComponent GetParent();
void SetParent(IComponent parent);
void Tick();
}
public interface IUpdatable
{
void Update();
}
public class Component : IComponent
{
public string Name;
public IComponent Parent;
public IComponent GetParent() => Parent;
public void SetParent(IComponent parent) => this.Parent = parent;
public void AddChild(IComponent child) => child.SetParent(this);
public void Tick() => Console.WriteLine("base Tick");
}
public class ComponentA : IComponent
{
public readonly Component Component = new Component();
public string Name { get => Component.Name; set => Component.Name = value; }
public IComponent Parent { get => Component.Parent; set => Component.Parent = value; }
public IComponent GetParent() => Component.GetParent();
public void SetParent(IComponent parent) => Component.SetParent(parent);
public void AddChild(IComponent child) => Component.AddChild(child);
public void Tick() => Component.Tick();
}
public class ComponentB : IComponent, IUpdatable
{
public readonly Component Component = new Component();
public string Name { get => Component.Name; set => Component.Name = value; }
public IComponent Parent { get => Component.Parent; set => Component.Parent = value; }
public IComponent GetParent() => Component.GetParent();
public void SetParent(IComponent parent) => Component.SetParent(parent);
public void AddChild(IComponent child) => Component.AddChild(child);
public void Update() => Console.WriteLine("ComponentB Update");
public void Tick()
{
Console.WriteLine("ComponentB Tick");
Update();
}
}
void RunComponent1()
{
Console.WriteLine("#####################RunComponent1##################################");
var child = new ComponentA();
child.Name = "child";
var parent = new ComponentB();
parent.Name = "parent";
parent.AddChild(child);
parent.Tick();
IUpdatable updater1 = parent;
updater1.Update();
var a1Parent = child.GetParent();
if (a1Parent == parent) {
Console.WriteLine("==");
}
a1Parent.Tick();
if (a1Parent is IUpdatable updater2)
{
updater2.Update();
}
}
/*
func RunComponent2() {
fmt.Println(“#####################RunComponent2##################################”)
child := new(ComponentA)
child.Name = “child”
parent := new(ComponentB)
parent.Name = "parent"
parent.Tick()
child.SetParent(parent)
var updater1 IUpdatable = parent
updater1.Update()
a1Parent := child.GetParent()
if a1Parent == parent {
fmt.Println("==")
}
a1Parent.Tick()
updater2, ok := a1Parent.(IUpdatable)
if ok {
updater2.Update()
}
}
*/
请注意我写的是:
public class ComponentA
{
public readonly Component Component = new Component();
}
而不是
public class ComponentA : Component { }
因为 Go 没有继承。
这是一个典型的Go语言接口类型转换问题。问题出现在RunComponent1()中,当通过AddChild方法设置父组件后,子组件存储的父组件类型信息丢失了。
问题分析
在RunComponent1()中:
parent := new(ComponentB) // 实际类型是 *ComponentB
parent.AddChild(child) // 这里将 parent 作为 IComponent 接口传递
在AddChild方法中:
func (c *Component) AddChild(child IComponent) {
child.SetParent(c) // c 是 *Component 类型,不是 *ComponentB
// 其他操作
}
这里发生了类型丢失:*ComponentB被转换为IComponent接口,然后存储在子组件的Parent字段中,丢失了*ComponentB特有的方法。
解决方案
方案1:使用类型断言恢复类型
func RunComponent1() {
fmt.Println("######################RunComponent1##################################")
child := new(ComponentA)
child.Name = "child"
parent := new(ComponentB)
parent.Name = "parent"
parent.AddChild(child)
parent.Tick()
var updater1 IUpdatable = parent
updater1.Update()
a1Parent := child.GetParent()
if a1Parent == parent {
fmt.Println("==")
}
// 尝试恢复为 *ComponentB
if compB, ok := a1Parent.(*ComponentB); ok {
compB.Tick() // 现在可以调用 ComponentB 的 Tick 方法
if updater, ok := compB.(IUpdatable); ok {
updater.Update()
}
} else {
a1Parent.Tick() // 只能调用基础的 Tick 方法
}
}
方案2:修改 AddChild 方法签名
func (c *ComponentB) AddChild(child IComponent) {
child.SetParent(c) // 这里 c 仍然是 *ComponentB 类型
// 其他操作
}
// 或者使用泛型(Go 1.18+)
type Component[T any] struct {
Name string
Parent T
}
func (c *Component[T]) SetParent(parent T) {
c.Parent = parent
}
方案3:使用组合模式保持类型信息
type TypedComponent struct {
Component
ConcreteParent interface{}
}
func (c *Component) AddChildWithType(child IComponent, parent interface{}) {
child.SetParent(c)
if typed, ok := child.(interface{ SetConcreteParent(interface{}) }); ok {
typed.SetConcreteParent(parent)
}
}
type ComponentA struct {
Component
ConcreteParent interface{}
}
func (c *ComponentA) SetConcreteParent(parent interface{}) {
c.ConcreteParent = parent
}
运行结果分析
当前代码的输出会是:
######################RunComponent1##################################
ComponentB Tick
ComponentB Update
ComponentB Update
==
基础 Tick # 这里调用了 Component 的 Tick,不是 ComponentB 的 Tick
######################RunComponent2##################################
ComponentB Tick
ComponentB Update
ComponentB Update
==
基础 Tick # 同样的问题
这是因为通过接口存储后,类型信息被擦除,只能调用接口定义的方法。要调用具体类型的方法,必须通过类型断言恢复原始类型。

