The typical suggestion is to define interfaces where you accept them in order to avoid issues like this. There are times where that doesn’t work though, so without seeing your code or really understanding why you want to use this factory, my best guess is that you have code like this:
package a
type Stringer interface {
String() string
}
type a struct{}
func (a) String() { return "a-impl" }
type b struct{}
func (b) String() { return "b-impl" }
func FactoryFunc(input string) Stringer {
if input == "" {
return a{}
}
return b{}
}
That is, you need to define the interface here because your factory is defined here as well, and it can’t return a discrete type, but later in your http handler you want something like:
package http
type Stringer interface {
String() string
}
type Handler struct {
StringerFactory func(input string) Stringer
}
One way to fix this is, like I said, to have package a
know about the http
package and do this:
package a
import "http"
type a struct{}
func (a) String() { return "a-impl" }
type b struct{}
func (b) String() { return "b-impl" }
func FactoryFunc(input string) http.Stringer {
if input == "" {
return a{}
}
return b{}
}
Another option is to define your interfaces in a somewhat neutral package. Eg we could move the Stringer
interface into a third package:
package b
type Stringer interface {
String() string
}
And now our code becomes:
package http
import "b"
type Handler struct {
StringerFactory func(input string) b.Stringer
}
package a
import "b"
type a struct{}
func (a) String() { return "a-impl" }
type b struct{}
func (b) String() { return "b-impl" }
func FactoryFunc(input string) b.Stringer {
if input == "" {
return a{}
}
return b{}
}
Now both packages use a neutral b.Stringer
and are entirely decoupled from one another. They just both rely on that b
package. Ben Johnson discusses this idea (well kinda) in https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1 and I go into a lot more detail while refactoring a web application and applying techniques in Ben’s article here: https://www.calhoun.io/apis-are-just-web-applications.
There are other options as well. For instance, you could define Stringer
in both packages and then in a third party package where you construct the two (eg a main
package) you could write a closure like:
import (
"a"
"http"
)
httpFactoryFn := func(input string) http.Stringer {
as := a.FactoryFunc(input)
return as.(http.Stringer) // convert it into the type you want
}