Chapter 10 - Encapsulation and Embedding

Exercise 1: Encapsulation

It shouldn’t be possible, but users of the rectangle type are setting its length and width fields to negative values. We need to encapsulate these fields and add getter and setter methods to control access to them.

Update the rectangle type so that the program below will compile, run, and produce the output shown.

  • Move the rectangle type and all its existing methods to a new shapes package.
  • You’ll need to rename the type and its existing methods so they’re exported from the new package.
  • Leave the struct fields unexported, however, so they can only be accessed through your getter and setter methods.
  • The setter methods should return a single error value. If any value less than 0 is passed, return an error value indicating the value is invalid. Otherwise, return nil.
package main

import (
	"fmt"
	"log"
	"shapes"
)

// check logs an error and exits if the error is not nil.
func check(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	var r shapes.Rectangle
	err := r.SetLength(4.2)
	check(err)
	fmt.Println("Length:", r.Length())
	// Set width to an invalid value.
	err = r.SetWidth(-2.3)
	check(err)
	fmt.Println("Width:", r.Width())
}

Output:

Length: 4.2
2019/05/31 18:46:50 invalid width: -2.300000
exit status 1

Solution

$GOPATH/src/shapes/rectangle.go

package shapes

import "fmt"

// Capitalize the type name, so it's exported.
type Rectangle struct {
	// DON'T capitalize the field names; we want these
	// unexported so they're only accessible through
	// getter and setter methods.
	length float64
	width  float64
}

// Length returns the value of the length field. Has a
// pointer receiver for consistency with the other methods.
func (r *Rectangle) Length() float64 {
	return r.length
}

// SetLength sets the length field. Must have a pointer
// receiver so it's not modifying a copy of the struct.
// Returns an error if an invalid value was passed,
// nil otherwise.
func (r *Rectangle) SetLength(length float64) error {
	if length < 0 {
		return fmt.Errorf("invalid length: %f", length)
	}
	r.length = length
	return nil
}

// Width returns the value of the width field. Has a
// pointer receiver for consistency with the other methods.
func (r *Rectangle) Width() float64 {
	return r.width
}

// SetWidth sets the width field. Must have a pointer
// receiver so it's not modifying a copy of the struct.
// Returns an error if an invalid value was passed,
// nil otherwise.
func (r *Rectangle) SetWidth(width float64) error {
	if width < 0 {
		return fmt.Errorf("invalid width: %f", width)
	}
	r.width = width
	return nil
}

// Capitalize the method name, so it's exported.
func (r *Rectangle) MakeSquare() {
	if r.length > r.width {
		r.length = r.width
	} else {
		r.width = r.length
	}
}

// Capitalize the method name, so it's exported.
func (r *Rectangle) Info() {
	fmt.Println("Length:", r.length)
	fmt.Println("Width:", r.width)
}