-1
\$\begingroup\$

I am trying to learn to code using Dependency Injection and Inversion of Control in Go.

Please consider the following code. I am basically creating an interface ListingManager and then an implementation of it. I am using the New() function as a means to initialise the implementation. While not show, I would probably add the fields of the implementation into the New() so it is is proper DI.

I am not sure if I have the correct use of New().

All these are in the same package and it is part of a library I am making. It can be consumed by some tools I am making. I also plan on creating mocks of the various fields for unit testing. I'm not sure where they should go, because New() is for the package. Would I have a separate mock package for each?

type ListingManagerImpl struct {

    Data data.DAL
    PriceHelper pricehelper.PriceHelper
    StockMan stockmanager.StockManager
}

func New() (ListingManagerImpl, error) {

    return ListingManagerImpl{}, nil
}

func (manager ListingManagerImpl) SyncListings(listings []domain.ArbListing) error {

    return nil
}

type ListingManager interface {

    SyncListings(listings []domain.ArbListing) error
}
\$\endgroup\$
1
\$\begingroup\$

If you want to get a real interface-based system going, where the implementation is opaque outside the package, then there are a few things you should do to bring the interface out of the struct.

First up, your struct should not be exposed, drop the capitalization... to listingManagerImpl.

This will immediately make your New() method fail because you can't really export the type outside the package now, but that's OK, because you should not be exporting the struct, you should export the interface. This makes the signature of New() become:

func New() (ListingManager, error) {

(Note the return type is the interface, not the struct).

Now, here comes the trick, you need to link your interface to a pointer of the struct, not the actual struct instance. Your full New() method will be:

func New() (ListingManager, error) {

    lm = &listingManagerImpl{}

    return lm, nil
}

here, note that we assign the address of the new struct, to the return value of the function, which is the interface, not the struct.

As long as your struct has implementations on all of the interface methods, then this will compile fine.

But, that will not compile yet, instead of having your implementation methods on the actual struct, you want it on the pointer to a struct... so, your methods like:

func (manager ListingManagerImpl) SyncListings(listings []domain.ArbListing) error {

    return nil
}

Should actually be:

func (manager *listingManagerImpl) SyncListings(listings []domain.ArbListing) error {

    return nil
}

Note that the function is now a method on a pointer to a listingManagerImpl.

Now, what reason would you have for exported fields in the struct? The struct should never leave the package in anything other than interface form, so make those fields lower-case (exception is if you serialize with encoding/json or something).

I would also shorten the struct name (I do that sort of thing...)

The full code would be something like:

type listMan struct {

    data data.DAL
    priceHelper pricehelper.PriceHelper
    stockMan stockmanager.StockManager
}

func New() (ListingManager, error) {
    lm := &listMan{}
    lm.data = ... //something
    lm.priceHelper = .... // something else.

    return lm, nil
}

func (m *listMan) SyncListings(listings []domain.ArbListing) error {

    return nil
}

type ListingManager interface {
    SyncListings(listings []domain.ArbListing) error
}
\$\endgroup\$
  • \$\begingroup\$ Great hustle, @rolfl Great primer. Reminded me of a few things I had forgotten. Coming from Java etc I am still getting used to pointers being a part of life. :) \$\endgroup\$ – jim Jan 25 '16 at 11:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.