Golang中继承和方法参数的使用探讨

Golang中继承和方法参数的使用探讨 你好, 我是Go语言的新手,正在尝试将其用于一个关于知识图谱的作业。

我的实现方式如下:

  • 我有一个名为Node的类,它包含名称和命名关系。它不应被直接使用。
  • 我想要一个Concept类和一个Instance类,它们都“继承”Node。 ……这就是我遇到问题的地方。

因为在Go语言中,Concept不是一个Node,所以我在Node上的AddRelationship方法无法对Concept起作用。

我最初的想法是使用接口,但我有点困惑,而且我尝试的方法似乎不起作用。

那么,以下是我的“类”Node,它是可以工作的:

package main

import "fmt"

// 知识图谱意义上的节点。不应直接使用。
type Node struct {
	name          string
	relationships map[string][]*Node
}

// 节点的构造函数
func NewNode(name string) *Node {
	return &Node{
		name:          name,
        // 关系具有名称
		relationships: make(map[string][]*Node),
	}
}

func (n *Node) GetName() string {
	return n.name
}

// 向节点添加关系
func (n *Node) AddRelationship(name string, node *Node) {
	n.relationships[name] = append(n.relationships[name], node)
}

// 按名称查找关系
func (n *Node) Find(name string) []*Node {
	return n.relationships[name]
}

// 打印节点及其关系
func (n *Node) Print() {
	fmt.Println("[", n.name, "]")
	for relationName, nodes := range n.relationships {
		for _, node := range nodes {
			fmt.Println(" └─(", relationName, ")->[", node.GetName(), "]")
		}
	}
}

简单示例:

package main

func main() {
	node := NewNode("Paul")
	node.AddRelationship("is-a", NewNode("Humain"))
	node.Print()
}
$ go run ./main.go ./node.go                                                                                                                    
[ Paul ]
 └─( is-a )->[ Humain ]

以下是我尝试为Concept类所做的:

定义一个接口

type INode interface {
	AddRelationship(name string, node *INode)
	Find(name string) []INode
	GetName() string
	Print()
}

并将之前代码中的每个参数和输出都替换为INode(而不是*Node)。 但即使我实现了所有方法,Concept也没有被识别为INode

package main

// 概念是知识图谱意义上的节点。
type Concept struct {
	Node
}

func NewConcept(name string) *Concept {
	return &Concept{
		Node: *NewNode(name),
	}
}

func (c *Concept) GetName() string {
	return c.Node.GetName()
}

// 我读到在参数中,*INode和INode的行为是相同的,因为它是接口?
func (c *Concept) AddRelationship(name string, node INode) {
	c.Node.AddRelationship(name, node)
}

func (c *Concept) Find(name string) []INode {
	return c.Node.Find(name)
}

func (c *Concept) Print() {
	c.Node.Print()
}

那么,在Go的继承系统中,我遗漏了什么呢?


更多关于Golang中继承和方法参数的使用探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

非常感谢您花费的时间!

我想我明白了。

更多关于Golang中继承和方法参数的使用探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


如何指定我的 map[string][]INode 变量包含的是指向变量的指针,而不是变量本身?

据我所知,这是无法指定的。

在我的 main.go 中(见下文),AddRelationship 的第二个参数是一个指向 Concept 的指针。但我从未指定 AddRelationship 应该接收一个指向值的指针还是一个值本身,对吗?

你没有指定。但由于满足 INode 接口所需的方法是在指针接收器上定义的,这意味着只有指向 Concept 的指针 (*Concept) 才能满足 INode 接口。

如果这有点令人困惑,请看这个例子:Go Playground - The Go Programming Language Given 结构体的方法是在 *Given 上定义的。因此,只有指向 Given 的指针才拥有这些方法。 当我将一个空的 Given 结构体 x 传递给接受 Sample 接口的 checkSample() 函数时,它会失败。将其改为指针 (x := &Given{}) 就能使其正常工作。

首先,Concept 不满足 INode 接口,因为

AddRelationship(name string, node INode)

AddRelationship(name string, node *INode)

是不同的。

INode 接口要求 AddRelationship() 的第二个参数是指向满足 INode 接口的类型的指针。而 Concept 的 AddRelationship() 方法的第二个参数要求任何可以满足 INode 接口的东西(可以是值,也可以是指向值的指针)。

关于为什么这两个参数不同的进一步解释:T 和 *T 可以有不同的方法集

以及为什么指向接口的指针(如 *INode)应该很少使用:何时应该使用指向接口的指针?

其次,看以下代码:

func (n *Node) AddRelationship(name string, node *Node) {
	n.relationships[name] = append(n.relationships[name], node)
}

期望的第二个参数是指向特定类型 (*Node) 的指针。

… 然后在:

func (c *Concept) AddRelationship(name string, node INode) {
	c.Node.AddRelationship(name, node)
}

第二个参数是任何满足 INode 接口的东西(任何类型或指向任何类型的指针)。然而,它被传递给 c.Node.AddRelationship(name, node),我们知道这需要是具体的 *Node。因此,例如,我有另一个满足 INode 接口的类型 Foo,Concept.AddRelationship() 可以接受它,但 Node.AddRelationship 将无法处理。 你需要使用类型断言来检查接收到的第二个参数是否是 *Node。

非常感谢你的帮助,我现在已经让代码运行起来了!(至少目前是这样)

为什么应该很少使用指向接口的指针(例如 *INode):何时应该使用指向接口的指针?

我认为那是我最大的错误。我现在在所有地方都使用 INode 作为参数(见下面的代码)。但有些地方我不太明白:

  • 如何指定我的 map[string][]INode 变量包含的是指向变量的指针,而不是变量本身?
  • 在我的 main.go(见下文)中,AddRelationship 的第二个参数是一个指向 Concept 的指针。但我从未指定 AddRelationship 应该接收一个指向值的指针还是一个值本身,我指定过吗?

node.go

package main

import "fmt"

type INode interface {
	AddRelationship(name string, node INode)
	Find(name string) []INode
	GetName() string
	Print()
}

// a node in the sense of a knowledge graph. Shouldn't be used directly.
type Node struct {
	name          string
	relationships map[string][]INode
}

// Constructor of node
func NewNode(name string) *Node {
	return &Node{
		name:          name,
		relationships: make(map[string][]INode),
	}
}

func (n *Node) GetName() string {
	return n.name
}

// Add a relationship to the node
func (n *Node) AddRelationship(name string, node INode) {
	n.relationships[name] = append(n.relationships[name], node)
}

// Find relationships by name
func (n *Node) Find(name string) []INode {
	return n.relationships[name]
}

// Print the node and its relationships
func (n *Node) Print() {
	fmt.Println("[", n.name, "]")
	for relationName, nodes := range n.relationships {
		for _, node := range nodes {
			fmt.Println(" └─(", relationName, ")->[", node.GetName(), "]")
		}
	}
}

concept.go

package main

// A concept is a node in the sense of a knowledge graph.
type Concept struct {
	Node
}

func NewConcept(name string) *Concept {
	return &Concept{
		Node: *NewNode(name),
	}
}

func (c *Concept) GetName() string {
	return c.Node.GetName()
}

func (c *Concept) AddRelationship(name string, node INode) {
	c.Node.AddRelationship(name, node)
}

func (c *Concept) Find(name string) []INode {
	return c.Node.Find(name)
}

func (c *Concept) Print() {
	c.Node.Print()
}

main.go

package main

func main() {
	human := NewConcept("Human")
	woman := NewConcept("Woman")
	woman.AddRelationship("is-a", human)
	woman.Print()
	juliette := NewNode("Juliette")
	juliette.AddRelationship("is-a", woman)
	juliette.Print()
}

stdout

[ Woman ]
 └─( is-a )->[ Human ]
[ Juliette ]
 └─( is-a )->[ Woman ]

在Go中实现这种"继承"关系,正确的方式是使用组合和接口。你的主要问题在于类型转换和方法签名不匹配。以下是修正后的实现:

package main

import "fmt"

// 定义接口
type INode interface {
	GetName() string
	AddRelationship(name string, node INode)
	Find(name string) []INode
	Print()
}

// Node结构体实现INode接口
type Node struct {
	name          string
	relationships map[string][]INode
}

func NewNode(name string) *Node {
	return &Node{
		name:          name,
		relationships: make(map[string][]INode),
	}
}

func (n *Node) GetName() string {
	return n.name
}

func (n *Node) AddRelationship(name string, node INode) {
	n.relationships[name] = append(n.relationships[name], node)
}

func (n *Node) Find(name string) []INode {
	return n.relationships[name]
}

func (n *Node) Print() {
	fmt.Println("[", n.name, "]")
	for relationName, nodes := range n.relationships {
		for _, node := range nodes {
			fmt.Println(" └─(", relationName, ")->[", node.GetName(), "]")
		}
	}
}

// Concept通过嵌入Node自动实现INode接口
type Concept struct {
	*Node
}

func NewConcept(name string) *Concept {
	return &Concept{
		Node: NewNode(name),
	}
}

// Instance通过嵌入Node自动实现INode接口
type Instance struct {
	*Node
	instanceOf *Concept
}

func NewInstance(name string, concept *Concept) *Instance {
	inst := &Instance{
		Node:       NewNode(name),
		instanceOf: concept,
	}
	// 自动添加"is-a"关系到所属概念
	inst.AddRelationship("is-a", concept)
	return inst
}

func (i *Instance) GetConcept() *Concept {
	return i.instanceOf
}

使用示例:

package main

func main() {
	// 创建概念
	animal := NewConcept("Animal")
	human := NewConcept("Human")
	
	// 添加概念间的关系
	human.AddRelationship("subclass-of", animal)
	
	// 创建实例
	paul := NewInstance("Paul", human)
	john := NewInstance("John", human)
	
	// 添加实例间的关系
	paul.AddRelationship("friend-of", john)
	
	// 打印结果
	fmt.Println("=== Concepts ===")
	animal.Print()
	human.Print()
	
	fmt.Println("\n=== Instances ===")
	paul.Print()
	john.Print()
	
	// 类型断言示例
	if node, ok := paul.(INode); ok {
		fmt.Printf("\nPaul作为INode: %v\n", node.GetName())
	}
}

关键点说明:

  1. 接口定义INode接口应该包含所有需要的方法签名
  2. 组合而非继承ConceptInstance通过嵌入*Node来获得其所有方法
  3. 接口一致性Node的方法参数和返回值都使用INode接口类型
  4. 自动接口实现:嵌入*Node后,ConceptInstance自动实现了INode接口

运行结果:

=== Concepts ===
[ Animal ]
[ Human ]
 └─( subclass-of )->[ Animal ]

=== Instances ===
[ Paul ]
 └─( is-a )->[ Human ]
 └─( friend-of )->[ John ]
[ John ]
 └─( is-a )->[ Human ]

Paul作为INode: Paul

这种设计允许你:

  • ConceptInstance作为INode传递
  • 保持类型安全
  • 扩展特定类型的功能(如Instance添加了instanceOf字段)
  • 符合Go语言的组合哲学
回到顶部