aelf Tech Talks: Dependency Injection Part 3

SOLID Design Principles

## Interface Segregation Principle （ISP）

> Clients should not be forced to depend upon interfaces that they don`t use.

The dependency of one class to another one should be minimal.

By following this principle, you prevent bloated interfaces that define methods for multiple responsibilities. As explained in the Single Responsibility Principle, you should avoid classes and interfaces with multiple responsibilities because they change often and make your software hard to maintain.

For a cross-contract call, the aelf team have provided a contract standard called AElf Contract Standard, ACS for short, and each ACS defines some Interface. When doing cross-contract calls, you don’t have to worry about which interface is defined in a particular ACS, you only need to provide a contract that implements the desired ACS. On the other hand, each contract can implement multiple ACSs. When it is called by other contracts, it may only be interested in one of the ACSs it implements. Thus you rely solely on this ACS and will not take this contract. Other methods are exposed to cross-contract calls. This is just an explanation of the principle of interface isolation.

## Dependency Inversion Principle （DIP）

> High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.

An important detail of this definition is, that high-level and low-level modules depend on the abstraction. The design principle does not just change the direction of the dependency, as you might have expected when you read its name for the first time. It splits the dependency between the high-level and low-level modules by introducing an abstraction between them. So in the end, you get two dependencies:

the high-level module depends on the abstraction the low-level depends on the same abstraction

This might sound more complex than it often is. If you consequently apply the Open/Closed Principle and the Liskov Substitution Principle to your code, it will also follow the Dependency Inversion Principle.

In a word: interface-oriented programming. We can put the interface in the lower-level module, and then provide the implementation of this interface in the high-level module. Communication between different high-level modules is also done through the interfaces in the underlying modules. For example, we treat the log as a high-level module. There is only one ILogger interface in the underlying module, which provides some methods for printing different levels of logs. The specific printing implementation is placed in the log module. Other modules may rely on the log module to log, you can use NLog, you can also use log4net, the log module becomes a dependency, and which one to use depends on how we manually inject this dependency. Here finally talked about dependency injection. So how do you inject? Next we will discuss the Composition Root.

As you have seen in the example project, you only need to consequently apply the Open/Closed and the Liskov Substitution principles to your code base. After you have done that, your classes also comply with the Dependency Inversion Principle. This enables you to change higher-level and lower-level components without affecting any other classes, as long as you don’t change any interface abstractions.

# Difference between DI, IoC and DIP

The DI here is DI technology, not a DI container. The dependency injection framework we used in the development, such as Autofac, Ninject, etc., belongs to the DI container.

The Composition Root is a design pattern. The key to using this design pattern is that we have to find the location where the Composition Root is placed. Fortunately, the predecessors gave us a solution: as close as possible to the application entry point, so, what is the combination root for? In short, configuration — although if we simply say ‘configuration’, this can be misleading, the combined root is a DI container — in other words, setting a dependent relationship. That is, which abstract type corresponds to which specific type in this application, it needs to be set in some way in the composition root. The abstract type mentioned here can be an interface or an abstract class in C#. In fact, it is not important to abstract in that way in the practice of applying DI. We only need to set the abstract type in the combined root to correspond to the specific Type dependencies. This setting can directly call methods such as Register or AddSingleton in the DI container. However, applying DI technology does not necessarily require the use of a DI container.

We rethink the concept of combination roots from the perspective of “beginning from the end”: It is conceivable that a complete application that can be used for production must contain a large number of services, and if we want the implementation of this application to be loosely coupled, the interface isolation principle will be used. When we need to use other services in a service, we do not know what other services will be implemented when writing the code, so in the code, we directly call an abstraction. After the application is running, there must be an assembly process before the actual call to the abstract. The so-called assembly is based on the abstract type in the code, giving a concrete implementation at runtime, which requires us to manually provide a mapping relationship between abstract types and specific types. The combined root is where the relationship is provided manually. The easiest console application will require cooperation between each component in order to run. There are many kinds of high-level components that rely on the underlying components. At present, the most commonly used is the constructor injection, and later will say other injection methods. You can not throw an interface into the applicable constructor injection, you must manually create a new implementation. This operation is to set the abstract type and the specific type of dependencies.

We look back at the Open Closed Principle, when a certain function needs to be modified, we can not change the original implementation, but provide a realization with the decoration mode, and then in the combination root, set the corresponding dependency to a new implementation. You only need to add the code and change the combination root. If the new implementation has problems, you can withdraw it at any time until you know that the old implementation is not needed. To eliminate this redundant relationship, a refactoring step is implemented. People are always unreliable, especially when they reconcile their work with others. So we need to add enough unit tests before refactoring, and even write unit tests before the implementation. Test Driven Design requires that the unit test be written first, if the unit test will fail the implementation will be updated until the test passes. But if the software development ends here, it can only be called Test First Design, not Test Drive. The core of the design is refactoring, decoupling to a satisfactory level, and the test case is an important guarantee that the refactoring will not break the function.

IoC, Inversion of Control — As mentioned at the beginning of this series, DI is actually an implementation of IoC, but the concept of IoC is more oriented from the perspective of its proposed. Many have heard of the Hollywood principle: don’t call me, I’ll call you. This is a concrete type as a dependency module, one does not need to care when they will be called, how they will be called, just need to wait for themselves to be called.

Finally, DIP in SOLID indicates that high-level modules should not rely on the underlying modules, they should all rely on abstraction. From this point of view, DIP pays more attention to the degree of abstraction, that is, the specific implementation, the form of the code.

But we can roughly think that these three concepts express an idea, if there is something that must be refined, it is interface-oriented programming. And to put the interface at the bottom, the implementation is placed at the top.

Other Topics in aelf Tech Talks:

aelf Tech Talks: Dependency Injection Part 1