Go program pattern 02: Implementing Class Inheritance and Method Overriding through Composition
Tags: go, pattern
Published: 2024-04-24 02:35:48


In the previous tutorial, I have already introduced that Go language, unlike object-oriented programming languages such as Java and PHP, does not support keywords like `class` to define classes. Instead, it uses the `type` keyword combined with basic types or structures to define the type system. Additionally, it does not support explicitly defining inheritance relationships between types using the `extends` keyword.

> This article is first published in the medium MPP plan. If you are a medium user, please follow me in [medium](https://medium.hxzhouh.com/). Thank you very much.

Strictly speaking, Go language is not an object-oriented programming language, at least not the best choice for object-oriented programming (Java is the most established one). However, we can simulate object-oriented programming based on some features provided by Go.

To implement object-oriented programming, we must implement the three major features of object-oriented programming: encapsulation, inheritance, and polymorphism.

## Inheritance

Next is **inheritance**. Although Go does not directly provide syntax for inheritance, we can indirectly achieve similar functionality through **composition**. Composition means embedding one type into another type to build a new type structure.

In traditional object-oriented programming, explicitly defining inheritance relationships has two drawbacks: one is that it leads to increasingly complex class hierarchies, and the other is that it affects the extensibility of classes. Many software design patterns advocate using composition instead of inheritance to improve class extensibility.

Let's take an example. Suppose we want to create a UI component library. We have a `Widget` structure type with two properties, `x` and `y`, representing the length and width of the component.  
If we want to define a class representing `Label`, we can do it like this:
```go
type Label struct {
    Widget
    text string
}
```

Here, `Label` inherits all the properties of `Widget` and adds a new property `text`. Similarly, we can define the `Button` and `ListBox` classes:
```go
type Button struct {
    Label
}
type ListBox struct {
    Widget
    text  []string
    index int
}
```

## Polymorphism

First, we define two interfaces, `Painter` for painting and `Clicker` for clicking:
```go
type Painter interface {
    Paint()
}
type Clicker interface {
    Click()
}
```

Then, the components implement these interfaces:
```go
func (label Label) Paint() {
    // display label
    fmt.Printf("%p:Label.Paint(%q)\n", &label, label.text)
}

func (button Button) Paint() {
    // display button
    fmt.Printf("Button.Paint(%q)\n", button.text)
}
func (button Button) Click() {
    // click button
    fmt.Printf("Button.Click(%q)\n", button.text)
}

func (listBox ListBox) Paint() {
    // display listBox
    fmt.Printf("ListBox.Paint(%q)\n", listBox.text)
}
```

`Label` implements `Painter`, and `Button` and `ListBox` implement both `Painter` and `Clicker`.

At the application level, we can use these components like this:
```go
label := Label{Widget{10, 10}, "State:"}
button1 := Button{Label{Widget{10, 70}, "OK"}}
button2 := NewButton(50, 70, "Cancel")
listBox := ListBox{Widget{10, 40},
    []string{"AL", "AK", "AZ", "AR"}, 0}
for _, painter := range []Painter{label, listBox, button1, button2} {
    painter.Paint()
}
fmt.Println("=========================================")
for _, clicker := range []Clicker{listBox, button1, button2} {
    clicker.Click()
}
fmt.Println("=========================================")
for _, widget := range []interface{}{label, listBox, button1, button2} {
    widget.(Painter).Paint()
    if clicker, ok := widget.(Clicker); ok {
        clicker.Click()
    }
}
```

Go language is different from object-oriented programming languages like Java and PHP in that it does not provide keywords specifically for referencing parent class instances (such as `super`, `parent`, etc.). In Go language, the design philosophy is simplicity, without any unnecessary keywords. All calls are straightforward.
## Summary
Let's summarize briefly. In Go language, the concept of classes in traditional object-oriented programming is intentionally weakened, which is in line with Go's philosophy of simplicity. The "classes" defined based on structures are just ordinary data types, similar to built-in data types. Built-in data types can also be transformed into "classes" that can contain custom member methods using the `type` keyword.

All methods associated with a data type collectively form the method set of that type. Like other object-oriented programming languages, methods within the same method set cannot have the same name. Additionally, if they belong to a structure type, their names cannot overlap with any field names in that type.
## References

- [How to pass a 'child' struct into a function accepting 'parent' struct?](https://stackoverflow.com/questions/37011799/how-to-pass-a-child-struct-into-a-function-accepting-parent-struct)
- [Check if a struct has struct embedding at run time](https://stackoverflow.com/questions/61585699/check-if-a-struct-has-struct-embedding-at-run-time)
- [GO编程模式：委托和反转控制](https://coolshell.cn/articles/21214.html)