Methods and Interfaces¶
One of the novel features of Go is its use of interfaces. They are designed to provide many of the same benefits of object-oriented programming, but without explicit classes or type inheritance as in C++ or Java.
Methods¶
Before we can discuss interfaces, we need to look at Go methods. Consider this code:
type Rectangle struct {
width, height float64
}
// area() is a method
func (r Rectangle) area() float64 {
return r.width * r.height
}
// perimeter() is a method
func (r Rectangle) perimeter() float64 {
return 2 * (r.width + r.height)
}
// main is a function
func main() {
r := Rectangle{width:5, height:3}
fmt.Printf("dimensions of r: width=%d, height=%d\n", r.width, r.height)
fmt.Printf(" area of r: %.2f\n", r.area())
fmt.Printf(" perimeter of r: %.2f\n", r.perimeter())
}
Look at the signatures for area() and perimeter(). The extra parameter
in brackets is called the method receiver, i.e. r is the receiver in
perimeter(). The receiver is essentially a special input that has some
some special privileges.
In main, notice how the usual dot-notation is used for calling methods,
e.g. r.area() calls the area method on r.
Method receivers can, if the programmer desires, be passed as pointers to a
type. For example, here r is passed a pointer because inflate modifies
the object r points to:
func (r *Rectangle) inflate(scale float64) {
r.width *= scale
r.height *= scale
}
Without the *, r would be passed by value, meaning that the code in
the body of inflate would modify this copy instead of the original object
that calls the method.
Note that there is no change in the syntax for how the fields of r are
accessed: the regular dot-notation is used. Similarly, inflate is called
the same way, e.g. r.inflate(2.2). There is no special -> operator as
in C/C++.
An important concept in Go is that of method sets. The Go language spec defines method sets like this:
A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type
Tconsists of all methods declared with receiver typeT. The method set of the corresponding pointer type*Tis the set of all methods declared with receiver*TorT(that is, it also contains the method set ofT). Further rules apply to structs containing embedded fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a unique non- blank method name.The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.
So, in our example above, the method set of the type Rectangle is:
{area, perimeter}. The method set of *Rectangle is: {area,
perimeter, inflate}.
Calling a method is defined like this in the Go spec:
A method callx.m()is valid if the method set of (the type of)xcontainsmand the argument list can be assigned to the parameter list ofm. Ifxis addressable and&x‘s method set containsm,x.m()is shorthand for(&x).m()....
Interfaces¶
Here’s an example of a Go interface:
type Shaper interface {
area() float64
perimeter() float64
}
The name of this interface is Shaper, and it lists the signatures of the
two methods that are necessary to satisfy it. A type T implements
Shaper if Shaper‘s method set (the names listed in the interface) is a
subset of the method set for T. We saw above that the method set for
Rectangle is {area, perimeter}, and the methods listed in
Shaper are clearly a subset of this. Similarly, Rectangle* also
implements Shape because the methods in Shape are a subset of the
method set of Rectangle*, {area, perimeter, inflate}.
Importantly, only the signatures of the methods are listed in the interface. The bodies of the required functions are not mentioned at all. The implementation is not part of the interface.
As another example, suppose we add this code:
type Circle struct {
radius float64
}
func (c Circle) area() float64 { // a method
return 3.14 * c.radius * c.radius
}
func (c Circle) perimeter() float64 { // a method
return 2 * 3.14 * c.radius
}
func (c Circle) diameter() float64 { // a method
return 2 * c.radius
}
The method set of Circle is {area, perimeter, diameter}, and
so it implements Shaper because it contains both area and
perimeter. The method set of *Circle is also {area, perimeter,
diameter}, and so *Circle also implements Shaper.
Now we can write code that works on any object of a type that implements
Shaper. For example:
func printShape(s Shaper) {
fmt.Printf(" area: %.2f\n", s.area())
fmt.Printf(" perimeter: %.2f\n", s.perimeter())
}
All that printShape knows about s is that the methods in the method
set for Shaper can be called on it. If s has methods not in the member
set for Shaper, then those methods cannot be called on s.
Here’s how you could use printShape:
func main() {
r := Rectangle{width:5, height:3}
fmt.Println("Rectangle ...")
printShape(r)
c := Circle{radius:5}
fmt.Println("\nCircle ...")
printShape(c)
}
An interesting detail about Go interfaces is that you don’t need to
explicitly tell Go that a struct implements an interface: the compiler
figures it out for itself. This contrasts with, for example, Java, where you
must explicitly indicate when a class implements an interface.
In summary:
- The method set of an interface is all the methods named in the interface.
- The method set of a type
T(whereTis not a pointer type) is all methods with a receiver of typeT. - The method set of a type
*T(whereTis not a pointer type) is all methods with a receiver of type*TorT. - A type
Timplements an interfaceIif the method set ofIis a subset of the method set ofT.
Example: Using the Sort Interface¶
Lets see how we can use the standard Go sorting package.
Suppose we want to sort records of people. In practice, such records might contain lots of information, such as a person’s name, address, email address, relatives, etc. But for simplicity we will use this struct:
type Person struct {
name string
age int
}
For efficiency, lets sort a slice of pointers to Person objects; this will
avoid moving and copying strings. To help with this, we define the type
People:
type People []*Person // slice of pointers to People objects
It turns out that it’s essential that we create the type People. The code
we write below won’t compile if we use []*Person directly. That’s because
the method set of []*Person does not satisfy sort.Interface. So we
will add methods on People that make it implement sort.Interface.
For convenience, here’s a String method that prints a People object:
func (people People) String() (result string) {
for _, p := range people {
result += fmt.Sprintf("%s, %d\n", p.name, p.age)
}
return result
}
The name of this is String(), and it returns a string object. A method
with this signature implements the fmt.Stringer interface, which allows it
to be called by the print functions in fmt.
Now we can write code like this:
users := People{
{"Mary", 34},
{"Lou", 3},
{"Greedo", 77},
{"Zippy", 50},
{"Morla", 62},
}
fmt.Printf("%v\n", users) // calls the People String method
To sort the items in the users slice, we must create the methods listed in
sort.Interface:
type Interface interface {
// number of elements in the collection
Len() int
// returns true iff the element with index i should come
// before the element with index j
Less(i, j int) bool
// swaps the elements with indexes i and j
Swap(i, j int)
}
This interface is pre-defined in the sort package. Notice that this is a
very general interface. It does not even assume that you will be sorting
slices or arrays!
Three methods are needed:
func (p People) Len() int {
return len(p)
}
func (p People) Less(i, j int) bool {
return p[i].age > p[j].age
}
func (p People) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
Less is the function that controls the order in which the objects will be
sorted. By examining Less you can see that we will be sorting people by
age, from oldest to youngest.
With these functions written, we can now sort users like this:
users := People{
{"Mary", 34},
{"Lou", 3},
{"Greedo", 77},
{"Zippy", 50},
{"Morla", 62},
}
fmt.Printf("%v\n", users)
sort.Sort(users)
fmt.Printf("%v\n", users)
To change the sort order, modify Less. For instance, this will sort
users alphabetically by name:
func (p People) Less(i, j int) bool {
return p[i].name < p[j].name
}
Another way to sort by different orders is shown in the examples section of the Go sort package documentation. The trick there is to create a new type for every different order you want to sort.