xpt
xpt

Reputation: 22984

Go "polymorphism"

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]

*/

Upvotes: 2

Views: 354

Answers (2)

Andy Schweig
Andy Schweig

Reputation: 6749

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.

Upvotes: 2

Alexey Soshin
Alexey Soshin

Reputation: 17691

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())
}

Upvotes: 2

Related Questions