Object Oriented Programming in Go


http://www.goinggo.net/2013/07/object-oriented-programming-in-go.html


Someone asked a question on the forum today on how to gain the benefits of inheritance without embedding. It is really important for everyone to think in terms of Go and not the languages they are leaving behind. I can't tell you much code I removed from my early Go implementations because it wasn't necessary. The language designers have years of experience and knowledge. Hindsight is helping to create a language that is fast, lean and really fun to code in.

I consider Go to be a light object oriented programming language. Yes it does have encapsulation and type member functions but it lacks inheritance and therefore traditional polymorphism. For me, inheritance is useless unless you want to implement polymorphism. With the way interfaces are implemented in Go, there is no need for inheritance. Go took the best parts of OOP, left out the rest and gave us a better way to write polymorphic code.

Here is a quick view of OOP in Go. Start with these three structs:

type Animal struct {
    Name string
    mean bool
}

type Cat struct {
    Basics Animal
    MeowStrength int
}

type Dog struct {
    Animal
    BarkStrength int
}

Here are three structs you would probably see in any OOP example. We have a base struct and two other structs that are specific to the base. The Animal structure contains attributes that all animals share and the other two structs are specific to cats and dogs.

All of the member properties are public except for mean. The mean property in the Animal struct starts with a lowercase letter. In Go, the case of the first letter for variables, structs, properties, functions, etc. determine the access specification. Use a capital letter and it's public, use a lowercase letter and it's private. I like using underscore (_) to make things private. In my program I would have written mean as _Mean.

Since there is no inheritance in Go, composition is your only choice. The Cat struct has a property called Basics which is of type Animal. The Dog struct is using an un-named struct for the Animal property. It's up to you to decide which is better for you and I will show you both implementations.

I want to thank John McLaughlin for his comment about un-named structs!!

To create a member function for both Cat and Dog, the syntax is as follows:

func  (dog *Dog) MakeNoise() {
    barkStrength :=  dog.BarkStrength

    if  dog.mean == true {
        barkStrength = barkStrength * 5
    }

    for bark := 0; bark < barkStrength; bark++ {
        fmt.Printf("BARK ")
    }

    fmt.Printf("\n")
}

func  (cat *Cat) MakeNoise() {
    meowStrength :=  cat.MeowStrength

    if  cat. Basics.mean == true {
        meowStrength = meowStrength * 5
    }

    for meow := 0; meow < meowStrength; meow++ {
        fmt.Printf("MEOW ")
    }

    fmt.Printf("\n")
}

Before the name of the function we specify a pointer to the type struct. Now both Cat and Dog have member functions called MakeNoise.

Both these member functions do the same thing. Each animal speaks in their native tongue based on their bark or meow strength and if they are mean. Silly, but it shows you how to access the referenced object.

When using the Dog reference we access the Animal properties directly. With the Cat reference we use the named property called Basics.

One thing that is missing is the famous "this" pointer. If you are really missing "this" you could change the local variable pointer as follows:

func ( this *Dog) MakeNoise() {
    barkStrength :=  this.BarkStrength

    if  this.mean == true {
        barkStrength = barkStrength * 5
    }

    for bark := 0; bark < barkStrength; bark++ {
        fmt.Printf("BARK ")
    }

    fmt.Printf("\n")
}

So far we have covered encapsulation, composition, access specifications and member functions. All that is left is how to create polymorphic behavior.

We use interfaces to create polymorphic behavior:

type AnimalSounder interface {
    MakeNoise()
}

func MakeSomeNoise(animalSounder AnimalSounder) {
    animalSounder.MakeNoise()
}

Here we add an interface and a public method that takes a reference to this interface. Actually the function will take a reference to an object that implements this interface. An interface is not a type that can be instantiated. An interface is a declaration of behavior.


There is a Go convention of naming interfaces with the "er" suffix when the interface only contains one method.

In Go, any type struct that implements this interface, via a member function, then represents this type. In our case both Cat and Dog have implemented the AnimalSounder interface and therefore are considered to be of type AnimalSounder.

This means that objects of both Cat and Dog can be passed as parameters to the MakeSomeNoise function. The MakeSomeNoise function implements polymorphic behavior through the AnimalSounder interface.

If you wanted to reduce the duplication of code in the MakeNoise member functions of Cat and Dog, you could create a member function in Animal to handle it:

func (animal *Animal) PerformNoise(strength int, sound string) {
    if animal.mean == true {
        strength = strength * 5
    }

    for voice := 0; voice < strength; voice++ {
        fmt.Printf("%s ", sound)
    }

    fmt.Printf("\n")
}

func (dog *Dog) MakeNoise() {
    dog.PerformNoise(dog.BarkStrength, "BARK")
}

func (cat *Cat) MakeNoise() {
    cat.Basics.PerformNoise(cat.MeowStrength, "MEOW")
}

Now the Animal type has a member function with the business logic for making noise. The business logic stays within the scope of the objects it belongs to. The other cool benefit is we don't need to pass the mean value in as a parameter because it already belongs to the Animal type.

Here is the complete working sample program:

package main

import (
    "fmt"
)

type Animal struct {
    Name string
    mean bool
}

type AnimalSounder interface {
    MakeNoise()
}

type Dog struct {
    Animal
    BarkStrength int
}

type Cat struct {
    Basics Animal
    MeowStrength int
}

func main() {
    myDog := &Dog{
        Animal{
           "Rover", // Name
           false,   // mean
        },
        2, // BarkStrength
    }

    myCat := &Cat{
        Basics: Animal{
            Name: "Julius",
            mean: true,
        },
        MeowStrength: 3,
    }

    MakeSomeNoise(myDog)
    MakeSomeNoise(myCat)
}

func (animal *Animal) PerformNoise(strength int, sound string) {
    if animal.mean == true {
        strength = strength * 5
    }

    for voice := 0; voice < strength; voice++ {
        fmt.Printf("%s ", sound)
    }

    fmt.Printf("\n")
}

func (dog *Dog) MakeNoise() {
    dog.PerformNoise(dog.BarkStrength, "BARK")
}

func (cat *Cat) MakeNoise() {
    cat.Basics.PerformNoise(cat.MeowStrength, "MEOW")
}

func MakeSomeNoise(animalSounder AnimalSounder) {
    animalSounder.MakeNoise()
}

Here is the output:

BARK BARK
MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW

Someone posted an example on the board about embedding an interface inside of a struct. Here is an example:

package main

import (
    "fmt"
)

type HornSounder interface {
    SoundHorn()
}

type Vehicle struct {
    List [2]HornSounder
}

type Car struct {
    Sound string
}

type Bike struct {
   Sound string
}

func main() {
    vehicle := new(Vehicle)
    vehicle.List[0] = &Car{"BEEP"}
    vehicle.List[1] = &Bike{"RING"}

    for _, hornSounder := range vehicle.List {
        hornSounder.SoundHorn()
    }
}

func (car *Car) SoundHorn() {
    fmt.Println(car.Sound)
}

func (bike *Bike) SoundHorn() {
    fmt.Println(bike.Sound)
}

func PressHorn(hornSounder HornSounder) {
    hornSounder.SoundHorn()
}

In this example the Vehicle struct maintains a list of objects that implement the HornSounder interface. In main we create a new vehicle and assign a Car and Bike object to the list. This assignment is possible because Car and Bike both implement the interface. Then using a simple loop, we use the interface to sound the horn.

Everything you need to implement OOP in your application is there in Go. As I said before, Go took the best parts of OOP, left out the rest and gave us a better way to write polymorphic code.

To learn more on related topics check out these posts:

http://www.goinggo.net/2013/07/how-packages-work-in-go-language.html
http://www.goinggo.net/2013/07/singleton-design-pattern-in-go.html

I hope this small example helps you in your future Go programming.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值