Fundamentals 15 min read

Why Object‑Oriented Programming Manages Dependencies, Not Objects

The article argues that true object‑oriented design is about controlling software dependencies, tracing its historical roots from Simula to Smalltalk and C++, illustrating how polymorphism, composition, and proper dependency inversion solve real‑world problems while warning against over‑engineered OO patterns.

samdeepthink
samdeepthink
samdeepthink
Why Object‑Oriented Programming Manages Dependencies, Not Objects

OO Is About Managing Dependencies, Not Objects

Many codebases place all business logic in services while objects contain only getters and setters. The core issue is the direction of dependencies: OO’s purpose is to control which parts of the code must know about each other.

Historical Background

Simula 67 (Dahl & Nygaard, 1967) introduced classes, inheritance, and virtual functions for simulation. Alan Kay later examined Simula’s source, built Smalltalk to support his Dynabook vision, and defined OO as message passing, local state protection, and extreme late binding. Bjarne Stroustrup incorporated Simula’s concepts into C, creating C++.

These histories show OO has always been a problem‑solving tool, not an end in itself.

Polymorphism as Dependency Management

Polymorphism decouples callers from concrete implementations. Without polymorphism, a switch that controls devices must contain if‑else branches for each device type:

public class Switch {
    public void turnOn(Light light, Fan fan, int type) {
        // each new device requires code change
        if (type == 1) light.on();
        else if (type == 2) fan.start();
    }
}

Using an interface reverses the dependency direction:

public class Switch {
    public void turnOn(Switchable device) {
        // no change needed for new devices
        device.on();
    }
}

The caller now depends only on an abstraction ( Switchable), embodying the Dependency Inversion Principle.

Pure OO vs. Distorted OO

Anemic Model vs. Rich Model

An Order class that only holds data and pushes all logic to OrderService is an anemic model:

public class Order {
    private BigDecimal totalAmount;
    // getters and setters only
}

orderService.save(order.calculateFinalPrice());

A rich model embeds behavior inside the object:

public class Order {
    private BigDecimal totalAmount;
    public BigDecimal calculateFinalPrice() {
        // discount rules, thresholds, etc.
    }
}

orderService.save(order.calculateFinalPrice());

When business rules are complex, the rich model localises changes; for cross‑object coordination a service layer may still be appropriate.

Data‑Driven vs. Over‑OO

For a simple OS‑message lookup, an over‑engineered design might create an interface, factory, singleton, and multiple strategy classes. The same requirement is solved with a single immutable map:

Map<String, String> osMessages = Map.of(
    "Linux", "This is a UNIX box.",
    "Windows", "This is a Windows box."
);
String msg = osMessages.getOrDefault(osName, "Unknown OS");

Adding a new OS requires only one line of data.

Inheritance Abuse vs. Composition First

Deep inheritance hierarchies propagate changes from a base class to all subclasses and are limited by single inheritance in Java:

// BaseExporter change affects all subclasses
class PdfExporter extends BaseExporter { }
class ExcelExporter extends BaseExporter { }

Replacing inheritance with composition makes relationships flexible; an ExportService holds a formatter, and changing the formatter does not affect exporters.

Scenarios Where OO Excels

Operations that must support many varying implementations that will keep growing (e.g., a payment gateway adding Alipay, WeChat, UnionPay, Apple Pay via a common interface).

Data and behavior are tightly coupled and rules change frequently (e.g., complex transaction state machines in finance); embedding rules in the domain object reduces scattering.

Unix’s Insight

Unix is not OO, yet it follows the same design principles: composition over inheritance, interface‑independent modules, high cohesion, low coupling. Pipes act as a Decorator, shells as Proxy/Facade, and device files as Adapters/Factories. The thin glue layer demonstrates that good design depends on principles, not on OO syntax.

OO’s Biggest Pitfall

When the goal is dependency management, over‑application of OO creates more dependencies. Common traps include:

Building deep class hierarchies and interface layers while still placing all logic in services, turning objects into pure data carriers.

Replacing a simple map with a full factory‑strategy‑observer stack for a trivial configuration lookup.

Defining interfaces and patterns everywhere without a clear reason, leading to thick glue layers and maintenance difficulty.

When to Use OO, When Not To

Data‑Behavior Relationship : Use OO when the relationship is complex and evolves; avoid OO for simple data‑only mappings.

Need for Polymorphism : Use OO when one operation must connect to many implementations that will increase; avoid OO when there is only a single stable implementation.

Domain Model Complexity : Use OO for rich business rules and state flows; use simple scripts or CRUD for straightforward data persistence.

Change Frequency : Use OO when rules change often and require localized modifications; avoid OO for stable logic.

Dependency Management : Use OO when caller and callee need decoupling; use direct calls for simple relationships.

Code Lifecycle : Use OO for long‑term, large‑scale systems; use lightweight scripts for one‑off tools.

Conclusion

Object‑oriented design’s value lies in managing software dependencies. Polymorphism decouples callers from implementations, encapsulation limits the impact of data‑behavior changes, and composition makes module dependencies flexible. These techniques all serve the same purpose: containing change within a small, manageable scope. Technical choices should start from the problem’s characteristics rather than from a dogmatic adherence to a paradigm.

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.

design patternssoftware architectureDependency Managementobject-oriented programmingpolymorphism
samdeepthink
Written by

samdeepthink

Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.

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.