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
, (CPC
cost 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
calculateNetIncome
You 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.