4

I've noticed how hard it is to let go of the OOP style programming I've used in Java and PHP over the last 10 years or so. I'm giving golang a go (pun intended) since a few weeks, but I'm trying to feel natural around the composition over inheritance principle golang has.

How would I define a useful interface to make sure all these structs can fulfill it? I've tried to come up with a useful example that does not involve dogs, humans or weird constructs of vehicles...

package main

type Store struct {
  name  string
  phone string
}

type HardwareStore struct{ Store }

type FoodStore struct{ Store }

type OnlineStore struct {
  Store
  url string
}

I think it might be because I base my thinking around their state/data and not their behaviour, I'm struggling a bit.

The obvious first choice, without an interface, would probably be this (simplified), which of course fails:

s := Store{}
h := HardwareStore{}
f := FoodStore{}
o := OnlineStore{}

stores := []Store{s, h, f, o}
Patrick
  • 1,044
  • 1
  • 10
  • 18
  • 1
    This still sounds like you're trying to something like inheritance, which isn't going to work well in Go. Do you have a concrete problem you're trying to solve? – JimB Jan 13 '17 at 22:12
  • Yes, it surrounds users that have different attributes depending on the type of user, and I'm trying to stay away from inheritance to do it in idiomatic go. – Patrick Jan 13 '17 at 22:21
  • 4
    Often when shifting away from inheritance-based OOP, you can "invert" the logic. Rather than users being of different "types", have a User type with attributes that differentiate them. You can also base _everything_ on behavior, and simply define methods that return the fields you're interested in and use the method sets to define interfaces. It's hard to say with contrived examples which best fits the problem you're working on. – JimB Jan 13 '17 at 22:30
  • How to use an interface? Decouple code. See https://stackoverflow.com/a/62297796/12817546. Call a method “dynamically”. See https://stackoverflow.com/a/62336440/12817546. Access a Go package. See https://stackoverflow.com/a/62278078/12817546. Assign any value to a variable. See https://stackoverflow.com/a/62337836/12817546. –  Jul 10 '20 at 09:49

1 Answers1

4

I think below is what you're after. Sorry for ugly names, but I had to make them standout to make a point. And keep in mind there are no virtual functions in go, even though example below sort of simulates one.

package main

import "fmt"


type StoreInterface interface {
    getName() string
    setName(string)
    getPhone() string
    setPhone(string)
}

type Store struct {
   name  string
   phone string
}

func (store *Store) getName() string {
   return store.name;
}

func (store *Store) setName(newName string){
   store.name = newName;
}

func (store *Store) getPhone() string {
   return store.phone;
}

func (store *Store) setPhone(newPhone string){
   store.phone = newPhone;
}

type HardwareStore struct{ Store }

type FoodStore struct{ Store }

type OnlineStore struct {
    Store
    url string
}

func (os *OnlineStore) getName() string{
    return fmt.Sprintf("%v(%v)", os.name, os.url)
}

func main() {
    s := Store{name:"s", phone:"111"}
    h := HardwareStore{Store{name:"h", phone:"222"}}
    f := FoodStore{Store{name:"f", phone:"333"}}
    o := OnlineStore{Store:Store{name:"o", phone:"444"}, url:"http://test.com"}

    fmt.Println ("Printout 1")
    stores := []*Store{&s, &h.Store, &f.Store, &o.Store}
    for _, s := range stores {
        fmt.Printf("\t%v: %v\n", s.name, s.phone);
    }

    fmt.Println ("Printout 2")
    stores2 := []StoreInterface{&s, &h, &f, &o}
    for _, s := range stores2 {
        fmt.Printf("\t%v: %v\n", s.getName(), s.getPhone());
    }
 }

Still, it goes without saying that you need to change your mindset from inheritance to functions, data structures and behavior. It's hard to break old habits (I come from many years of OOP too). But when in Rome you should think like Romans do :o) Otherwise you gonna be missing on some potential of the language.

Seva
  • 2,388
  • 10
  • 9
  • 3
    I've kept away from defining getters and setters in my go programming, because it didn't feel correct from an idiomatic standpoint, but perhaps it is the only way out anyway? – Patrick Jan 14 '17 at 14:39
  • 2
    If it is just for the sake of accessing, why don't expose the vars writing them capital? – Michael Brenndoerfer Jan 14 '17 at 16:07
  • 1
    @Patrick Have you checked the output from "Printout 2"? And compared it to "Printout 1"? I only used getters and setters in the example as nothing more useful suggested in the code. The code is actually illustrating how to simulate inheritance and polymorphism from OOP. You obviously don't need an interface to simply access structure fields. See "Printout 1" for that. – Seva Jan 15 '17 at 02:14
  • 1
    @Patrick Just look at getName() again and how it produces different output in Printout 2 depending on actual type of the object, even though they all are supposedly "downcasted" to StoreInterface. Ignore the method name - it's irrelevant for the concept illustrated. – Seva Jan 15 '17 at 02:38
  • Thanks for all the input. I ended up implementing it in a very similar fashion. – Patrick Jan 15 '17 at 23:32