英文:
Go "polymorphism"
问题
人们说,Go语言不是面向对象(OO)的语言;不要在Go中使用面向对象的术语。好的,让我描述一下我能用面向对象做什么 -
在面向对象的语言中,我可以根据它们的类使不同的动物说不同的话:
cat.Say() // 喵
sheep.Say() // 咩
cow.Say() // 哞
同样,也可以从形状中获取面积。
然而,这个Go演示代码让我相信这是不可能的。下面是作为展示1的代码。
然后今天,我发现了这个Go演示代码,它完全可以实现。下面是作为展示2的代码。
所以我的问题是,两者之间有什么根本的不同,使得第一个是错误的,第二个是正确的?
如何使第一个代码“工作”?
展示1:
// Credits: hutch
// https://groups.google.com/d/msg/golang-nuts/N4MBApd09M8/0ij9yGHK_8EJ
////////////////////////////////////////////////////////////////////////////
/*
https://groups.google.com/d/msg/golang-nuts/N4MBApd09M8/tOO5ZXtwbhYJ
LRN:
子类型多态性:不适用(Go没有子类型)。
虽然如果你将实现接口X的结构体A嵌入到结构体B中,
结构体B将实现接口X,并且可以在期望结构体A的地方使用结构体B。所以,有点类似。
Robert Johnstone:
接口的行为类似于虚函数,但它们并不相同。请参见(以下)hutch的示例程序。
*/
package main
import "fmt"
type A struct {
astring string
}
type B struct {
A
bstring string
}
type Funny interface {
strange()
str() string
}
func (this *A) strange() {
fmt.Printf("my string is %q\n", this.str())
}
func (this *A) str() string {
return this.astring
}
func (this *B) str() string {
return this.bstring
}
func main() {
b := new(B)
b.A.astring = "this is an A string"
b.bstring = "this is a B string"
b.strange()
// 输出:my string is "this is an A string"
// 许多熟悉面向对象(并对Go不熟悉)的人对该程序的输出会感到非常惊讶。
}
展示2:
// Credits: https://play.golang.org/p/Zn7TjiFQik
////////////////////////////////////////////////////////////////////////////
/*
问题(来自Polymorphism-Subtype.go):
https://groups.google.com/d/msg/golang-nuts/N4MBApd09M8/tOO5ZXtwbhYJ
LRN:子类型多态性:不适用(Go没有子类型)。
目标:
这是为了演示在Go中“多态”仍然是可行的。
*/
package main
import (
"fmt"
)
type Shape interface {
Area() float32
}
type Point struct {
x float32
y float32
}
// 确保结构体的大小不同,以确保它适用于各种类型
type Circle struct {
center Point
radius float32
}
func (c Circle) Area() float32 {
return 3.1415 * c.radius * c.radius
}
type Rectangle struct {
ul Point
lr Point
}
func (r Rectangle) Area() float32 {
xDiff := r.lr.x - r.ul.x
yDiff := r.ul.y - r.lr.y
return xDiff * yDiff
}
func main() {
mtDict := make(map[string]Shape)
// 在多类型字典中存储不同的自定义类型没有问题
mtDict["circ"] = Circle{Point{3.0, 3.0}, 2.0}
mtDict["rect"] = Rectangle{Point{2.0, 4.0}, Point{4.0, 2.0}}
for k, v := range mtDict {
fmt.Printf("[%v] [%0.2f]\n", k, v.Area())
}
}
/*
$ go run Polymorphism-Shape.go
[circ] [12.57]
[rect] [4.00]
*/
英文:
People are saying, Go is not an OO (Object Oriented) language; don't use OO terms on Go. OK, let me describe what I am able to do with OO --
With an OO language, I can make different animals say different things based on their classes:
cat.Say() // miao
sheep.Say() // bahh
cow.Say() // moo
The same is getting the Area() from Shapes.
However, this go demo code made me believe that it is impossible. Included below as Exhibit#1.
Then today, I found this go demo code, which makes it entirely possible. Included below as Exhibit#2.
So my question is, what's fundamentally different between the two, that makes the first one wrong and second one correct?
How to make the first one "works"?
Exhibit#1:
// Credits: hutch
// https://groups.google.com/d/msg/golang-nuts/N4MBApd09M8/0ij9yGHK_8EJ
////////////////////////////////////////////////////////////////////////////
/*
https://groups.google.com/d/msg/golang-nuts/N4MBApd09M8/tOO5ZXtwbhYJ
LRN:
Subtype polymorphism: Not applicable (Go doesn't have subtyping).
Although if you embed a struct A implementing interface X into a struct B,
struct B will implement interface X, and can be used instead of struct A in
places where struct A is expected. So, kind of yes.
Robert Johnstone:
interfaces behave similarly to virtual functions, but they are not identical. See the (following) example program by hutch.
*/
package main
import "fmt"
type A struct {
astring string
}
type B struct {
A
bstring string
}
type Funny interface {
strange()
str() string
}
func (this *A) strange() {
fmt.Printf("my string is %q\n", this.str())
}
func (this *A) str() string {
return this.astring
}
func (this *B) str() string {
return this.bstring
}
func main() {
b := new(B)
b.A.astring = "this is an A string"
b.bstring = "this is a B string"
b.strange()
// Output: my string is "this is an A string"
// Many people familiar with OO (and unfamiliar with Go) will be quite
// surprised at the output of that program.
}
Exhibit#2:
// Credits: https://play.golang.org/p/Zn7TjiFQik
////////////////////////////////////////////////////////////////////////////
/*
Problem (From Polymorphism-Subtype.go):
https://groups.google.com/d/msg/golang-nuts/N4MBApd09M8/tOO5ZXtwbhYJ
LRN: Subtype polymorphism: Not applicable (Go doesn't have subtyping).
Goal:
This is to demo that "polymorphism" is still doable in Go.
*/
package main
import (
"fmt"
)
type Shape interface {
Area() float32
}
type Point struct {
x float32
y float32
}
// Make sure the structs are different sizes so we're sure it'll work with
// all sorts of types
type Circle struct {
center Point
radius float32
}
func (c Circle) Area() float32 {
return 3.1415 * c.radius * c.radius
}
type Rectangle struct {
ul Point
lr Point
}
func (r Rectangle) Area() float32 {
xDiff := r.lr.x - r.ul.x
yDiff := r.ul.y - r.lr.y
return xDiff * yDiff
}
func main() {
mtDict := make(map[string]Shape)
// No problem storing different custom types in the multitype dict
mtDict["circ"] = Circle{Point{3.0, 3.0}, 2.0}
mtDict["rect"] = Rectangle{Point{2.0, 4.0}, Point{4.0, 2.0}}
for k, v := range mtDict {
fmt.Printf("[%v] [%0.2f]\n", k, v.Area())
}
}
/*
$ go run Polymorphism-Shape.go
[circ] [12.57]
[rect] [4.00]
*/
答案1
得分: 2
首先,我想讨论一下“不可能”的部分。
import "fmt"
type Animal interface {
Say() string
}
type Cat struct {}
func (cat Cat) Say() string {
return "miao"
}
type Sheep struct {}
func (sheep Sheep) Say() string {
return "bahh"
}
type Cow struct {}
func (cow Cow) Say() string {
return "moo"
}
func main() {
cat := Cat{}
sheep := Sheep{}
cow := Cow{}
fmt.Println(cat.Say())
fmt.Println(sheep.Say())
fmt.Println(cow.Say())
}
这段代码将按照你的预期工作。因此,在“不同的结构对同一方法有不同响应”的方面存在多态性。
Exhibit#1的意图表明,Go所做的实际上类似于Java中的@Overrides之前的类型转换。
只需将以下方法添加到第一个示例中,看看它是如何工作的:
func (this B) strange() {
fmt.Printf("my string is %q\n", this.str())
}
英文:
First of all I would like to discuss the "impossible" part.
import "fmt"
type Animal interface {
Say() string
}
type Cat struct {}
func (cat Cat) Say() string {
return "miao"
}
type Sheep struct {}
func (sheep Sheep) Say() string {
return "bahh"
}
type Cow struct {}
func (cow Cow) Say() string {
return "moo"
}
func main() {
cat := Cat{}
sheep := Sheep{}
cow := Cow{}
fmt.Println(cat.Say())
fmt.Println(sheep.Say())
fmt.Println(cow.Say())
}
This will work exactly as you would expect. So there is a polymorphism in terms of "different structs responding differently to same method".
The intention of Exhibit#1 demonstrates that what Go does is actually similar to Java castings before @Overrides.
Just add the following method to the first example and see how that will work:
func (this B) strange() {
fmt.Printf("my string is %q\n", this.str())
}
答案2
得分: 2
你的两个示例展示了不同的情况。
在第一个示例中,B
在其中嵌入了 A
,而 B
本身并没有实现 strange()
方法,所以当你调用 b.strange()
时,实际上调用的是为 A
定义的 strange()
方法。strange
方法的接收者(this
)是 b.A
,而不是 b
,所以打印的是 b.A.astring
的值。如果你希望 strange
打印 bstring
,你需要为 B
定义 strange
方法。
这指出了 Go 与其他面向对象语言的一个不同之处:将 A
嵌入 B
并不意味着 B
是 A
的“子类”,因此无法将 B
类型的对象用作期望 A
类型对象的地方。然而,由于 B
继承了 A
的字段和方法,任何由 A
实现的接口也同样被 B
实现,除非这些方法是专门为 B
定义的,否则它们操作的是 B
内部的 A
,而不是 B
本身。
在第二个示例中,你有一个 Shape
接口,它由 Circle
和 Rectangle
类型实现。你的映射的元素类型是 Shape
,因此任何实现该接口的类型都可以作为映射的元素。在循环中使用接口类型的值时,你可以调用值在接口中定义的任何方法,并且将调用与值的实际类型相对应的定义。
英文:
Your two exhibits are doing different things.
In the first one, B
has A
embedded in it, and B
doesn't implement the strange()
method itself, so when you call b.strange()
, you get the implementation of strange()
defined for A
. The receiver (this
) of the strange
method is b.A
, not b
, so the value b.A.astring
is printed. If you wanted strange
to print bstring
, you would have to define strange
for B
.
This points out one of the differences between Go and other OO languages: embedding A
within B
does not mean that B
is a "subclass" of A
, so an object of type B
cannot be used where an object of type A
is expected. However, since B
inherits the fields and methods of A
, any interface that's implemented by A
is also implemented by B
, and, unless those methods are defined specifically for B
, they operate on the A
within B
, not B
itself.
In the second exhibit, you have the Shape
interface which is implemented by the types Circle
and Rectangle
. The element type of your map is Shape
, so any type that implements that interface can be an element in the map. When working with a value of an interface type as you are doing in the loop, you can call any method defined in the interface on the value, and the definition corresponding to the actual type of the value will be called.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论