-1

I guess I got stuck in thinking about a polymorphism solution to my following problem:

Let's say I have a BaseTX struct with fields for a transaction. Now I have two special types of transactions: RewardTX struct and AllowanceTX struct.

RewardTX struct has at this moment only the composition of BaseTX struct.

AllowanceTX struct has a composition of BaseTX struct and an AddField.

I have also a function logicAndSaveTX(), which has some logic on fields from BaseTX but at the end is serializing the whole object using json.Marshal() and saving the byte[] somewhere.

type TXapi interface {
    logicAndSaveTX()
}

type BaseTX struct {
    Field1 string
    Field2 string
}

type RewardTX struct {
    BaseTX 
}

type AllowanceTX struct {
    BaseTX 
    AddField string
}

func (tx BaseTX) logicAndSaveTX() {
    // logic on BaseTX fields; simplified:
    tx.Field1 = "overwritten"
    tx.Field2 = "logic done"

    // here would be marshal to json and save; simplified to print object:
    fmt.Printf("saved this object: %+v \n", tx)
}

func SaveTX(tx TXapi) {
    tx.logicAndSaveTX()
}


func main() {
    rewardTX := RewardTX{BaseTX : BaseTX{Field1: "Base info1", Field2: "Base info2"}}
    SaveTX(rewardTX) // should print rewardTX with fields from BaseTX
    allowanceTX := AllowanceTX{BaseTX : BaseTX{Field1: "Base info1", Field2: "Base info2"}, AddField: "additional field"}
    SaveTX(allowanceTX) // would like to print allowanceTX with fields from BaseTX + AdditionalField >>> instead only printing fields from BaseTX
}

https://play.golang.org/p/0Vu_YXktRIk

I try to figure out how to implement the structures and the function to operate on both kinds of transactions but at the end serializing both structures properly. My problem is, that the AddField is not being seen in my current implementation.

Maybe I have got some brain fail here--I would really like to implement this the "proper Go way". :)

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
fabem
  • 156
  • 6
  • 1
    Go doesn't really have *Inheritance* like that of Java or C++. There are no class hierarchies in the classic definition of *parent-child* classes. The closest you can get is interfaces which is like an abstract class with just abstract methods (no fields). Go favors composition which IMO is simpler and easier to trace/read. – ssemilla Dec 07 '18 at 18:05
  • 1
    Whenever someone tries to emulate inheritance by embedding he will fail and hurt himself. Stop even trying: You will fail. Encapsulate behaviour in an interface and you have polymorphism. – Volker Dec 07 '18 at 22:41
  • [Embedding instead of inheritance in Go](https://stackoverflow.com/q/1727250/86967) – Brent Bradburn Aug 17 '19 at 04:22

2 Answers2

6

Go is not object-oriented. The only form of polymorphism in Go is interfaces.

Coming from other, object-oriented languages can be difficult, because you have to get rid of a lot of ideas you might try to carry over - things like, for example, "base" classes/types. Just remove "base" from your design thinking; you're trying to turn composition into inheritance, and that's only going to get you into trouble.

In this case, maybe you have a legitimate case for composition here; you have some common shared fields used by multiple types, but it's not a "base" type. It's maybe "metadata" or something - I can't say what to call it given that your example is pretty abstract, but you get the idea.

So maybe you have:

type TXapi interface {
    logicAndSaveTX()
}

type Metadata struct {
    Field1 string
    Field2 string
}

type RewardTX struct {
    Metadata 
}

func (tx RewardTX) logicAndSaveTX() {
    // logic on BaseTX fields; simplified:
    tx.Field1 = "overwritten"
    tx.Field2 = "logic done"

    // here would be marshal to json and save; simplified to print object:
    fmt.Printf("saved this object: %+v \n", tx)
}

type AllowanceTX struct {
    Metadata 
    AddField string
}

func (tx AllowanceTX) logicAndSaveTX() {
    // logic on BaseTX fields; simplified:
    tx.Field1 = "overwritten"
    tx.Field2 = "logic done"
    tx.AddField = "more stuff"

    // here would be marshal to json and save; simplified to print object:
    fmt.Printf("saved this object: %+v \n", tx)
}

If the handling of the metadata (or whatever) fields is identical in all uses, maybe you give that type its own logicTX method to fill those fields, which can be called by the logicAndSaveTX of the structs that embed it.

The key here is to think of the behavior (methods) on a type to be scoped to that type, instead of thinking of it as somehow being able to operate on "child types". Child types don't exist, and there is no way for a type that is embedded in another type to operate on its container.

Adrian
  • 42,911
  • 6
  • 107
  • 99
  • I addressed that the second-to-last paragraph of my answer. How viable that is depends on the actual use case. – Adrian Dec 07 '18 at 19:10
  • thanks a lot for the advice. I will try to figure out a way, to move the "common logic" into a general function for both types and the rest implement in separate functions for each type. I guess I have to learn to stop thinking in strict object-oriented ways :) – fabem Dec 08 '18 at 00:34
0

Also point to be noted here aht Go only support run time polymorphism through interfaces. Compile time polymorphism is not possible in Golang.

Source: - https://golangbyexample.com/oop-polymorphism-in-go-complete-guide/

user27111987
  • 995
  • 2
  • 10
  • 26