Problem overview
A shop ships parcels in three ways. Shipping is the standard option, and Express and Economy are two more. All three answer the same two questions: name() says what the option is called, and cost(weight) says what a parcel of that many kilograms costs. Only the answers differ.
Shipping: name"Standard", cost5 + weight.Express: name"Express", cost10 + 2*weight.Economy: name"Economy", cost3 + weight/2, with the division rounding down.
For a parcel of 4 kilograms, the three costs are 9, 18 and 5. Express and Economy must embed Shipping and then give themselves methods with the same names.
Approach
Go has no classes and no inheritance. What it has is embedding: when a struct lists another type on a line of its own, without a field name, it gets that type's fields and methods as if they were its own. That is why Express and Economy start with just Shipping inside the braces.
When the outer struct defines a method with the same name as an embedded one, the outer method wins. It shadows the embedded one: on an Express value, name() finds Express's own method first. The embedded method is not gone, and you can still reach it by naming the embedded field.
Because every shipping type has the same two methods, the code that prints a parcel's line calls the same name() and cost(weight) whichever of the three types it created, and never cares how the answer is made. That is the point of giving the three types matching method names and signatures.
Step by step
- Write
Shipping, an empty struct. It needs no fields, because its answers never change. Itsnamereturns"Standard". - Its
costreturns5 + weight: a base price plus one per kilogram. - In
Express, writeShippingalone on a line. That is the embedding. - Give
Expressits ownnameandcost. Each one shadows the embedded method of the same name. If you left outcost, an express parcel would quietly get the standard price. - Do the same for
Economy: embedShipping, then writenameand acostthat returns3 + weight/2.
The solution in Go
type Shipping struct{}
func (this Shipping) name() string {
return "Standard"
}
func (this Shipping) cost(weight int) int {
return 5 + weight
}
type Express struct {
Shipping
}
func (this Express) name() string {
return "Express"
}
func (this Express) cost(weight int) int {
return 10 + 2*weight
}
type Economy struct {
Shipping
}
func (this Economy) name() string {
return "Economy"
}
func (this Economy) cost(weight int) int {
return 3 + weight/2
}
The receiver is named this here. Go lets you pick any receiver name; the lessons use this, and the code never reads it, since none of these types has fields.
Using the structs
Create a value of each type and call the methods:
e := Express{}
c := Economy{}
fmt.Println(Shipping{}.name(), Shipping{}.cost(4))
fmt.Println(e.name(), e.cost(4))
fmt.Println(c.name(), c.cost(4))
fmt.Println(c.cost(7), c.cost(1))
This prints:
Standard 9
Express 18
Economy 5
6 3
The last line shows the rounding: 7/2 is 3, so the cost is 6, and 1/2 is 0, so the cost is 3. Both operands of weight/2 are int, so Go drops the remainder.
The embedded method is still there
Shadowing hides the embedded method; it doesn't delete it. Name the embedded field, which has the same name as its type, and you get the original:
e := Express{}
fmt.Println(e.name(), e.cost(4))
fmt.Println(e.Shipping.name(), e.Shipping.cost(4))
Express 18
Standard 9
Edge cases and common mistakes
- Forgetting one override. An
Expresswith its ownnamebut nocostcompiles and runs, and printsExpresswith the standard price. Write both methods in both types. - Weight 0. Only the base prices are left: 5, 10 and 3.
- Odd weights in Economy. The halving rounds down, so weights 6 and 7 both cost 6.
- Expecting inheritance. Embedding copies no behaviour into
Express; it forwards calls. A method ofShippingthat callsthis.name()always callsShipping's ownname, even when the value came from anExpress. With a helperlabel()onShippingthat returns"Shipping by " + this.name(),Express{}.label()givesShipping by Standard. Go has no virtual dispatch through embedding, so write the methods you need on each type.
Testing
Try one parcel through all three types, then the extremes. A weight of 4 gives 9, 18 and 5. A weight of 0 gives 5, 10 and 3. A weight of 1000 gives 1005, 2010 and 503. Checking the odd weights matters most for Economy: if 7 kilograms does not cost 6, the division is the problem. If Express prints a standard price, one of its methods is missing and the embedded one is answering instead.
Why this shape
The three types share one idea, shipping a parcel, and each answers it in its own way. Embedding Shipping says that Express and Economy start from the standard option. Anything they don't redefine, they get for free. In this lesson they redefine everything, so the embedding shows the mechanism more than it saves code. It pays off when a type changes only one of several methods.
Key takeaways
- Embedding a type means writing its name alone in the struct.
- An outer method with the same name shadows the embedded one, and
value.Shipping.name()still reaches the original. - Embedded methods run with the embedded value as the receiver, so they never see the outer type's overrides.
