Help understanding some OO patterns I cannot get to work

I come from a long career as a Java developer and I am now moving to go(lang). I tried rust for a while but as my C and CPP experience is very poor I could not afford the time and effort. Go is a much better fit especially now I can create cross platform UI(s) using fyne. I have also been using flutter and dart and like it a lot. Anyway to my questions. See Things I need help with! below.

I have included the code below.

I have an interface (called NodeI) in the structure code. It has three methods.

  • GetName() string
  • GetNodeType() NodeType
  • String() string

I have a node called ParentNode (I want to inherit its behaviour). It has two methods.

  • GetName() string
  • GetNodeType() NodeType

I have four struct(ures) that all have an unnamed parent. For example ListNode and All have a String() function (String() string)

type ListNode struct {
	parentNode     // In ALL four structures  
	value []*NodeI // Specific to ListNode
}

I am using pointers to prevent ‘copy’ behaviour and all the structures must be updatable (except name and NodeType in the parent. These are immutable)

I am creating a tree structure with ListNode(s) that contain leaf nodes and also more ListNode(s)…etc

Things I need help with!

Create a concrete object in a func and return it as its interface
With reference to makeStruct() in main.go. I would prefer to return *structure.NodeI. Sort of a factory method. However I cannot convert the *structure.ListNode to a *structure.NodeI and return it.

I tried ‘pp := (*p).(*structure.NodeI)’ where p is a *structure.ListNode and I have tried ‘many’ different formats to get the conversion to work, without any success.

Passing a concrete object to a method as it’s interface

With reference to the printName function that takes a *structure.NodeI as a parameter.
I can pass it a *structure.NodeI but not any of the concrete objects that have the interface.

For example printName(p) where p is *structure.ListNode fails. While printName(b) works. Here b is (T) *structure.NodeI. It was returned from the ListNode GetNodeAt(i) method and was created as a structure.NumberNode.

Please help. The code will be much easier to read and understand if the patterns can be applied.

Many thanks

Stuart

main.go

func main() {
	p := makeStruct()
	fmt.Printf(" 1: %T\n", p) // *structure.ListNode
	fmt.Printf(" 2: %s", p)   // String() on ListNode
	b := p.GetValueAt(1)
	//
	//	printName(p) // This does not compile. p is *structure.NumberNode
	//
	printName(b, " 3")
	fmt.Printf(" 4: %T\n", b)  // *structure.NodeI
	fmt.Printf(" 5: %T\n", *b) // *structure.NumberNode
	fmt.Printf(" 6: %s\n", *b) // String() on NumberNode

	bb := (*b).(*structure.NumberNode)
	fmt.Printf(" 7: %T\n", bb) // *structure.NumberNode

	bb.SetValue(999)
	fmt.Printf(" 8: %s\n", bb.GetName())  // NUM
	fmt.Printf(" 9: %f\n", bb.GetValue()) // 999.000000
	fmt.Printf("10: %s\n", bb)            // String() on NumberNode

	fmt.Printf("11: %s", p) // String() on ListNode
}

func printName(ni *structure.NodeI, id string) {
	fmt.Printf("%s: Name: %s\n", id, (*ni).GetName())
}

func makeStruct() *structure.ListNode {
	p := structure.NewListNode()
	s := structure.NewStringNode("STR", "B")
	n := structure.NewNumberNode("NUM", 123.5)
	b := structure.NewBoolNode("BOO", true)
	p.AddValue(s)
	p.AddValue(n)
	p.AddValue(b)
	fmt.Printf(" 0: %T\n", p) // *structure.ListNode
	// pp := (*p).(*structure.NodeI)
	// fmt.Printf("makeStruct: %T", pp) // *structure.ListNode
	return p
}

Code for nodes!

package structure

import (
	"fmt"
	"strings"
)

type NodeType int

const (
	NT_NUMBER NodeType = iota
	NT_STRING NodeType = iota
	NT_BOOL   NodeType = iota
)

type NodeI interface {
	GetName() string
	GetNodeType() NodeType
	String() string
}

//
// Parent node interface (NodeI) and properties
//
type parentNode struct {
	name string
	nt   NodeType
}

func newParentNode(name string, nt NodeType) parentNode {
	return parentNode{name: name, nt: nt}
}
func (n *parentNode) GetName() string {
	return n.name
}
func (n *parentNode) GetNodeType() NodeType {
	return n.nt
}

type ListNode struct {
	parentNode
	value []*NodeI
}

//
// List node is a ParentNode with a value of type []NodeI
//
func NewListNode() *ListNode {
	return &ListNode{parentNode: newParentNode("", NT_NUMBER), value: make([]*NodeI, 0)}
}
func (n *ListNode) GetValue() *[]*NodeI {
	return &n.value
}
func (n *ListNode) GetValueAt(i int) *NodeI {
	return n.value[i]
}
func (n *ListNode) AddValue(node NodeI) {
	n.value = append(n.value, &node)
}

func (n *ListNode) String() string {
	var sb strings.Builder
	sb.WriteString("ListNode.String() :\n")
	for i, v := range n.value {
		sb.WriteString(fmt.Sprintf("   %d :", i))
		sb.WriteString((*v).String())
		sb.WriteString("\n")
	}
	return sb.String()
}

//
// String node is a ParentNode and a value of type string
//
type StringNode struct {
	parentNode
	value string
}

func NewStringNode(name string, value string) *StringNode {
	return &StringNode{parentNode: newParentNode(name, NT_STRING), value: value}
}
func (n *StringNode) GetValue() string {
	return n.value
}
func (n *StringNode) SetValue(newValue string) {
	n.value = newValue
}
func (n *StringNode) String() string {
	return fmt.Sprintf("StringNode.String(): \"%s\": \"%s\"", n.GetName(), n.value)
}

//
// Number node is a ParentNode and a value of type float64
//
type NumberNode struct {
	parentNode
	value float64
}

func NewNumberNode(name string, value float64) *NumberNode {
	return &NumberNode{parentNode: newParentNode(name, NT_NUMBER), value: value}
}
func (n *NumberNode) GetValue() float64 {
	return n.value
}
func (n *NumberNode) SetValue(newValue float64) {
	n.value = newValue
}
func (n *NumberNode) String() string {
	return fmt.Sprintf("NumberNode.String(): \"%s\": %f", n.GetName(), n.value)
}

//
// Bool node is a ParentNode and a value of type bool
//
type BoolNode struct {
	parentNode
	value bool
}

func NewBoolNode(name string, value bool) *BoolNode {
	return &BoolNode{parentNode: newParentNode(name, NT_BOOL), value: value}
}
func (n *BoolNode) GetValue() bool {
	return n.value
}
func (n *BoolNode) SetValue(newValue bool) {
	n.value = newValue
}
func (n *BoolNode) String() string {
	return fmt.Sprintf("BoolNode.String(): \"%s\": %t", n.GetName(), n.value)
}
1 Like

The thing is, you need to understand interface is not an object but a patterned rule with specific list of methods. You can’t create NodeI “object” like struct type. However, the struct objects that offers methods complying NodeI interface can be accepted as NodeI. Example, says accepting NodeI interface as shown below:

func MyProc1(x NodeI) string {
    ...
    name := x.GetName()
    ...
    node := x.GetNodeType()
    ...
    statement := x.String()
    ...
}

Any object (be it struct1, *struct2, etc) that offers GetName() string, GetNodeType() NodeType, String() string methods as defined by NodeI can be used to operate inside MyProc1 function.

That being said, your StringNode and parentNode objects are not complying to NodeI interface because they are missing some required methods. Get the idea?

So, for your list of nodes, if you want them to comply with NodeI interface, they MUST offer all the interface’s defined methods. Example, your StringNode (and all other nodes) definition shall have something like:

type StringNode struct {
	parentNode  // parentNode is wrong. I assume you think this is inheritance?
	value string
}

func NewStringNode(name string, value string) *StringNode {
	return ...
}

...

// this method is missing
func (n *StringNode) GetName() string {
	return ??? 
}

// this method is missing
func (n *StringNode) GetNodeType() NodeType {
	return ??? 
}

// this method is available
// your StringNode is not offering `GetName()` method so where did you get `n.GetName()` from?
func (n *StringNode) String() string {
	return fmt.Sprintf("StringNode.String(): \"%s\": \"%s\"", n.GetName(), n.value)
}

Type interface is not a structure (struct). It’s a rule so the pointer won’t work. You can just use the interface directly so your printName function should be:

func printName(ni NodeI, id string) {
	fmt.Printf("%s: Name: %s\n", id, ni.GetName())
}

Then after correcting your nodes to conformNodeI interface, you just pass it in as ni parameter.


It’s obvious that Go does not offer Java (or any other similar languages) styled Object-oriented (OO) programming. There is no inheritance and polymorphism. Go only accepts composition like C-Style data structuring and also offering interface for type acceptance to keep things really simple.


If I were you, I will communicate with your supervisor or manager to make time for properly learning Go from scratch. It’s absurd to think Go is a direct replacement of Java where it is isn’t. Similarly, Go and Java syntax, codes, patterns, and approaches cannot be a direct replacement to Python.

Go is not hard to learn. (In fact, it’s the second easiest by far.) Hence, Do it properly.

  1. https://gobyexample.com/
  2. A Tour of Go
1 Like

Hi Holloway

Thank you for your reply. I am not used to this forum’s syntax so please excuse the plain response.

I think my confusion is passing pointers to ‘structure.NodeI’ and as you said it is NOT a real object.

My understanding is that ALL of my nodes have the functions of an NodeI from the combined ‘parentNode’ and the node itself.

So for example my String node is:

type StringNode struct {
	parentNode
	value string
}
...
func (n *StringNode) String() string {
	return fmt.Sprintf("StringNode.String(): \"%s\": \"%s\"", n.GetName(), n.value)
}

And the parentNode has

func (n *parentNode) GetName() string {
	return n.name
}
func (n *parentNode) GetNodeType() NodeType {
	return n.nt
}

Combined they cover all method signatures from NodeI.

Questions:

  • The inclusion of ‘parentNode’ in each structure adds to the overall interface of the struct. Is this the correct was to do this?

  • In a method like ‘func printName(n *structure.NodeI)’ what am I actually passing? Is this an error?

  • If I change it to ‘func printName(n structure.NodeI)’ does that imply a copy operation, how do I then pass in a pointer to an object that has all three associated functions.

I think I have all the issues answered but I do not feel that it has been an ‘easy’ lesson.

I can see the reasons for the complexity I just think it is a little ugly in places with all the casting and pointing. This is not a criticism it is an observation:-). I am so used to having my hand held by Java’s abstraction.

So Thanks for your assistance. It has been very informative.

The Recommended practice is to return the concrete Struct and not an interface. (this is more flexible and you don’t loose information).

You simply pass the concrete object even if the method accepts it’s interface.

No. As I said previously, Go does not have OO paradigm and parentNode will never be inherited auto-magically (no inheritance and polymorphism). What you just described is inheritance (node inherits parentNode). Composition is something like this:

type Common struct {
    Department string
    CostCenter string
}

type Person struct {
    Common *Common
    Name string
}

type Inventory struct {
    Common *Common
    ID string
    Name  string
}

func main() {
    p1 := &Person {
         Common: &Common {
              Department: "Service",
              CostCenter: "service-center",
         }
         Name: "John Smith",
    }
    fmt.Printf("%s is from %s department under cost center '%s'\n", p1.Name, p1.Common.Department, p1.Common.CostCenter)
}

Notice how the Common structure data type is composited inside each different specific structure (Person and Inventory) and how main function access the Common data.

You operate with the struct like StringNode. As long as the struct is compliant to an interface, you can pass the struct object in using its pointer.

Here is a simple full example for understanding:


Complete error. Since interface is a set of “rules”, memory pointer on its type won’t work (because it’s not an concrete object like struct, int, etc.


As far as I understand: No. interface can only be operated based on its defined methods so there is nothing to copy into the function.

In another word, if you pass in a structure (be it pointer or actual), you cannot alters the data inside the structure directly unless the interface has a method like SetName(...) or re-cast the structure.

Because you’re adapted to Java paradigm and it’s will take time to change. Folks from C/C++ (where I was from) can see Go’s benefits easily. Your thinking about using pointer to avoid copying is on track. It is the confusion about interface, which is a Go special and specific thing and “first-step to generic thing”.

He’s right here. You do not need to return an interface since rules are already defined. You return the struct, which is the data.

OK. I have read about ‘Extension Interface Pattern’ Which allows me to ‘compose’ a struct and it’s methods to comply with an interface.

This is really good and is somewhat auto-magical in that it just does it without additional annotation or key words.

I will need to experiment to understand it better. For example what happens with precedence of methods. For example if the embedded struct implements ‘String() string’, and each of my struct’s also have a ‘String() string’, which one is used.

Is the recommendation to always declare a parameter and return value using the struct name not the interface?

If it is then I would like to disagree with that. All objects a concrete, you never loose data, a NumberNode is always a NumberNode even when returned as a NodeI. Once you cast the returned object to the correct struct the object is complete.

Declaring parameters and return values as abstract (interface) is a really useful pattern when building API’s.

Having a single, simple GetNode() NodeI and SetNode(n NodeI) is less confusing than having, in my case three Get and Set methods.

GetStringNode() StringNode
SetStringNode(n StringNode)
GetNumberNode() NumberNode
SetNumberNode(n NumberNode)
GetBoolNode() BoolNode
SeBoolNode(n BoolNode)

Although I do find ‘casting’ or ‘conversion’ (whatever it’s called) in go is a bit less intuitive than other languages I have used. I will get used to it!

A huge advantage is that it allows the user of the API to create their own objects and pass them to your API, without changing the code in the API. This is a necessity for building useful tools and packages.

This combined with the factory pattern makes for generic and more expressive API’s.

Thank you for the discussion. I have learned a lot about the internals of go and it has been really useful. You have answered all my questions :slight_smile:

Regards

Stuart

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.