0

I have two types, B and C, which share all methods, but implement one of them differently. I would like to express this by having a parent type A, containing the implementations of the shared methods, and embedding A in B and C. (Don't Repeat Yourself!) The snag is that the method that differs between B and C is called in many of the shared methods. What's the idiomatic way to structure such code?

My current implementation looks essentially like this (https://play.golang.org/p/RAvH_hBFDN; based on an example by Dave Cheney):

package main

import (
    "fmt"
)

type Legger interface {
    Legs() int
}

type Cat struct {
    Name string
    L    Legger
}

// Cat has many methods like this, involving calling c.L.Legs()
func (c Cat) PrintLegs() {
    fmt.Printf("I have %d legs.\n", c.L.Legs())
}

// OctoCat is a Cat with a particular implementation of Legs
type OctoCat struct {
    Cat
}

func (c OctoCat) Legs() int {
    return 8
}

// TetraCat has a different implementation of Legs
type TetraCat struct {
    Cat
}

func (c TetraCat) Legs() int {
    return 4
}

func main() {
    c := TetraCat{Cat{"Steve",nil}}
    c.L = &c
    c.PrintLegs() // want 4
    o := OctoCat{Cat{"Bob",nil}}
    o.L = &o
    o.PrintLegs() // want 8
}

The type definitions themselves look nice and clean, but the initialization code in main is wacky (first the nil in the struct literal, then c.L = &c, what?). Is there a better solution?

A similar pattern was presented in is it possible to call overridden method from parent struct in golang?, but the question of whether this is the idiomatic way to proceed was not answered.

Community
  • 1
  • 1
Ted Pudlik
  • 665
  • 8
  • 26

1 Answers1

1

Two approaches I would consider for solving this:

1. Refactor your code to have a single type Cat with the fields Name string and Legs int:

package main

import (
    "fmt"
)

type Cat struct {
    Name string
    Legs int
}

func (c *Cat) PrintLegs() {
    fmt.Printf("I have %d legs.\n", c.Legs)
}

func main() {
    c := &Cat{"Steve", 4}
    c.PrintLegs() // want 4
    o := &Cat{"Bob", 8}
    o.PrintLegs() // want 8
}

https://play.golang.org/p/_PNAu3sgG8

2. Do away with the Cat type and just have TetraCat and OctoCat implement the Legger interface:

package main

import (
    "fmt"
)

type Legger interface {
    Legs() int
}

func PrintLegs(l Legger) {
    fmt.Printf("I have %d legs.\n", l.Legs())
}

// OctoCat is a Cat with a particular implementation of Legs
type OctoCat struct {
    Name string
}

func (c *OctoCat) Legs() int {
    return 8
}

// TetraCat has a different implementation of Legs
type TetraCat struct {
    Name string
}

func (c *TetraCat) Legs() int {
    return 4
}

func main() {
    c := &TetraCat{"Steve"}
    PrintLegs(c) // want 4
    o := &OctoCat{"Bob"}
    PrintLegs(o) // want 8
}

https://play.golang.org/p/m4QvKhSj0w