📜 ⬆️ ⬇️

Introducing dependencies in .Net Mark Siman 2 - Implementing a constructor, lifetime

Dependencies between application layers | Designer implementation, lifetime | Cross-cutting aspects of the application, interception, decorator

We continue the struggle for weak connectivity. In the previous article we looked at the dependencies between the layers of the application, going to the smaller forms.

Aggregation, designer implementation


Objects / classes of the system, as well as layers, interact with each other. There are dependencies between classes too.
')
For example, in Listing 1, MyService uses MyDataContext (EF) - it has a dependency MyDataContext.

class MyService { public void DoSomething() { using(var dbCtx = new MyDataContext()) { //  dbCtx } } }  1.   MyService  MyDataContext 

The code above has flaws:

- Dictator antipattern is used: MyService itself creates and controls the lifetime of its dependency MyDataContext.
- Dependency Inversion Principle (DIP) was violated (where in a “science-based” article without SOLID): MyService depends on the specific implementation of MyDataContext, it would be better to use the interface / abstract class.

Dependency Inversion Principle (DIP)

In fact, a synonym for the requirement of "Programming in accordance with the interface, but not with a specific implementation."
(quote from the book)

Improve the code with aggregation - Listing 2:

 class MyService { private readonly IRepository Repository; public MyService(IRepository repository){ if(repository == null) throw new ArgumentNullException(nameof(repository)); Repository = repository; } public void DoSomething() { //  Repository } }  2. . MyService          Repository 

Retreat:

A good article about the aggregation and composition was written by Sergey Teplyakov. Among other things, the article will teach you to draw smart schemes. As a spoiler: which scheme describes the aggregation?

Schemes of composition and aggregation
Figure 1. Composition and aggregation schemes

Let's return to listing 2. This is the introduction of dependency, with the best option being “Implementing a constructor”. The connectivity has decreased, but the question has appeared: how do you call Dispose of the repository? Remember in Listing 1 used Using?

The class that transfers control of its dependencies loses more than the ability to choose specific implementations of abstractions. It also loses the ability to control both the moment the instance is created and the moment when the instance becomes unavailable.
(quote from the book)

An interesting note: if a class has more than 4 dependencies (more than 4 constructor parameters), this is a reason to think about refactoring. It seems that the object performs too many functions, the Single Responsibility Principle (SRP - again SOLID) is violated.

Dependency lifetime


Answering the question “how do you call Dispose of the repository?” Mark proposes a compromise. MyService does not need to know about the features of the implementation of the IRepository, including the need to release resources. Those. This definition of the IRepository is undesirable:

 interface IRepository : IDisposable { void DeleteProduct(int id); } 

Besides the fact that such an interface opens up to the consumer (MyService) a part of knowledge about a specific implementation, it also imposes a restriction on possible implementations - they must implement IDisposable (they may not need it).

And in the implementation of the IRepository, this knowledge of the implementation is allowed - Listing 3.

 class SqlRepository : IRepository { IDataContextFactory DbContextFactory; public SqlRepository(IDataContextFactory dbContextFactory) { if(dbContextFactory == null) throw new ArgumentNullException(nameof(dbContextFactory)); DbContextFactory = dbContextFactory; } public void DeleteProduct(int id); { using(var dbCtx = DbContextFactory.Create()) { //  dbCtx } } }  3.  IRepository      

Addition (not the most important): SqlRepository controls the lifetime of the DataConext, but the creation has been moved to the factory.

There’s a tradeoff: yes, the SqlRepository controls the lifetime of the DataContext, but this does not affect the rest of the code.

The above is a good solution, but it is not always possible to apply it. For example, you need a transaction:

 public void DoSomething(int productId) { this.Repository.DeleteProduct(productId); this.Repository.DeleteHistory(productId); }  4.          

If the deletion of the history is completed with an error, the deletion of the product must be canceled (cleverly, this is the Unit of Work pattern). Then you cannot commit to the database separately in the DeleteProduct and DeleteHistory methods. How to be? You know where to look for the answer.

To be continued


We considered the main method of implementing envy: an aggregation implemented with the help of an implementation designer. We touched the theme of managing the lifetime of objects. Until new meetings.

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


All Articles