Dependency Injection (DI)- is a technique used to achieve Inversion Of
Control (IOC) between classes and their dependencies. Helps to creating Loosely Coupling
code, ensuring that classes are not responsible for creating their dependencies, instead
they get dependencies from an external source. it make easy to change and replace
dependencies without modifying classes.
Dependency: is an object, a class need to perform its function. ex: If an
"OrderService" needs
a PaymentProcessor, then "PaymentProcessor" is a dependency.
Injection: instead of class creating its own dependencies (can lead to
tight coupling), this
dependencies are injected to class from outside. There are 3 types of Injection:
Inversion of Control (IOC) is a tool that automates the implementation of Dependency Injectyion(DI). Traditionally, in a program the main application flow is controlled by program itself, create and manage its own dependencies. But in IOC, an external system like 'Container' takes over the responsibility. It automatically resolve dependency and inject them where needed. Eg: Autofac, Unity, and NInject, implement the IoC principle using DI.
Dependency Injection (DI) container is a framework that manages creation, configuration, lifetime of objects and their dependencies. It is a type of IOC. Like Microsoft.Extensions.DependencyInjection in .NET core, automatically provide instance of dependencies when class needs, instead of creating these dependencies manually. Also third-party containers like 'Autofac' offer more advanced features. We register service in program.cs so the container knows how to resolve them when needed. There are 3 main lifetime for services:
Is a DI container, but more flexible and advanced compared to default
built-in DI. Offer more configuration options, such as controlling how long object live,
register services in more detailed way. Supports constructor, property, and method
injection, and provides module-based registration. To use AutoFac we install
Autofac.Extensions.DependencyInjection package.
RegisterType vs RegisterInstance:
a. RegisterType
b. RegisterInstance(obj) : in here we create an object ourselves, Autofac uses
that
same instance every time when Interface is requested, act like Singeleton (no
new instance). If we already have an object like config,logger it is useful. eg:
in below line Autofac will always return same logger object whenever Ilogger is
requested.
var logger = new Logger();
builder.Services.RegisterInstance(logger).As<ILogger>
();
NOTE: Let's say we have UserService class and it has
dependency on IUserProcessor , Autofac will check UserService's constructor
and sees it needs IUserProcessor, so it'll look up for IUserProcessor =>
bound to UserProcessor, and automatically inject an instance of
UserProcessor.
Are designed to help move from tightly coupled to loosely coupled code, increases cohesion
between classes and reduces unnecessary coupling between them. Resulting in code is more
modular and easier to extend, help to reduce number of bugs in code, making easier find and
solve bugs. Solid has 5 principles, but first let see Coupling and Cohesion :
a. Coupling means- dependency between components (Classes). Goal is,
classes should depend on
abstractions, not concrete implementation.
Low Coupling means - Classes are independent each other, changes in one class affect
others.
High coupling means- Classes are related each other, changes in one class may
break/require
changes in others.
b. Cohesion means- how related members of class. Goal is, each class should
have one clear
responsibility.
Low cohesion means- member of class are unrelated with each other
High cohesion means- member of class are closely related with each other and used for
single
purpose.
Summary: SOLID, helps to design systems where classes interact with each other
through interfaces
or abstractions (low coupling) and members in a class are closely related and used for
single purpose (high cohesion).
Single Responsibility Principle (SRP): A class only have 1 reason to change,
it
means it should have only 1 responsibility, eg: Suppose we have 'User' class, that store
user information and handles saving user data to DB. In here User has 2 responsibilities:
a. It must manage user information
b. Save information to DB. And if DB logic changes, then we have to modify User class, which
Violating SRP.
Open Closed Principle (OCP): Entites (classes, modules, functions) should be open for extension but closed for modification, which means we should be able to add new behavior without modifying existing code, eg: Suppose we have 'Payment' class that handle different payment methods, if we need to add new payment method, we must update Payment class, and it violates OCP.
Liskov Substiton Principle (LSP): Object of base class should be replaceable with objects of its child class without breaking application, eg: Suppose we have 'Bird' base class, and Sparrow , Penguin are subclasses. When we use Bird base class, then any subclass should act like Bird without breaking the system. In this example when we call Fly() it will throw exception because Penguin can't fly, but we are assuming all Bird types can fly, so Penguin will change behavior of base class, which violated LSP. When we split behaviors appropriately like not all birds can fly, then we don't force them to implement Fly().
Interface Segregation Principle (ISP): Client class shouldn't be forced to implement an Interface they don't use, means don't create large Interface, instead of split them into smaller ones, eg: if a class need only to implement ReadFile() method not WriteFile() one, it forced to implement both, which violates ISP.
Dependency Inversion Principle (DIP):
High-level modules should not depend on low-level modules, both should depend on
abstractions, means abstraction should not depend on details, instead details should depend
on abstraction, eg: Suppose we have User (high-level) and DatabaseLogger (low-level)
classes, if we want to switch DatabaseLogger to FileLogger then we must change User class,
resulting is tightly coupled. Violates DIP.
Dependency Inversion Principle (DIP) vs Dependency Injection (DI)
: both of them reduce tight coupling between components, but they do it different
ways. DIP is one of 5 SOLID principle of OOP. DI is design pattern that define how object
get their dependencies. DI is used to implement DIP.
Without DIP and DI: Violates DIP: Depends on a concrete class; No DI: Dependency is created
inside the class; Hard to test or replace DatabaseLogger
With DIP and DI: Depends on interface; Injects logger from outside; Easy to test, extend, or
swap implementation