Typical example with added getName()
function.
I wonder how can I do not write getName()
for circle
and rect
twice?
package main
import "fmt"
import "math"
// Here's a basic interface for geometric shapes.
type geometry interface {
area() float64
perim() float64
getName() string
}
// For our example we'll implement this interface on
// `rect` and `circle` types.
type rect struct {
width, height float64
name string
}
type circle struct {
radius float64
name string
}
// To implement an interface in Go, we just need to
// implement all the methods in the interface. Here we
// implement `geometry` on `rect`s.
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
func (r rect) getName() string {
return r.name
}
// The implementation for `circle`s.
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
func (c circle) getName() string {
return c.name
}
// If a variable has an interface type, then we can call
// methods that are in the named interface. Here's a
// generic `measure` function taking advantage of this
// to work on any `geometry`.
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
fmt.Println(g.getName())
}
func main() {
r := rect{width: 3, height: 4, name: "rect5"}
c := circle{radius: 5, name: "circle2"}
// The `circle` and `rect` struct types both
// implement the `geometry` interface so we can use
// instances of
// these structs as arguments to `measure`.
measure(r)
measure(c)
}
You could - and potentially should - embed your geometry type in another struct type that includes the name. Unless the name is "circle" or "square" or what have you, the name isn't actually related in any way to the geometry itself. So you could have:
type namedGeometry struct {
geometry
name string
}
func (ng *namedGeometry) getName() string {
return ng.name
}
Which would serve the same purpose, remain DRY, and maintain separation of concerns. With geometry
embedded, you can still call the geometry
methods on a namedGeometry
instance.
I would take a similar approach to Adrian's, but I would instead embed a base type, which holds common functionality, in your other types e.g.
type baseShape struct {
name string
}
func (s *baseShape) getName() string {
return s.name
}
type rect struct {
width, height float64
baseShape
}
type circle struct {
radius float64
baseShape
}
After doing this, you only need to implement the functions that differ between the shapes on a per type basis.