- Learning Functional Programming in Go
- Lex Sheehan
- 442字
- 2021-07-02 23:13:46
Open/closed principle
Software should be open for extension but closed for modification. Embedding fields in a struct allows us to extend one type with another. The object (CarWithSpare) that embedded the other (Car) has access to its fields and methods. The CarWithSpare object can call Car methods, but cannot modify the Car object's methods. Therefore, Go's types, while being open for extension, are closed for modification. Let's look at an example:
package car
import "fmt"
type Car struct {
Make string
Model string
}
func (c Car) Tires() int { return 4 }
func (c Car) PrintInfo() {
fmt.Printf("%v has %d tires\n", c, c.Tires())
}
We defined our Car type and two methods, Tires and PrintInfo. Next, we'll define our CarWithSpare type and embed the Car type as an unnamed field:
type CarWithSpare struct {
Car
}
func (o CarWithSpare) Tires() int { return 5 }
In our main.go file, we create a Honda Accord and call its PrintInfo method. As expected it returns 4 tires.
Next, we create a Toyota Highlander, but when we print its info, it prints 4 tires instead of 5. Why?
package main
import (
. "car"
"fmt"
)
func main() {
accord := Car{"Honda", "Accord"}
accord.PrintInfo()
highlander := CarWithSpare{Car{"Toyota", "Highlander"}}
highlander.PrintInfo()
fmt.Printf("%v has %d tires", highlander.Car, highlander.Tires())
}
The following is the output:
{Honda Accord} has 4 tires
{Toyota Highlander} has 4 tires
{Toyota Highlander} has 5 tires
That's because PrintInfo is a method of Car, but since CarWithSpare is missing that method, when we call highlander.PrintInfo we're actually executing Car's method (not CarWithSpare).
In order to print the actual number of tires our highlander has, we must manually delegate the call by executing highlander.Tires directly from within our fmt.Printf statement.
Do we have other options? Yes. We can override the PrintInfo method. In other words, we can define a PrintInfo method for our CarWithSpare as follows:
func (c CarWithSpare) PrintInfo() {
fmt.Printf("%v has %d tires\n", c, c.Tires())
}
The following is the output:
{Honda Accord} has 4 tires
{Toyota Highlander} has 5 tires
{Toyota Highlander} has 5 tires
What if we call accord.PrintInfo() again? We get the following output:
{Honda Accord} has 4 tires
So, Go allows us to:
- implicitly call an embedded object's method (if not defined)
- manually delegate to call our object's method
- override an embedded object's method
What about method overloading?
Not allowed. If we were to attempt to create another PrintInfo method with a different argument signature, Go would throw a compiler error:
Using the decorator pattern in the next chapter, we'll see how we can extend functionality without modifying the existing code.