Go program pattern 02: Implementing Class Inheritance and Method Overriding through Composition

huizhou92 - Apr 24 - - Dev Community

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. 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:

type Label struct {
    Widget
    text string
}
Enter fullscreen mode Exit fullscreen mode

Here, Label inherits all the properties of Widget and adds a new property text. Similarly, we can define the Button and ListBox classes:

type Button struct {
    Label
}
type ListBox struct {
    Widget
    text  []string
    index int
}
Enter fullscreen mode Exit fullscreen mode

Polymorphism

First, we define two interfaces, Painter for painting and Clicker for clicking:

type Painter interface {
    Paint()
}
type Clicker interface {
    Click()
}
Enter fullscreen mode Exit fullscreen mode

Then, the components implement these interfaces:

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)
}
Enter fullscreen mode Exit fullscreen mode

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

At the application level, we can use these components like this:

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()
    }
}
Enter fullscreen mode Exit fullscreen mode

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

. . . . . . . . . . . . . . . . .