SOlID-DIP
DIP: The Dependency Inversion
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions
What are dependences?
- Framework
- Third Party Libraries
- Database
- File System
- Email
- Web Services
- System Resources (Clock)
- Configuration
- The new Keyword
- Static methods
- Thread.Sleep
- Random
Traditional Programming and Dependencies
- High level modules call low level modules
-
User interface depends on
-
Business logic depend on
- Infrastructure
- Utility
- Data Access
- Static methods are used fro convenience or ad Facade layers
-
Class instantiation / Call stack logic is scattered through all modules
- Violation of single responsibility principle
Class Dependencies: Be Honest!
- Class constructors should require any dependencies the class needs
- Classes whose constructors make this clear have explicit dependencies
-
Classes that do not have implicit, hidden dependencies
public class HelloWorldHidden
{
public string Hello(string name)
{
if (DateTime.Now.Hour < 12 )
{
return "Good morning " + name;
}
if (DateTime.Now.Hour < 18 )
{
return "Good afternoon " + name;
}
return "Good evening";
}
}
The example above has a hidden dependency on the system clock. There is no way to change the dependency and it
violated the OCP principle changing the class if you want to change the timings or wanting to test this class
Demo
public class Order
{
public void Checkout(Cart cart, PymentDetails paymentDetails, bool notifyCostomer)
{
if (paymentDetails.PymentMEthod == PymentMethod.CreditCard)
{
ChargeCard(paymentDetails, cart);
}
if (notifyCostomer)
{
NotifyCustomer(cart);
}
}
private void NotifyCustomer(Cart cart)
{
string customerEmail = cart.CustomerEmail;
if (!string.IsNullOrEmpty(customerEmail))
{
using (var message = new MailMessage("someMail@somwere.com", customerEmail))
using (var clent = new SmtpClient("localHost"))
{
message.Subject = "some subject on" + DateTime.Now;
message.Body = "Body";
try
{
clent.Send(message);
}
catch (Exception ex)
{
Logger.Error("Some Error");
throw;
}
}
}
}
private void ChargeCard(PymentDetails paymentDetails, Cart cart)
{
using (var paymenetGateway=new PaymentGateway())
{
paymenetGateway.Credentials = "some credentials";
paymenetGateway.AmountToCharge = cart.TotalAmount;
paymenetGateway.Charge();
}
}
}
The Problem
-
Order has hidden dependencies:
- MailMessage
- SmtpCleint
- Logger
- PaymentGateway
- DateTime.Now
-
Result
- Tight coupling
- No way to change implementation details (OCP violation)
- Difficult to test
Dependency Injection
-
Dependency Injection is a technique that is used to allow calling code to inject the dependencies a class
needs when it is instantiated
-
The Hollywood principle: "Don't callus; we call you"
-
Three primary techniques
- Constructor Injection
- Property injection
- Parameter injection
- Other methods exits ass well
Constructor Injection (instant on the strategy pattern)
- Dependencies are passed in via constructor
-
Pros
- Classes self-document what they need to perform their work
- Works well with or without a container
- Classes are always in a valid state once constructed
-
Cons
- Constructors can have many parameters/dependencies (design smell)
- Some features (e.g. Serialization) may require a default contractor
- Some methods in the class may not require things other methods require (design smell)
Property Injection
- Dependencies are passed in via property (a.k.a "setter injection")
-
Pros
- Dependency can be changed at any time during object lifetime
- Very flexible
-
Cons
- Objects may be in an invalid state between construction and setting of dependencies
- Less intuitive
Parameter Injection
- Dependencies are passed in via a method parameter
-
Pros
- Most granular
- Very flexible
- Requires no change to rest of class
-
Cons
- Breaks method signature
- Can result in many parameters (design smell)
-
Consider if only on method has the dependency,otherwise prefer constructor injection
Refactoring
- Extract dependencies into interfaces
- Inject implementations of interfaces into order
- Reduce orders responsibilities (apply SRP)
Refactoring
public class Order
{
private readonly Cart _cart;
private readonly INotifyCustomer _notifyCustomer;
public interface INotifyCustomer
{
void NotifyCustomer(Cart car);
}
public Order(Cart cart, INotifyCustomer notifyCustomer)
{
_cart = cart;
_notifyCustomer = notifyCustomer;
}
class NotifyCustomerService : INotifyCustomer
{
public void NotifyCustomer(Cart cart)
{
string customerEmail = cart.CustomerEmail;
if (!string.IsNullOrEmpty(customerEmail))
{
using (var message = new MailMessage("someMail@somwere.com", customerEmail))
using (var clent = new SmtpClient("localHost"))
{
message.Subject = "some subject on" + DateTime.Now;
message.Body = "Body";
try
{
clent.Send(message);
}
catch (Exception ex)
{
Logger.Error("Some Error");
throw;
}
}
}
}
}
public void Checkout(Cart cart, PymentDetails paymentDetails, bool notifyCostomer)
{
if (paymentDetails.PymentMEthod == PymentMethod.CreditCard)
{
ChargeCard(paymentDetails, cart);
}
if (notifyCostomer)
{
_notifyCustomer.NotifyCustomer(cart);
}
}
private void NotifyCustomer(Cart cart)
{
string customerEmail = cart.CustomerEmail;
if (!string.IsNullOrEmpty(customerEmail))
{
using (var message = new MailMessage("someMail@somwere.com", customerEmail))
using (var clent = new SmtpClient("localHost"))
{
message.Subject = "some subject on" + DateTime.Now;
message.Body = "Body";
try
{
clent.Send(message);
}
catch (Exception ex)
{
Logger.Error("Some Error");
throw;
}
}
}
}
private void ChargeCard(PymentDetails paymentDetails, Cart cart)
{
using (var paymenetGateway = new PaymentGateway())
{
paymenetGateway.Credentials = "some credentials";
paymenetGateway.AmountToCharge = cart.TotalAmount;
paymenetGateway.Charge();
}
}
}
In the example above we can see how for example the "notifyCustomer" class is now being passed to the contractor, you can
pass your notification class(dummy) or use the class that was implemented. You can use framework for DI like Ninject to do so
DIP Smells
-
Use of new keyword
-
Use of static methods/properties (DateTime)
Where do we instantiate objects?
- Apply dependency injection typically results in many interfaces that eventually need to be instantiated somewhere..but where?
-
Default constructor
- You can provide default constructor that news up the instances you expect to typically need in your application
- Referred to as "Poor man's dependency injection" or "poor mans's IoC"
-
Main
- You can manually instantiate whatever is needed in your application's startup routine or main() method
-
IoC Container
- Use an "Inversion of control" container
IoC Containers
- Responsible for object graph instantiation
- Initiated at application startup via code or configuration(xml file)
- Managed interfaces and the implementation to be used are Registered with the container
- Dependencies on interfaces are resolved at application startup or runtime
-
Examples of IoC Containers for .NER
- Ninject
- Windsor
- Microsoft Unity
Summary
- Depend on abstraction
- Don't force high-level modules to depend on low-level modules through direct instantiation or static method calls
- Declare class dependencies explicitly in their constructors
- Inject dependencies via constructor, property, or parameter injection
The Dependency Inversion Principle Part 2
Layered / Tiered application design
-
Separate logical (and sometimes physical) layers
- For instance
- User Interface(UI)
- Business logic layer(BLL)
- Data access layer (DAL)
-
Supports encapsulation and abstraction
- Work at the abstraction level appropriate
- Each level only knows about one level deep (ideally)
-
Provides units or reuse
- Lowest levels generally are most reusable (unlike UI layer)
Traditional(Naive) layered architecture
Here, according the dependency flow the UI layer is always depended on the Business layer who is depended on the data access layer
which in turn is depended on the data base and the service layer
These means that the application is difficult to test or change or work with in isolation from a database or the services
We can not depend on abstraction but an explicit instants
Inverted Architecture
Here, all the top modules like data access and ui are depended on the services and models
The Problem with the Naive Architecture
- Dependencies Flow toward infrastructure
- Core/ business / Domain classes depend on implementation details
-
Results
Tight coupling
- No way to change implementation details without recompile (OCP violation)
- Difficult to test
Dependency injection
-
Dependency is transitive
- If UI depends on BLL depends on DAL depends on Database then *everything* depends on the Database
- Depend on abstractions (DIP)
- Package interfaces (abstraction) with the client (ISP)
- Structure solutions and projects so Core / BLL is at center, with fewest dependencies
Summary
- don't depend on infrastructure assemblies from core
- Apply DIP to reverse dependencies