Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can an external package implement an interface implicitly?

Tags:

interface

go

I'm writing a piece of code that relies on some implementation.
I want to decouple the implementation from my code, and make the implementation as independent as possible.
I thought of achieving this approach by using interfaces instead of concrete types, like so:

package mypackage

type MyType interface {
    Title() string
    Price() int
}

type TypeGetter interface {
    GetType() MyType
}

func MyHandler(tg TypeGetter) {
    t := tg.GetType()
    fmt.Printf("Title: %s, Price: %d", t.Title(), t.Price())
}

And an implementation might be something like this:

package external

// CustomType implicitly implements the MyType interface
type CustomType struct {
    title string
    price int
}
func (t CustomType) Title() string { return t.title }
func (t CustomType) Price() int { return t.price }


// CustomTypeGetter implicitly implements the TypeGetter interface. Or is it???
type CustomTypeGetter struct {
}
func (g CustomTypeGetter) GetType() CustomType {
    return CustomType{"Hello", 42}
}

Then, the code would do something like this:

package main

import "mypackage"
import "external"

func main() {
    tg := external.CustomTypeGetter{}
    mypackage.MyHandler(tg)            // <--- the compiler does not like this
}

I hope the example speaks for itself: I have no coupling between "mypackage" and the "external" package, which may be replaced, substituted my mocks for testing, etc.

The problem: the compiler complains that the call to MyHandler has an object that implements:
func GetType() CustomType, instead of:
func GetType() MyType

The only solution I found is to move the interface declarations (MyType and TypeGetter) to a third package, and then both "mypackage" and "external" packages can use it.
But I want to avoid that.

Isn't Go's concept of implicit implementation of interfaces contradict the idea of a third common package?

Is there a way to implement such thing, without binding the two packages together?

like image 811
user1102018 Avatar asked Dec 06 '25 02:12

user1102018


1 Answers

Isn't Go's concept of implicit implementation of interfaces contradict the idea of a third common package?

I think it does. Go authors introduced an implicit interface implementation to eliminate unnecessary dependencies between packages. That works well for simple interfaces like io.Reader, but you cannot apply it everywhere.

One of the language creators, Rob Pike, says that the non-declarative satisfaction of interfaces is not the essential part of the idea behind interfaces in Go. It's a nice feature, but not all elements of the language are practical or possible to use every time.

For complex interfaces, you need to import a package where the interface is defined. For example, if you want to implement an SQL driver that works with the sql package from the standard library, you must import the sql/driver package.

I would recommend not introducing interfaces at the beginning of your project. Usually, it leads to situations where you need to solve artificial problems like rewriting the interface each time you updates your understanding of the domain model. It is hard to come up with a good abstraction from the first attempt, and, in many cases, it is unnecessary, in my opinion.

I need to query external source for products. I don't care how the external sources store the data (db, file, network). I just need a "product" type. So it's either I define a Product type, forcing the external implementations to import and use it, or the Go way - define a Product interface and let the implementations implicitly implement this interface. Which apparently doesn't work

I see two loosely related goals here:

  1. Define an interface to swap implementations of the product source.
  2. A package that implements the product source should not import the package that defines the interface.

From my experience, I would recommend doing point 1 only when you have at least one working implementation of the product source service.

Point 2 is not always possible to achieve, and it is fine; please see the example from the standard Go library above.

P.S. Please, consider not creating Product interface. While it does makes sense to come up with the PorductSource interface eventually, Product is most probably just a set of data; struct is a perfect way to represent such information. Please, see this very relevant code smaple and this article for inspiration.

like image 140
Andrey Dyatlov Avatar answered Dec 08 '25 14:12

Andrey Dyatlov