Dependency Inject (DI) “friendly” library

后端 未结 4 656
天命终不由人
天命终不由人 2020-11-22 00:23

I\'m pondering the design of a C# library, that will have several different high level functions. Of course, those high-level functions will be implemented using the SOLID c

4条回答
  •  情书的邮戳
    2020-11-22 01:04

    This is actually simple to do once you understand that DI is about patterns and principles, not technology.

    To design the API in a DI Container-agnostic way, follow these general principles:

    Program to an interface, not an implementation

    This principle is actually a quote (from memory though) from Design Patterns, but it should always be your real goal. DI is just a means to achieve that end.

    Apply the Hollywood Principle

    The Hollywood Principle in DI terms says: Don't call the DI Container, it'll call you.

    Never directly ask for a dependency by calling a container from within your code. Ask for it implicitly by using Constructor Injection.

    Use Constructor Injection

    When you need a dependency, ask for it statically through the constructor:

    public class Service : IService
    {
        private readonly ISomeDependency dep;
    
        public Service(ISomeDependency dep)
        {
            if (dep == null)
            {
                throw new ArgumentNullException("dep");
            }
    
            this.dep = dep;
        }
    
        public ISomeDependency Dependency
        {
            get { return this.dep; }
        }
    }
    

    Notice how the Service class guarantees its invariants. Once an instance is created, the dependency is guaranteed to be available because of the combination of the Guard Clause and the readonly keyword.

    Use Abstract Factory if you need a short-lived object

    Dependencies injected with Constructor Injection tend to be long-lived, but sometimes you need a short-lived object, or to construct the dependency based on a value known only at run-time.

    See this for more information.

    Compose only at the Last Responsible Moment

    Keep objects decoupled until the very end. Normally, you can wait and wire everything up in the application's entry point. This is called the Composition Root.

    More details here:

    • Where should I do Injection with Ninject 2+ (and how do I arrange my Modules?)
    • Design - Where should objects be registered when using Windsor

    Simplify using a Facade

    If you feel that the resulting API becomes too complex for novice users, you can always provide a few Facade classes that encapsulate common dependency combinations.

    To provide a flexible Facade with a high degree of discoverability, you could consider providing Fluent Builders. Something like this:

    public class MyFacade
    {
        private IMyDependency dep;
    
        public MyFacade()
        {
            this.dep = new DefaultDependency();
        }
    
        public MyFacade WithDependency(IMyDependency dependency)
        {
            this.dep = dependency;
            return this;
        }
    
        public Foo CreateFoo()
        {
            return new Foo(this.dep);
        }
    }
    

    This would allow a user to create a default Foo by writing

    var foo = new MyFacade().CreateFoo();
    

    It would, however, be very discoverable that it's possible to supply a custom dependency, and you could write

    var foo = new MyFacade().WithDependency(new CustomDependency()).CreateFoo();
    

    If you imagine that the MyFacade class encapsulates a lot of different dependencies, I hope it's clear how it would provide proper defaults while still making extensibility discoverable.


    FWIW, long after writing this answer, I expanded upon the concepts herein and wrote a longer blog post about DI-Friendly Libraries, and a companion post about DI-Friendly Frameworks.

提交回复
热议问题