These kinds of problems are usually the result of:
- Premature interfaces
- Fake interfaces, or "bubble-wrapping" (see my rant here)
- Parallel interfaces
Your error you have is most specifically related to the 3rd issue, but each is present in your example design here.
Good interfaces should convey (in their name and their method signatures) a set of behaviour. The name of the interface should be implied by method(s) it contains, as the name is simply supposed to capture the essence of that behaviour.
The only interface seemingly needed for this purpose is:
type Mover interface {
Move() MoveResult
}
For MoveResult, you can do this (exchange float with int if you like):
type MoveResult struct {
Distance, Height float64
}
This type can potentially function just fine as a struct, given the following assumptions:
- Move results are just data points (they don't need to be dynamic; you can set the values and leave them)
- "Extra" data like Height function properly with a zero value when it's not specified.
Now, implement the bird type:
type Bird struct {
// some internal data
}
func (b *Bird) Move() MoveResult {
// some movement calculations
return MoveResult{Distance: howFar, Height: howHigh}
}
Now implement foo:
func foo(m Mover) float64 {
return m.Move().Distance
}
Now use it in main:
func main() {
foo(&Bird{})
}
Now we can add other kinds of Mover
s to the program and use them with foo
without issue.
Addressing your comment, where Animal
, AnimalMoveResult
and foo
are all external and you can't modify them:
How I'm posing the problem with my understanding: There is this function foo
, designed to accept an interface called Animal
, which is characterized by how it returns an proprietary type. You want to use your type Bird
as an Animal
, but you can't fit all of the behaviour you want into that proprietary return type.
So how to resolve this?
Implement your own, wider return type
type birdMoveResult struct {
// some internal data
}
func (r birdMoveResult) GetDistance() int {
// get the distance
}
func (r birdMoveResult) GetHeight() int {
// get the height
}
Now, implement the Bird type
type Bird struct {
// some internal data
}
func (b *Bird) Move() AnimalMoveResult {
var result birdMoveResult
// calculate the move result
return birdMoveResult
}
Notice that Move
returns the AnimalMoveResult
type, so now Bird
will satisfy the Animal
interface. We are able to do this because birdMoveResult
satisfies the AnimalMoveresult
type, so returning it here is legal.
The remaining issue is that Bird
's Move
method narrows the wide interface, we've effectively lost that new functionality you added. In order to get it back while still satisfying the Animal
interface, we can't change the Move
method signature at all. Here are 2 possible workarounds.
- When using your bird type, you can type-assert the result to gain access to the extra method
var b Bird
// ...
height := b.Move().(birdMoveResult).GetHeight()
- Add a new method to
Bird
that returns the wider type, leaving the existing Move
with the same signature
func (b *Bird) MoveFull() birdMoveResult {
var result birdMoveResult
// calculate the move result
return birdMoveResult
}
func (b *Bird) Move() AnimalMoveResult {
return b.MoveFull()
}
height := b.MoveFull().GetHeight()
Either of these will work, it's mostly a matter of preference.
Best Answer
As the FAQ mentions
In your case, you would satisfy both interfaces.
You can you the can test whether an object (of an interface type) satisfies another interface type
A.Doer
, by doing:The OP adds:
Then you need to implement a wrapper around you object:
DoerA
, which has your objectC
as a field, and implementA.Do()
in a manner that satisfy howA.Do()
is supposed to workDoerB
, which has your same objectC
as a field, and implementB.Do()
in a manner that satisfy howB.Do()
is supposed to workThat way, you will know which Doer to pass to a function expecting an
A.Doer
or aB.Doer
.You won't have to implement a
Do()
method on your original objectC
, which would be unable to cope with the different logic ofA.Do()
andB.Do()
.