Lesson 24. Interfaces

After reading lesson 24, you’ll be able to

  • Get your types talking
  • Discover interfaces as you go
  • Explore interfaces in the standard library
  • Save humanity from a Martian invasion

Pen and paper aren’t the only tools you could use to jot down your latest insight. A nearby crayon and napkin can serve the purpose. Crayons, permanent markers, and mechanical pencils can all satisfy your need to write a reminder in a notepad, a slogan on construction paper, or an entry in a journal. Writing is very flexible.

The Go standard library has an interface for writing. It goes by the name of Writer, and with it you can write text, images, comma-separated values (CSV), compressed archives, and more. You can write to the screen, a file on disk, or a response to a web request. With the help of a single interface, Go can write any number of things to any number of places. Writer is very flexible.

A 0.5 mm ballpoint pen with blue ink is a concrete thing, whereas a writing instrument is a fuzzier idea. With interfaces, code can express abstract concepts such as a thing that writes. Think of what something can do, rather than what it is. This way of thinking, as expressed through interfaces, will help your code to adapt to change.

Consider this

What are some concrete things around you? What can you do with them? Can you do the same thing with something else? What is the common behavior or interface that they have?

24.1. The interface type

The majority of types focus on the values they store: integers for whole numbers, strings for text, and so on. The interface type is different. Interfaces are concerned with what a type can do, not the value it holds.

Methods express the behavior a type provides, so interfaces are declared with a set of methods that a type must satisfy. The following listing declares a variable with an interface type.

Listing 24.1. A set of methods: talk.go
var t interface {
    talk() string
}

The variable t can hold any value of any type that satisfies the interface. More specifically, a type will satisfy the interface if it declares a method named talk that accepts no arguments and returns a string.

The following listing declares two types that meet these requirements.

Listing 24.2. Satisfying an interface: talk.go
type martian struct{}

func (m martian) talk() string {
    return "nack nack"
}

type laser int

func (l laser) talk() string {
    return strings.Repeat("pew ", int(l))
}

Though martian is a structure with no fields and laser is an integer, both types provide a talk method and therefore can be assigned to t, as in the following listing.

Listing 24.3. Polymorphism: talk.go
var t interface {
    talk() string
}

t = martian{}
fmt.Println(t.talk())            1

t = laser(3)
fmt.Println(t.talk())            2

  • 1 Prints nack nack
  • 2 Prints pew pew pew

The shape-shifting variable t is able to take the form of a martian or laser. Computer scientists say that interfaces provide polymorphism, which means “many shapes.”

Note

Unlike Java, in Go martian and laser don’t explicitly declare that they implement an interface. The benefit of this is covered later in the lesson.

Typically interfaces are declared as named types that can be reused. There’s a convention of naming interface types with an -er suffix: a talker is anything that talks, as shown in the following listing.

Listing 24.4. A talker type: shout.go
type talker interface {
    talk() string
}

An interface type can be used anywhere other types are used. For example, the following shout function has a parameter of type talker.

Listing 24.5. Shout what was spoken: shout.go
func shout(t talker) {
    louder := strings.ToUpper(t.talk())
    fmt.Println(louder)
}

You can use the shout function with any value that satisfies the talker interface, whether martians or lasers, as shown in the next listing.

Listing 24.6. Shouting: shout.go
shout(martian{})            1
shout(laser(2))             2

  • 1 Prints NACK NACK
  • 2 Prints PEW PEW

The argument you pass to the shout function must satisfy the talker interface. For example, the crater type doesn’t satisfy the talker interface, so if you expect a crater to shout, Go refuses to compile your program:

type crater struct{}
shout(crater{})            1

  • 1 crater does not implement talker (missing talk method)

Interfaces exhibit their flexibility when you need to change or extend code. When you declare a new type with a talk method, the shout function will work with it. Any code that only depends on the interface can remain the same, even as implementations are added and modified.

It’s worth noting that interfaces can be used with struct embedding, the language feature covered in lesson 23. For example, the following listing embeds laser in a starship.

Listing 24.7. Embedding satisfies interfaces: starship.go
type starship struct {
    laser
}

s := starship{laser(3)}

fmt.Println(s.talk())           1
shout(s)                        2

  • 1 Prints pew pew pew
  • 2 Prints PEW PEW PEW

When a starship talks, the laser does the talking. Embedding laser gives the starship a talk method that forwards to the laser. Now the starship also satisfies the talker interface, allowing it to be used with shout.

Used together, composition and interfaces make a very powerful design tool.

Bill Venners, JavaWorld (see mng.bz/B5eg)

Quick check 24.1

1

Modify the laser’s talk method in listing 24.4 to prevent the Martian guns from firing, thus saving humanity from the invasion.

2

Expand listing 24.4 by declaring a new rover type with a talk method that returns “whir whir”. Use the shout function with your new type.

QC 24.1 answer

1

func (l laser) talk() string {
    return strings.Repeat("toot ", int(l))
}

2

type rover string

func (r rover) talk() string {
    return string(r)
}

func main() {
    r := rover("whir whir")
    shout(r)                      1
}

  • 1 Prints WHIR WHIR

 

24.2. Discovering the interface

With Go you can begin implementing your code and discover the interfaces as you go. Any code can implement an interface, even code that already exists. This section walks you through an example.

The following listing derives a fictional stardate from the day of the year and hour of the day.

Listing 24.8. Stardate calculation: stardate.go
package main

import (
    "fmt"
    "time"
)

// stardate returns a fictional measure of time for a given date.
func stardate(t time.Time) float64 {
    doy := float64(t.YearDay())
    h := float64(t.Hour()) / 24.0
    return 1000 + doy + h
}

func main() {
    day := time.Date(2012, 8, 6, 5, 17, 0, 0, time.UTC)
    fmt.Printf("%.1f Curiosity has landed
", stardate(day))        1
}

  • 1 Prints 1219.2 Curiosity has landed

The stardate function in listing 24.8 is limited to Earth dates. To remedy this, the following listing declares an interface for stardate to use.

Listing 24.9. Stardate interface: stardater.go
type stardater interface {
    YearDay() int
    Hour() int
}

// stardate returns a fictional measure of time.
func stardate(t stardater) float64 {
    doy := float64(t.YearDay())
    h := float64(t.Hour()) / 24.0
    return 1000 + doy + h
}

The new stardate function in listing 24.9 continues to operate on Earth dates because the time.Time type in the standard library satisfies the stardater interface. Interfaces in Go are satisfied implicitly, which is especially helpful when working with code you didn’t write.

Note

This wouldn’t be possible in a language like Java because java.time would need to explicitly say that it implements stardater.

With the stardater interface in place, listing 24.9 can be expanded with a sol type that satisfies the interface with methods for YearDay and Hour, as shown in the following listing.

Listing 24.10. Sol implementation: stardater.go
type sol int

func (s sol) YearDay() int {
    return int(s % 668)               1
}

func (s sol) Hour() int {
    return 0                          2
}

  • 1 There are 668 sols in a Martian year.
  • 2 The hour is unknown.

Now the stardate function operates on both Earth dates and Martian sols, as shown in the next listing.

Listing 24.11. In use: stardater.go
day := time.Date(2012, 8, 6, 5, 17, 0, 0, time.UTC)
fmt.Printf("%.1f Curiosity has landed
", stardate(day))        1

s := sol(1422)
fmt.Printf("%.1f Happy birthday
", stardate(s))                2

  • 1 Prints 1219.2 Curiosity has landed
  • 2 Prints 1086.0 Happy birthday

Quick check 24.2

Q1:

How is implicitly satisfying interfaces advantageous?

QC 24.2 answer

1:

You can declare an interface that’s satisfied by code you didn’t write, providing more flexibility.

 

24.3. Satisfying interfaces

The standard library exports a number of single-method interfaces that you can implement in your code.

Go encourages composition over inheritance, using simple, often one-method interfaces ... that serve as clean, comprehensible boundaries between components.

Rob Pike, “Go at Google: Language Design in the Service of Software Engineering” (see talks.golang.org/2012/splash.article)

As an example, the fmt package declares a Stringer interface as follows:

type Stringer interface {
    String() string
}

If a type provides a String method, Println, Sprintf, and friends will use it. The following listing provides a String method to control how the fmt package displays a location.

Listing 24.12. Satisfying stringer: stringer.go
package main

import "fmt"

// location with a latitude, longitude in decimal degrees.
type location struct {
    lat, long float64
}

// String formats a location with latitude, longitude.
func (l location) String() string {
    return fmt.Sprintf("%v, %v", l.lat, l.long)
}

func main() {
    curiosity := location{-4.5895, 137.4417}
    fmt.Println(curiosity)                       1
}

  • 1 Prints -4.5895, 137.4417

In addition to fmt.Stringer, popular interfaces in the standard library include io.Reader, io.Writer, and json.Marshaler.

Tip

The io.ReadWriter interface provides an example of interface embedding that looks similar to struct embedding from lesson 23. Unlike structures, interfaces don’t have fields or attached methods, so interface embedding saves some typing and little else.

Quick check 24.3

Q1:

Write a String method on the coordinate type and use it to display coordinates in a more readable format.

type coordinate struct {
    d, m, s float64
    h       rune
}

Your program should output: Elysium Planitia is at 4°30'0.0" N, 135°54'0.0" E

QC 24.3 answer

1:

// String formats a DMS coordinate.
func (c coordinate) String() string {
    return fmt.Sprintf("%v°%v'%.1f" %c", c.d, c.m, c.s, c.h)
}

// location with a latitude, longitude in decimal degrees.
type location struct {
    lat, long coordinate
}

// String formats a location with latitude, longitude.
func (l location) String() string {
    return fmt.Sprintf("%v, %v", l.lat, l.long)
}

func main() {
    elysium := location{
        lat: coordinate{4, 30, 0.0, 'N'},
        long: coordinate{135, 54, 0.0, 'E'},
    }
    fmt.Println("Elysium Planitia is at", elysium)            1
}

  • 1 Prints Elysium Planitia is at 4°30’0.0” N, 135°54’0.0” E

 

24.4. Summary

  • Interface types specify required behaviors with a set of methods.
  • Interfaces are satisfied implicitly by new or existing code in any package.
  • A structure will satisfy the interfaces that embedded types satisfy.
  • Follow the example set by the standard library and strive to keep interfaces small.

Let’s see if you got this...

Experiment: marshal.go

Write a program that outputs coordinates in JSON format, expanding on work done for the preceding quick check. The JSON output should provide each coordinate in decimal degrees (DD) as well as the degrees, minutes, seconds format:

{
    "decimal": 135.9,
    "dms": "135°54'0.0" E",
    "degrees": 135,
    "minutes": 54,
    "seconds": 0,
    "hemisphere": "E"
}

This can be achieved without modifying the coordinate structure by satisfying the json.Marshaler interface to customize the JSON. The MarshalJSON method you write may make use of json.Marshal.

Note

To calculate decimal degrees, you’ll need the decimal method introduced in lesson 22.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset