📜 ⬆️ ⬇️

Dependency injection patterns. Part 2

Last time, the basic, most commonly used dependency injection patterns were disassembled. Today we analyze the remaining two, which are also used in the design of flexible systems. Today we will talk about implementation through the method and the surrounding context. Go!

Implementation through the method


How can you add dependencies to a class if they are different for each operation?
By passing as a parameter to a method. If each time you call a method, a different dependency is used, 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 as simple as the signature of the method below:

public void DoStuff(ISomeInterface dependency) 

Often the dependency will represent some kind of context for the operation, passed as an appropriate value:
')
 public string DoStuff(SomeValue value, ISomeContext context) 

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

When should method injection be used


The implementation of the method is best used when each call to the method specifies a different dependency. This may occur when the dependency itself represents a value.

There are several cases when it is more appropriate to pass a dependency through a method, and not through a constructor or a property:


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<int> comparer = Comparer<int>.Default; var list = new List<int> {3, 4, 1}; //  ""  list.Sort(comparer); var task = Task.Run(() => { }); TaskScheduler taskScheduler = TaskScheduler.Current; //  ""  ""  task.ContinueWith(t => { }, taskScheduler); 

Conclusion


Introduction through a method ( Method Injection ) is hardly a very common pattern in the context of dependency management, however, this is quite a common approach in libraries, as well as some design patterns for dragging an additional context or strategy that changes from operation to operation.

Ambient context


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

The surrounding context is available to any consumer through a static property or method. The 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 the writing of 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 the two is that the surrounding context provides only a single, strongly typed dependency instance, while Service Locator provides instances for any dependency you requested.

When to use the surrounding context?



Important features


Virtuesdisadvantages
Doesn't pollute APIImplicitness
Always availableDifficult to achieve correct implementation
-It may not work properly in some execution environments.


Implicitness When working with the surrounding context, you cannot, just looking at the interface, say with certainty whether this context is used by a particular class.

The complexity of the implementation. Proper implementation of the surrounding context can be enough. At a minimum, you must ensure that the context is always in a usable state — that is, when querying it, there should be no exception type NullReferenceExceptions only because one implementation of the context has been removed without replacing it with another.

Problems with execution in ASP.NET. If the surrounding context uses TLS , problems may arise when launching an application in ASP.NET , because there is a possibility of a change in the flow at certain points in the life cycle of Internet pages. It is not guaranteed that the data stored in TLS will be copied from the old stream to the new one. In this situation, you need to use the current HttpContext , not TLS, to store the specific request data.

Known methods of use



Conclusion


If you need to request a end-to-end 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 actual context with the default behavior that is used by all clients without explicit configuration.

This article ends the dependency injection patterns. We looked at 4 dependency injection patterns, 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 the algorithm for selecting 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.

Source: https://habr.com/ru/post/352574/


All Articles