Factory function Interface <-> Implemantion

Hi,

in my concrete usecase I need a particular dependency for ech http-request. So I thought about this idea:

package main

import "fmt"

type I interface {
	print()
}

type S struct {}
func (s S) print() {
	fmt.Printf("%T", s)
}

func factory() S {
    return S{}
}

func Ifactory() func() I {
    return factory
}

The problem of course is that the return of I conflicts with the return of S. No I am wondering if there is a way for this.

I don’t understand why you would declare the function to return the interface type, as an interface only defines the signature of a struct and never contains any actual implementation. On top of the way interfaces work in Go, which is more to do with defining parameters than setting return types. Why is return type of factory not working for you? So long as whatever consumes the return takes a parameter of the interface type it doesn’t matter as long as the struct conforms to the interface.

Have a read of this article: https://medium.com/@cep21/what-accept-interfaces-return-structs-means-in-go-2fe879e25ee8

1 Like

Hi,

to be more precise an example (uses another interface instead of a concrete struct):

package http

type LocallyDefinedInterface interface {
    func DoSth()
}

type  GiveMeAnIDependingOnTheInput func(input string) LocallyDefinedInterface

type Handler struct {
    Factory GiveMeAnIDependingOnTheInput
}

package whereTheHandlerIsCreated

type LocallyDefinedInterface interface {
    func DoSth()
}

type IImpl struct {}

func(i Iimpl DoSth() {
    // does implement a functionality
}

func factoryFunction(input string) LocallyDefinedInterface {
    return IImpl{}
}

func NewHandler() http.Handler {
    return http.Handler{factoryFunction}
}

Do you see my point? I want to use the advantage of implicit implemented interfaces to decouple the package http from the other one.

I think what you are saying is that factoryFunction doesn’t work according to the compiler because it returns a whereTheHandlerIsCreated.LocallyDefinedInterface instead of returning an http.LocallyDefinedInterface. this is probably confusing because these two types appear to be the same functionality wise, but are different according to the compiler.

Without getting into whether this may or may not be a good idea, try this:

func factoryFunction(input string) http.LocallyDefinedInterface {
    return IImpl{}
}

Hi,

this is probably confusing

It is not confusing - I am absolutely aware of this. The question was if I maybe do not know a particular way to work around this.

The problem on your example is that the http package should not be aware of IImpl

The http package isn’t aware of IImpl in my example. The package whereTheHandlerIsCreated knows about the http package, but not vice versa.

It sounds like what you are asking is given a type A that is an interface, is there a way to write a function that satisfies func() A without explicitly stating that it returns A, to which the answer is no.

1 Like

Ah I see - Hm - So far so good. But what if different packages are using factoryFunction and they have there own LocallyDefinedInterface?

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
}

Thank you joncalhoun for your explanation. Unfortunately I was so far already :-/ I thought there is an option without having the packages to know each other or having this “helper-package”

Nevertheless thank you for your effort.

Hi Marc,

In less abstract terms, what are you trying to achieve?

I have the feeling you are not using interfaces “the Go way”: you should always define interfaces where you use them (as variables) not where you implement them.

I think the problem of the two packages not knowing about each other is uninteresting: what if you just return concrete types (please always do that, or you might have problems when returning nil), and then test whether the returned type implements an interface when you want to use it.

Again, if you give us a concrete example we can help you make it more idiomatic.

I do indeed:

package http

type locallyDefinedInterface interface {
    print()
}

package someOtherPackage

type locallyDefinedInterface interface {
    print()
}

In package whereTheHandlerIsCreated the factory function needs to return an interface as well since in it we need to distinguish between the different implementations

package someOtherPackage

type locallyDefinedInterface interface {
    print()
}

func factory($input string) locallyDefinedInterface {
    switch $input {
        case 'this':
            return <ConcreteImplementationA>
        case 'that':
            return <ConcreteImplementationB>
    }
}

btw: We have a multi tenancy system and for the different tenants we have different kind of storages. This is the reason why I need to distinguish netween different types of them.

Okay, I see what the problem is. Others gave you a right answer (for an interface to return an interface they need to know about each other between packages), but there is another way.

You could invert the logic and define an interface “persist(storageObject)” and make your tenant types satisfy it. Would that work?

Hi,

I am afraid I do not get completely what you mean. You mean instead of providing a factory function a wrapper for the storage?

If I understand correctly yes.

I imagine to find a “tenant” type, a “storageDisk” and “storageCloud” types that satisfy the “storage” interface.

Your storage interface should have a “store(storageObject)” which writes the file to disk or PUTs it via HTTP, depending on which storage type you are writing.

Then if you just pass the object to the “tenant.storage”, it should work.

1 Like

That’s a propper solution I guess. Thanks for your input.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.