When to share an interface, and when not to.

145 views
Skip to first unread message

Jerry Londergaard

unread,
Sep 26, 2023, 8:16:58 AM9/26/23
to golang-nuts
Hello,

I'm trying to understand if I'm going overboard with defining interfaces where they are used (i.e am I re-defining them nu-necessarily). Here's an example. The interface is passed down from A->B->C where C (and only C) calls the DoThing() method of the interface:

----------------------------------------------
package A
import B

type DoThinger interface {
    DoThing()
}

func Run(dt DoThinger) {
    B.CallB(dt)
}
---------------------------------------------
package B
import C

type DoThinger interface {
    DoThing()
}

func Call(dt DoThinger) {
    C.Call(dt)
}
---------------------------------------------
package C

type DoThinger interface {
    DoThing()
}
func Call(dt DoThinger) {
    dt.DoThing()
}

---------------------------------------------

Do I need to be re-defining this interface everywhere? I guess
an alternative would be to just re-use the `C.DoThinger`, but I feel like this doesn't seem right as now A is directly importing C, which seems to 'break' the abstraction provided by B:

----------------------------------------------
package A
import (
    "B",
    "C"
)

func Run(dt C.DoThinger) {
    B.Call(dt)
}
```

We could obviously define the interface elsewhere in one place, and have A, B and C share use of it. This doesn't seem like the end of the world, as any of the consumers of that interface can define a new local interface if the shared one becomes unsuitable for them.

My current implementation (the example given), is to reduce coupling between packages. Should I be thinking about this differently? Should I be concerned that I'm not adhering to DRY?

burak serdar

unread,
Sep 26, 2023, 10:56:50 AM9/26/23
to Jerry Londergaard, golang-nuts
On Tue, Sep 26, 2023 at 6:18 AM Jerry Londergaard
<jlonde...@gmail.com> wrote:
>
>
> Do I need to be re-defining this interface everywhere? I guess
> an alternative would be to just re-use the `C.DoThinger`, but I feel like this doesn't seem right as now A is directly importing C, which seems to 'break' the abstraction provided by B:


It looks like you can at least get rid of the definition in package A.
It already has a dependency on B.

If an interface is common enough, you can define it in a common
package imported by other packages. A common pattern I use is to
define interfaces and models at the package root level and then create
additional packages under it for the actual implementation(s). That
is,

package C

type Something interface {...}

type Model struct {...}

Then, the package C/D would have the implementation of the package.

But at the end of the day, it is subjective. There is no single right answer.





>
> ----------------------------------------------
> package A
> import (
> "B",
> "C"
> )
>
> func Run(dt C.DoThinger) {
> B.Call(dt)
> }
> ```
>
> We could obviously define the interface elsewhere in one place, and have A, B and C share use of it. This doesn't seem like the end of the world, as any of the consumers of that interface can define a new local interface if the shared one becomes unsuitable for them.
>
> My current implementation (the example given), is to reduce coupling between packages. Should I be thinking about this differently? Should I be concerned that I'm not adhering to DRY?
>
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/a0436c1c-b24b-4c64-a4bc-12993c25c360n%40googlegroups.com.

Jerry Londergaard

unread,
Oct 1, 2023, 7:55:18 AM10/1/23
to golang-nuts
Thanks Burak. I think that makes sense. You can perhaps still think of that
approach as defining the interface "where it's used", even if it's not quite
as close to where it's used as is technically possible. It still has its caveats,
but it does seem better than defining it where it's implemented, which risks
misunderstanding the purpose of it entirely.
Reply all
Reply to author
Forward
0 new messages