How to keep code DRY in Golang
Asked Answered
A

3

12

EDIT++:

How to not to repeat my code in Go?

type Animal interface {
    Kingdom() string
    Phylum() string
    Family() string
}

type Wolf struct {}
type Tiger struct {}

func (w Wolf) Kingdom() string {return "Animalia"}
func (w Wolf) Phylum() string {return "Chordata"}
func (w Wolf) Family() string {return "Canidae"}

I implemented a three methods for Wolf type and I need to implement all the methods for Tiger type to implement the interface. But Kingdom and Phylum methods are the same for both types. Is it somehow possible to implement only Family method for Tiger type:

func (t Tiger) Family() string {return "Felidae"}

and not to repeat all the three methods for each type?

Disclaimer

Please don't be confused with simple string returns in the methods, in a real case I need different method implementations not just pre-defined values. Using this silly style I want to avoid of defiling your brains. So skip methods at all is not the way. Thanks

Aetna answered 25/10, 2016 at 14:3 Comment(3)
@Thilo, I'm just trying to keep it simple to not to hide the meaning of the question. Definitely it is possible if I want to return pre-known value.Aetna
I know you've rewritten this to try to make it more abstract, but it now doesn't compile (or actually make sense; you can't square an Aa as you've written it). It still very much looks like you're misusing interfaces. What's your actual interface? Are you sure it's really an interface, and not a class in disguise? (Remember, Go is not object oriented. If you try to reinvent classes, you will fight the language all day.)Wnw
@RobNapier, I understand your point. so rolled back my changes with a disclaimer to consistent to the answer which solved my issue.Aetna
L
15

This is classical composition:

type Wolf struct {
    Animalia
    Chordata
    Canidae
}
type Tiger struct {
    Animalia
    Chordata
    Felidae
}

type Animalia struct{}

func (Animalia) Kingdom() string { return "Animalia" }

type Chordata struct{}

func (Chordata) Phylum() string { return "Chordata" }

type Canidae struct{}

func (Canidae) Family() string { return "Canidae" }

type Felidae struct{}

func (Felidae) Family() string { return "Felidae" }

func main() {
    w := Wolf{}
    t := Tiger{}
    fmt.Println(w.Kingdom(), w.Phylum(), w.Family())
    fmt.Println(t.Kingdom(), t.Phylum(), t.Family())
}

Playground: https://play.golang.org/p/Jp22N2IuHL.

Legerdemain answered 25/10, 2016 at 14:10 Comment(1)
I am very grateful to you.Aetna
W
4

This looks very much like an misuse of interfaces. Interfaces are not a replacement for classes; they're an expression of what a type can do. What you have here is data. Store data in structs.

type Animal struct {
    kingdom string
    phylum  string
    family  string
}

var wolf = Animal{"Animalia", "Chordata", "Canidae"}
var tiger = wolf
tiger.family = "Felidae"
Wnw answered 25/10, 2016 at 14:8 Comment(2)
Got it. I confused you guys with my question. I have edited it to be clearer. thanks!Aetna
I'll write up the difference, but there's no difference. You just want a struct full of methods rather than as struct full of strings.Wnw
A
3

Since I'm interested in the feature I have read a few articles about the topic and aggregated it to several reference points.

Embedding

The feature named "embedding". And it solves the issue with repeated methods implementations. Base syntax:

type Person struct {
    Name string
}

type Speaker struct { // Speaker is an abstract concept it has no name
    *Person // There is a type without field name. It is Anonymous.
}

Wrapping and decoration

Yes, there is no OOP but code must be DRY anyway. The clearest way to think about the feature is take this as wrapping structs with methods. So the most veracious way to describe anonymous fields is "decorator" pattern (well known for Pythonistas).

func (a *Speaker) Introduce(){ // But speaker can introduce itself
    fmt.Println(a.Name) // We have direct access to a wrapped struct attributes.
}

Combining and overriding

Also we can combine methods implemented on a structs

func (s Speaker) Speak() string {
    return "Blah-blah"
}

type Den struct { // Combine Person and Speaker under Den struct
    Person
    Speaker
}

func (d Den) Speak() string { // Override Speak method only for Dennis 
    return "I'm quit!"
}

func main() {
    den := Den{Person: Person{Name: "Dennis",}}
    mike := Speaker{Person: Person{Name: "Michael",}}

    fmt.Println(den.Introduce())
    fmt.Println(den.Speak())
    fmt.Println(mike.Introduce())
    fmt.Println(mike.Speak())
}

In this way we can avoid implementation of each required method for each type.

Interfaces

Same thing with interfaces. But if several interfaces are combined it means just that we don't need to declare methods which already declared in used interfaces.

Playground

Dennis Suratna's Blog article

Docs

Aetna answered 26/10, 2016 at 11:49 Comment(1)
The issue with your example is the exact one that I am currently struggling with continuing to learn and work with Go - you inherit the Person in the Speaker which is totally fine, but you write method for speaker and not for person which means there is no DRY and you write a ton of code over and over again.Craigie

© 2022 - 2024 — McMap. All rights reserved.