OOP in Go – Polymorphism

Polymorphism in Go is achieved through interfaces. As we have already discussed, interfaces are implemented implicitly in Go. A type implements an interface if it provides definitions for all methods declared in the interface. Let’s see how polymorphism is achieved in Go with the help of interfaces.

Polymorphism using interfaces

Any type that provides definitions for all methods of an interface is said to implicitly implement that interface. This will become clearer when we discuss examples of polymorphism later.

Variables of interface type can hold any value that implements the interface. Go uses this feature of interfaces to achieve polymorphism.

Let us understand polymorphism in Go with the help of a program that calculates the net income of an organization. For the sake of simplicity, let us assume that this fictitious organization has revenue from two types of items, viz. Fixed billing, Time and materials. The organization’s net income is calculated based on the sum of revenue from these items. To keep this tutorial simple, we’ll assume the currency is US dollars. It will use to represent int.

We first define an interface Income.

type Income interface {<!-- -->
    calculate() int
    source() string
}

The interface Income defined above contains two methods calculate(), which respectively calculate and return the income of the source and source() return the name of the source.

Next, let’s define a structure for the FixedBilling project type.

type FixedBilling struct {<!-- -->
    projectName string
    biddedAmount int
}

The FixedBilling project has two fields projectName and biddedAmount, which respectively represent the project name and the amount of the organization’s bid for the project.

The TimeAndMaterial structure will represent items of type Time and Material.

type TimeAndMaterial struct {<!-- -->
    projectName string
    noOfHours int
    hourlyRate int
}

The TimeAndMaterial structure has three field names projectName, noOfHours and hourlyRate.

The next step is to define methods on these structure types that calculate and return actual revenue and revenue sources.

func (fb FixedBilling) calculate() int {<!-- -->
    return fb.biddedAmount
}

func (fb FixedBilling) source() string {<!-- -->
    return fb.projectName
}

func (tm TimeAndMaterial) calculate() int {<!-- -->
    return tm.noOfHours * tm.hourlyRate
}

func (tm TimeAndMaterial) source() string {<!-- -->
    return tm.projectName
}

With fixed billingFixedBilling, revenue is simply the bid amount for the project. The value is returned from the calculate() method whose receiver type is FixedBilling

For time and material type projects, revenue is the product of noOfHours and hourlyRate. The value is returned from the calculate() method whose receiver type is TimeAndMaterial

We return the project name as the source of income for this method source().

Since both the FixedBilling and TimeAndMaterial structures provide the definition of the interface’s methods, both structures implement the interface. calculate() and source()

Let’s declare a calculateNetIncome function that calculates and prints total income.

func calculateNetIncome(ic []Income) {<!-- -->
    var netincome int = 0
    for _, income := range ic {<!-- -->
        fmt.Printf("Income From %s = $%d\
", income.source(), income.calculate())
        netincome + = income.calculate()
    }
    fmt.Printf("Net income of organization = $%d", netincome)
}

calculateNetIncome The above function accepts an interface fragment Income as a parameter. It calculates the total revenue by iterating over the slice and calling the calculate() method on each of its items. It also displays the source of income by calling the source() method. Depending on the specific type of the Income interface, different calculate() and source() methods will be called. In this way we achieve polymorphism of functions

In the future, if the organization adds a new revenue source, the function will still correctly calculate total revenue without any code changes :).

The only remaining part of the program is the main function.

func main() {<!-- -->
    project1 := FixedBilling{<!-- -->projectName: "Project 1", biddedAmount: 5000}
    project2 := FixedBilling{<!-- -->projectName: "Project 2", biddedAmount: 10000}
    project3 := TimeAndMaterial{<!-- -->projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
    incomeStreams := []Income{<!-- -->project1, project2, project3}
    calculateNetIncome(incomeStreams)
}

In the function above main, we created three projects, two of type FixedBilling and one of type TimeAndMaterial. Next, we create a type slice with these 3 items. Since each item implements the Income interface, all three items can be added to Income‘s slice. Finally, we call the calculateNetIncome function and pass this slice as a parameter. It will show the various sources of income and the income derived from them.

Here is the complete program for your reference.

package main

import (
    "fmt"
)

type Income interface {<!-- -->
    calculate() int
    source() string
}

type FixedBilling struct {<!-- -->
    projectName string
    biddedAmount int
}

type TimeAndMaterial struct {<!-- -->
    projectName string
    noOfHours int
    hourlyRate int
}

func (fb FixedBilling) calculate() int {<!-- -->
    return fb.biddedAmount
}

func (fb FixedBilling) source() string {<!-- -->
    return fb.projectName
}

func (tm TimeAndMaterial) calculate() int {<!-- -->
    return tm.noOfHours * tm.hourlyRate
}

func (tm TimeAndMaterial) source() string {<!-- -->
    return tm.projectName
}

func calculateNetIncome(ic []Income) {<!-- -->
    var netincome int = 0
    for _, income := range ic {<!-- -->
        fmt.Printf("Income From %s = $%d\
", income.source(), income.calculate())
        netincome + = income.calculate()
    }
    fmt.Printf("Net income of organization = $%d", netincome)
}

func main() {<!-- -->
    project1 := FixedBilling{<!-- -->projectName: "Project 1", biddedAmount: 5000}
    project2 := FixedBilling{<!-- -->projectName: "Project 2", biddedAmount: 10000}
    project3 := TimeAndMaterial{<!-- -->projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
    incomeStreams := []Income{<!-- -->project1, project2, project3}
    calculateNetIncome(incomeStreams)
}

Run program in playground

The program will output

Income From Project 1 = $5000
Income From Project 2 = $10000
Income From Project 3 = $4000
Net income of organization = $19000

Add new revenue streams to the above plan

Suppose the organization finds a new source of revenue through advertising. Let’s see how simple it is to add this new income stream and calculate the total income without making any changes to the function calculateNetIncome. This is possible due to polymorphism.

Let’s first define the Advertisement type and the calculate() and source() methods.

type Advertisement struct {<!-- -->
    adName string
    CPC int
    noOfClicks int
}

func (a Advertisement) calculate() int {<!-- -->
    return a.CPC * a.noOfClicks
}

func (a Advertisement) source() string {<!-- -->
    return a.adName
}

The Advertisement type has three fields adName, (CPCcost per click) and noOfClicks (number of clicks ). Total ad revenue is the product of CPC and noOfClicks.

Let’s modify the main function slightly to include this new revenue stream.

func main() {<!-- -->
    project1 := FixedBilling{<!-- -->projectName: "Project 1", biddedAmount: 5000}
    project2 := FixedBilling{<!-- -->projectName: "Project 2", biddedAmount: 10000}
    project3 := TimeAndMaterial{<!-- -->projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
    bannerAd := Advertisement{<!-- -->adName: "Banner Ad", CPC: 2, noOfClicks: 500}
    popupAd := Advertisement{<!-- -->adName: "Popup Ad", CPC: 5, noOfClicks: 750}
    incomeStreams := []Income{<!-- -->project1, project2, project3, bannerAd, popupAd}
    calculateNetIncome(incomeStreams)
}

We created two ads, bannerAd and popupAd. The incomeStreams slice contains the two ads we just created.

Here is the complete program after adding ads.

package main

import (
    "fmt"
)

type Income interface {<!-- -->
    calculate() int
    source() string
}

type FixedBilling struct {<!-- -->
    projectName string
    biddedAmount int
}

type TimeAndMaterial struct {<!-- -->
    projectName string
    noOfHours int
    hourlyRate int
}

type Advertisement struct {
    adName string
    CPC int
    noOfClicks int
}

func (fb FixedBilling) calculate() int {<!-- -->
    return fb.biddedAmount
}

func (fb FixedBilling) source() string {<!-- -->
    return fb.projectName
}

func (tm TimeAndMaterial) calculate() int {<!-- -->
    return tm.noOfHours * tm.hourlyRate
}

func (tm TimeAndMaterial) source() string {<!-- -->
    return tm.projectName
}

func (a Advertisement) calculate() int {
    return a.CPC * a.noOfClicks
}

func (a Advertisement) source() string {
    return a.adName
}
func calculateNetIncome(ic []Income) {<!-- -->
    var netincome int = 0
    for _, income := range ic {<!-- -->
        fmt.Printf("Income From %s = $%d\
", income.source(), income.calculate())
        netincome + = income.calculate()
    }
    fmt.Printf("Net income of organization = $%d", netincome)
}

func main() {<!-- -->
    project1 := FixedBilling{<!-- -->projectName: "Project 1", biddedAmount: 5000}
    project2 := FixedBilling{<!-- -->projectName: "Project 2", biddedAmount: 10000}
    project3 := TimeAndMaterial{<!-- -->projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
    bannerAd := Advertisement{<!-- -->adName: "Banner Ad", CPC: 2, noOfClicks: 500}
    popupAd := Advertisement{<!-- -->adName: "Popup Ad", CPC: 5, noOfClicks: 750}
    incomeStreams := []Income{<!-- -->project1, project2, project3, bannerAd, popupAd}
    calculateNetIncome(incomeStreams)
}

Run program in playground

The above program will output,

Income From Project 1 = $5000
Income From Project 2 = $10000
Income From Project 3 = $4000
Income From Banner Ad = $1000
Income From Popup Ad = $3750
Net income of organization = $23750

calculateNetIncomeYou may have noticed that although we’ve added new revenue streams, we haven’t made any changes to this feature. It only works because of polymorphism. Since the new Advertisement type also implements the Income interface, we can add it to the incomeStreams slice. The calculateNetIncome function works without any changes because it is able to call the calculate() and source() methods of the type.

This concludes this tutorial. Have a nice day.