Go语言依赖注入 go语言import( 二 )


TheServeris dependent on thePersonServiceand theConfig .
Ok, we know all the components of our system. Now how the hell do we actually initialize them and start our system?
First, let’s write ourmain()function the old fashioned way.
First, we create ourConfig . Then, using theConfig , we create our database connection. From there we can create ourPersonRepositorywhich allows us to create ourPersonService . Finally, we can use this to create ourServerand run it.
Phew, that was complicated. Worse, as our application becomes more complicated, ourmainwill continue to grow in complexity. Every time we add a new dependency to any of our components, we’ll have to reflect that dependency with ordering and logic in themainfunction to build that component.
As you might have guessed, a Dependency Injection framework can help us solve this problem. Let’s examine how.
The term “container” is often used in DI frameworks to describe the thing into which you add “providers” and out of which you ask for fully-build objects. Thediglibrary gives us theProvidefunction for adding providers and theInvokefunction for retrieving fully-built objects out of the container.
First, we build a new container.
Now we can add new providers. To do so, we call theProvidefunction on the container. It takes a single argument: a function. This function can have any number of arguments (representing the dependencies of the component to be created) and one or two return values (representing the component that the function provides and optionally an error).
The above code says “I provide aConfigtype to the container. In order to build it, I don’t need anything else.” Now that we’ve shown the container how to build aConfigtype, we can use this to build other types.
This code says “I provide a*sql.DBtype to the container. In order to build it, I need aConfig . I may also optionally return an error.”
In both of these cases, we’re being more verbose than necessary. Because we already haveNewConfigandConnectDatabasefunctions defined, we can use them directly as providers for the container.
Now, we can ask the container to give us a fully-built component for any of the types we’ve provided. We do so using theInvokefunction. TheInvokefunction takes a single argument – a function with any number of arguments. The arguments to the function are the types we’d like the container to build for us.
The container does some really smart stuff. Here’s what happens:
That’s a lot of work the container is doing for us. In fact, it’s doing even more. The container is smart enough to build one, and only one, instance of each type provided. That means we’ll never accidentally create a second database connection if we’re using it in multiple places (say multiple repositories).
Now that we know how thedigcontainer works, let’s use it to build a better main.
The only thing we haven’t seen before here is theerrorreturn value fromInvoke . If any provider used byInvokereturns an error, our call toInvokewill halt and that error will be returned.
Even though this example is small, it should be easy to see some of the benefits of this approach over our “standard” main. These benefits become even more obvious as our application grows larger.
One of the most important benefits is the decoupling of the creation of our components from the creation of their dependencies. Say, for example, that ourPersonRepositorynow needs access to theConfig . All we have to do is change ourNewPersonRepositoryconstructor to include theConfigas an argument. Nothing else in our code changes.
Other large benefits are lack of global state, lack of calls toinit(dependencies are created lazily when needed and only created once, obviating the need for error-proneinitsetup) and ease of testing for individual components. Imagine creating your container in your tests and asking for a fully-build object to test. Or, create an object with mock implementations of all dependencies. All of these are much easier with the DI approach.

推荐阅读