"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 is the difference between "high-level" and "low-level"?

High-Level

  • More abstract
  • Related to the problem domain:
    • Business rules
    • Process-oriented
    • Further from I/O.

Low-Level

  • Closer to I/O.
  • "Plumbing" code
  • Interacts with specific external systems and hardware.

What is an abstraction?

Types you cannot instantiate.

  • Interfaces     <-- Prefered
  • Abstract base classes

Detials and Abstractions

  • Abstractions shouldn't be coupled with details.
  • Abstractions describe what
    • Send a message
    • Store a Customer record
  • Details specify how
    • Send an SMTP email over port 25
    • Serialise Customer to JSON and store in a text file.
public interface IOrderDataAccess
{
    SqlDataReader ListOrders(SqlParamterCollection params);
}
    
BAD interface implementation, because client code is forced to have a dependence on some SQL assembly.
public interface IOrderDataAccess
{
    List<Order> ListOrders(Dictionary<string, string> params);
}
    
GOOD interface implementation.

Low Level Dependencies

Database

File system

@

Email Service

Web APIs

Configuration

System Clock

Hidden Direct Dependencies

  • Direct use of low level dependencies
  • Static calls and new
  • Causes pain
    • Tight coupling
    • Difficult to isolate and unit test
    • Duplication

New Is Glue

  • Using new to create dependencies glues your code to that dependency
  • New isn't bad - just bear in mind the coupling it creates
    • Do you need to specify the implementation?
    • Could you use an abstraction instead?

Explicit Dependencies Principle

  • Your classes shouldn't surprise clients with dependenciess
  • List them up from, in the constructor
  • Think of them as ingredients in a cooking recipe.

Dependencies with Abstractions

Dependency Injection

  • Don't create your own dependencies
    • Depend on abstractions
    • Request dependencies from client
  • CLient injects dependencies as
    • Constructor arguments   <-- Preferred approach
    • Properties
    • Method arguments

See: Strategy Desing Pattern

Prefer Constructor Injection

  • Follows Explicit Dependencies Principle
  • Injected classes are never in an uninitialised state.
  • Can leverage an IOC container to construct types and their dependencies

Organising and Extending Your SOLID Project

Use Feature Folders

  • Online Store
    • Catalogue
    • Search
    • Cart

Example of clean archtecture: github.com/ardalis/CleanArchitecture

Key Takeaways

  • Most classes should depend on abstractions, not implementation details.
  • Abstractions shouldn't leak details.
  • Cleints should inject dependencies when they create other classes.
  • Structure your solutions to leverage dependency inversion.

Other Links

stackify.com/dependency-inversion-principle

Copyright © 2025 delaney. All rights reserved.