Fundamentals 9 min read

Why High‑Level Modules Should Depend on Abstractions: Mastering Dependency Inversion

This article explains the Dependency Inversion Principle, shows why traditional layered architectures suffer from maintenance and reuse problems, provides concrete Java and web examples, and demonstrates how refactoring to abstract interfaces resolves these issues for more flexible, decoupled software design.

JavaEdge
JavaEdge
JavaEdge
Why High‑Level Modules Should Depend on Abstractions: Mastering Dependency Inversion

Introduction

When developing with Spring, you can use features such as dependency injection and MVC without directly invoking Spring classes. This is possible because the application depends on abstractions defined by the framework, not on concrete framework implementations. The Dependency Inversion Principle (DIP) explains why.

Dependency Inversion Principle

DIP states that high‑level modules must not depend on low‑level modules; both must depend on abstractions. Moreover, abstractions must not depend on concrete implementations; concrete implementations depend on abstractions.

Issues in Traditional Layered Architecture

Maintenance difficulty : Changes in lower layers propagate upward, forcing modifications in higher‑level code.

Reuse difficulty : High‑level modules become tightly coupled to low‑level details, reducing their reusability.

Typical Examples of DIP in Practice

Database access: Application code depends on the java.sql.Connection / JDBC API, not on a specific driver. Swapping drivers requires no code change.

Web applications: Code depends on the Servlet API (J2EE specification) rather than on a concrete container such as Tomcat. Any compliant container can be used.

Design Guideline to Apply DIP

Each high‑level module should declare the abstract services it needs. Low‑level modules implement those abstractions. This reverses the dependency direction so that high‑level code never directly references concrete low‑level classes.

Concrete Refactoring Example: Button Controlling a Lamp

Original design (tight coupling):

public class Button {
    private Lamp lamp;

    public void poll() {
        if (/* condition */) {
            lamp.turnOn();
        }
    }
}

Problems:

Button depends on the concrete Lamp class.

Any change to Lamp forces a change in Button.

Button cannot be reused to control other devices.

Refactored design using an abstraction:

public interface Switchable {
    void turnOn();
    void turnOff();
}

public class Lamp implements Switchable {
    @Override public void turnOn() { /* … */ }
    @Override public void turnOff() { /* … */ }
}

public class Button {
    private final Switchable target;

    public Button(Switchable target) {
        this.target = target;
    }

    public void poll() {
        if (/* condition */) {
            target.turnOn();
        }
    }
}

Now Button depends only on the Switchable abstraction, which is defined by the high‑level module. Any class that implements Switchable (e.g., a motor, a fan) can be controlled without modifying Button.

Broader Implications for Frameworks

Frameworks such as Spring and servlet containers follow the same pattern: the application implements framework‑provided interfaces (e.g., Servlet, ApplicationContextAware), and the framework invokes the application code. The application never calls framework internals directly.

Practical Guidelines

Prefer abstract interfaces or abstract classes over concrete implementations.

Define the abstraction in the high‑level module; let low‑level modules implement it.

Avoid inheriting from concrete classes unless they are designed as abstractions.

Do not override methods that contain concrete business logic; instead, extend the abstraction.

When designing a framework, expose core functionality through interfaces and let client code provide implementations.

References

Dependency Inversion Principle – https://flylib.com/books/en/4.444.1.71/1/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSoftware ArchitecturespringDependency Inversiondesign principlesabstraction
JavaEdge
Written by

JavaEdge

First‑line development experience at multiple leading tech firms; now a software architect at a Shanghai state‑owned enterprise and founder of Programming Yanxuan. Nearly 300k followers online; expertise in distributed system design, AIGC application development, and quantitative finance investing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.