Dependency injection patterns. Part 2

    The last time disassembled base, most frequently used patterns of dependency injection. Today we will analyze the other two, which are also used in the design of flexible systems. Today we’ll talk about implementation through the method and about the surrounding context. Go!

    Method injection


    How can dependencies be embedded in a class if they are different for each operation?
    By passing as a parameter to a method. If a different dependency is used with each method call, you can pass it through the method parameter.

    How it works


    The caller passes the dependency as a parameter to the method each time it is called. This procedure is no more complicated than the signature of the method below:

    public void DoStuff(ISomeInterface dependency)

    Often, a dependency will represent some kind of context for the operation, passed as the corresponding value:

    public string DoStuff(SomeValue value, ISomeContext context)

    If a service uses a dependency, it must first check for null .

    When to use the implementation of the method


    The implementation of the method is best used when each call to the method sets a different dependency. This can happen when the addiction itself is of some value.

    There are several cases where it is more appropriate to pass a dependency through a method, rather than through a constructor or property:

    • The method is static and other options are not suitable. In the same context used IFormatProvider in method double.Parse and other similar methods.
    • Dependence may vary from operation to operation. There is a variant of the Strategy pattern in which this strategy cannot be passed in the constructor arguments, since it is required by only one method and can change from call to call. A classic example of this strategy is the sorting strategy passed to the List method..Sort (). The same approach can be applied when a certain strategy is available at the place of the operation call, and not at the place of creation of the object.
    • Passing the local context for the operation. A number of design patterns, such as Team , Status, and some others, may use an additional external context to perform the operation. The same approach is intensively used in multi-threaded programming , when an additional context known to the calling code is transferred to the stream (or timer).

    Examples of using


    
    public interface ICommandContext
    {
    	int ProcessorCount { get; }
    }
    // CustomCommand
    public void Execute(ICommandContext context)
    {}
    Локальные стратегии
    IFormatProvider provider = new NumberFormatInfo  { NumberDecimalSeparator = ";" };
    // Задаем "стратегию" разбора double
    var value = double.Parse("1;1", provider);
    IComparer comparer = Comparer.Default;
    var list = new List {3, 4, 1};
    // Передаем "стратегию" сортировки
    list.Sort(comparer);
    var task = Task.Run(() => { });
    TaskScheduler taskScheduler = TaskScheduler.Current;
    // Задаем "стратегию" запуска "продолжения" задачи
    task.ContinueWith(t => { },
                    	taskScheduler);

    Conclusion


    Implementation through the Method Injection can hardly be called a very common pattern in the context of dependency management, however, this is a very common approach in libraries, as well as some design patterns for dragging an additional context or strategy from operation to operation into the operation.

    Surrounding context


    How can we make the dependency available in each module without including cross-cutting aspects of the application in each component API?

    The surrounding context is accessible to any consumer through a static property or method. A consuming class can use it as follows:

    public string GetMessage() 
    {   
     return SomeContext.Current.SomeValue;
    }

    To be useful in dependency injection scenarios, the context itself must be an abstraction . At the same time, it should be possible to modify it from the outside - this means that the Current property should allow writing values ​​(be writable ).

    public abstract class SomeContext
    {
       public static SomeContext Current
       {
          get
          {
             if (Thread.GetData(Thread.GetNamedDataSlot("SomeContext")) is SomeContext ctx)
               return ctx;
             ctx = Default;
             Thread.SetData(Thread.GetNamedDataSlot("SomeContext"), ctx);
             return ctx;
           }
           set => Thread.SetData(Thread.GetNamedDataSlot("SomeContext"), value);
        }
        private static SomeContext Default = new DefaultContext();
        public abstract string SomeValue { get; }
    }

    The surrounding context is similar in structure to the antipattern Service Locator . The difference between them is that the surrounding context provides only an instance of a single, strongly typed dependency, while the Latitude Service provides instances for any dependency you requested.

    When to use the surrounding context?


    • Need the ability to make context requests. If you only need to request data (for example, current time)
    • A suitable local default has been defined. It will be used implicitly, so it is important that the context function correctly.
    • Guaranteed availability is required. Even when a suitable local default is defined, it must be protected from being set to null .

    Important Features


    Advantagesdisadvantages
    No pollution APIImplicit
    Always availableIt’s hard to get the right implementation
    -May not work correctly in some runtime environments


    Implicit When working with the surrounding context, you cannot just look at the interface with confidence to say whether this context is used by a specific class.

    The complexity of the implementation. A correct implementation of the surrounding context may be sufficient. At a minimum, you must ensure that the context is always in a usable state - that is, when calling it, there should not be any exceptional situations like NullReferenceExceptions just because one implementation of the context was deleted without replacing it with another.

    Performance issues in ASP.NET. If the surrounding context uses TLS, problems may arise when running the application in ASP.NET , since it is likely that the flows will change at certain points in the life cycle of web pages. It is not guaranteed that the data stored in TLS will be copied from the old stream to the new one. In such a situation, the current HttpContext , not TLS , must be used to store the specific request data .

    Known Uses


    • Security is implemented through the System.Security.Principal.IPrincipal interface associated with each thread. You can get ( get ) or set ( set ) the current owner of the thread using the access methods from Thread.CurrentPrincipal .
    • Thread.CurrentCulture and Thread.CurrentUICulture allow you to access and modify the cultural context of the current operation. Many formatting APIs , such as parsers and type converters, implicitly use the current culture if no other is explicitly set.
    • The Trace class is not associated with a specific thread, but is shared throughout the application. By using the Trace.Write method , you can record a trace message from anywhere

    Conclusion


    If you need to request a cross-cutting aspect dependency to receive a response that is not included in the original interface, you can use the surrounding context , provided that it has a local default . This way you can combine the context itself with the default behavior that is used by all clients without explicit configuration.

    This article ends with dependency injection patterns. We looked at 4 patterns of dependency injection, such as implementation through a constructor, implementation through a property, implementation through a method, and the surrounding context. To decide which template to choose for your specific task, below is an algorithm for choosing the appropriate pattern proposed by Mark Siman. Use as often as possible. And if this algorithm does not help, choose the implementation of the constructor - in this case you will never make terrible mistakes.


    Also popular now: