I have multiple types and I want to pass around data of those types, like store them in a variable and pass them to functions:
type Pizza struct {
Toppings []string
Diameter int
}
type Steak struct {
Weight float64
Doneness string
}
type Car struct {
Speed int
}
type Chair struct {
}
func main() {
var favoriteFood interface{}
favoriteFood = Pizza{
Diameter: 20,
}
cook(favoriteFood, Chair{})
}
func cook(food interface{}, vehicle interface{}) {
fmt.Print("Cooking ")
if pizza, ok := food.(Pizza); ok {
fmt.Println("a " + strconv.Itoa(pizza.Diameter) + " cm pizza")
}
if steak, ok := food.(Steak); ok {
fmt.Println("a " + steak.Doneness + " steak")
}
if car, ok := vehicle.(Car); ok {
fmt.Print(" in a car at " + strconv.Itoa(car.Speed) + " km/h")
}
if _, ok := vehicle.(Chair); ok {
fmt.Print(" on a chair")
}
}
I would like cook()
to accept Pizza
and Steak
for food
but not Car
.
Since interfaces are defined by their methods and my types don't share any common methods, I can't let them "implement" an interface.
I could also introduce an identifying receiver function, like this:
type Food interface {
IsFood() bool
}
func (f *Pizza) IsFood() bool { return true }
func (f *Steak) IsFood() bool { return true }
Is that common/idiomatic?
Go provides interfaces to support your requirements. As an example, you can have an interface called Food
:
type Food interface {
Cook()
}
Now let Pizza
and Steak
satisfy this interface:
func (p *Pizza) Cook() {
// use Pizza's fields
// ...
}
You can now have a CookFood
method that can accept both Pizza
and Steak
:
func CookFood(f Food) {
// ...
f.Cook()
// ...
}
You can call this like:
func main() {
var favoriteFood = Pizza{
Diameter: 20,
}
CookFood(favoriteFood)
}
Calling CookFood
with Car
(which does not implement Cook
method) would make compiler throw an error, providing proper type safety.
Ok, I changed your code making use of interface: here
The interface part:
type Food interface {
CookedInfo() string
}
func (p Pizza) CookedInfo() string {
return "a " + strconv.Itoa(p.Diameter) + " cm pizza"
}
func (s Steak) CookedInfo() string {
return "a" + s.Doneness + " steak"
}
type Location interface { //chair is hardly a vehicle
Where() string
}
func (c Chair) Where() string {
return "on a chair"
}
func (c Car) Where() string {
return "in a car at " + strconv.Itoa(c.Speed) + " km/h"
}
The whole point (well, maybe not whole but very closely) of interface
is to hide implentation details. For example, a Pizza
's size, name or other attribute are such details, the function cook
care not about them. Nor does it care about whether a steak is over-done, nor whether you are cooking a dragon, as a dragon has a method CookedInfo
. And each food implents its own method to provide info of being cooked, used by Cook
. That is it. It prevents you to write an infinate list of testing types (which even become impossible when the function is being exported as in a lib) and simplify your logic.
I’ve slightly modified previous example - https://play.golang.org/p/uN1m7pNZbsv
You may try to use embedding and embed interface Food
into a concrete structures Pizza
and Steak
but not car.
type Food interface {
Cook()
}
type Pizza struct {
Food
Diameter int
}
func Cook(food Food) {
...
}
This way only structs matching Food
will be accepted.
But do not forget Go is duck-typed language, so if you suddenly implement method Cook
on Chair
you may start cooking it ) However it doesn’t embed Food
explicitly.