DEV Community

Yodit Weldegeorgise
Yodit Weldegeorgise

Posted on

OOP vs DOP: Rethinking Design, Complexity, and Trade-offs

On Wednesday, I attended an ๐—ข๐—ข๐—ฃ ๐˜ƒ๐˜€ ๐——๐—ข๐—ฃ discussion that challenged the way I think about software design, complexity, and trade-offs.

๐—ค๐˜‚๐—ถ๐—ฐ๐—ธ ๐—ด๐—ฟ๐—ผ๐˜‚๐—ป๐—ฑ๐—ถ๐—ป๐—ด ๐—ฏ๐—ฒ๐—ณ๐—ผ๐—ฟ๐—ฒ ๐—ฑ๐—ถ๐˜ƒ๐—ถ๐—ป๐—ด ๐—ถ๐—ป

๐—ข๐—ฏ๐—ท๐—ฒ๐—ฐ๐˜-๐—ข๐—ฟ๐—ถ๐—ฒ๐—ป๐˜๐—ฒ๐—ฑ ๐—ฃ๐—ฟ๐—ผ๐—ด๐—ฟ๐—ฎ๐—บ๐—บ๐—ถ๐—ป๐—ด (๐—ข๐—ข๐—ฃ) organizes software around objects that bundle data and behavior together. It relies on abstraction, encapsulation, inheritance, and polymorphism.

๐——๐—ฎ๐˜๐—ฎ-๐—ข๐—ฟ๐—ถ๐—ฒ๐—ป๐˜๐—ฒ๐—ฑ ๐—ฃ๐—ฟ๐—ผ๐—ด๐—ฟ๐—ฎ๐—บ๐—บ๐—ถ๐—ป๐—ด (๐——๐—ข๐—ฃ) focuses on modeling data first and driving behavior from that data. It often favors simplicity, immutability, and pattern matching over deep object hierarchies.

๐—Ÿ๐—ฒ๐˜ ๐—บ๐—ฒ ๐˜€๐˜๐—ฎ๐—ฟ๐˜ ๐˜„๐—ถ๐˜๐—ต ๐˜๐—ต๐—ฒ ๐˜€๐˜๐—ผ๐—ฟ๐˜†๐˜๐—ฒ๐—น๐—น๐—ถ๐—ป๐—ด

The way Dr. Venkat Subramaniam teaches is refreshing.

A three-hour workshop felt like a thirty-minute conversation.

No slides.
Just live typing in Notepad, thinking out loud, and building ideas step by step.

As a visual learner, this style really worked for me. It was easy to follow and very different from the usual conference-style presentations.

๐—ก๐—ผ๐˜„ ๐˜๐—ผ ๐˜๐—ต๐—ฒ ๐—ฐ๐—ผ๐—ป๐˜๐—ฒ๐—ป๐˜

Java has been centered around Object-Oriented Programming for more than thirty years, and as a community, we are deeply attached to its principles. But one question set the tone for the entire session:

๐Ÿ‘‰ ๐—œ๐—ณ ๐˜„๐—ฒ ๐—ณ๐—ผ๐—น๐—น๐—ผ๐˜„ ๐—ฑ๐—ฒ๐˜€๐—ถ๐—ด๐—ป ๐—ฝ๐—ฟ๐—ถ๐—ป๐—ฐ๐—ถ๐—ฝ๐—น๐—ฒ๐˜€ ๐—ฏ๐—น๐—ถ๐—ป๐—ฑ๐—น๐˜†, ๐—ฎ๐—ป๐—ฑ ๐˜๐—ต๐—ฒ ๐—ฑ๐—ฒ๐˜€๐—ถ๐—ด๐—ป ๐—ถ๐˜€ ๐˜€๐˜๐—ถ๐—น๐—น ๐—ฏ๐—ฎ๐—ฑ, ๐—ฎ๐—ฟ๐—ฒ ๐˜„๐—ฒ ๐—ฟ๐—ฒ๐—ฎ๐—น๐—น๐˜† ๐˜€๐—ฒ๐—ฟ๐˜ƒ๐—ถ๐—ป๐—ด ๐˜๐—ต๐—ฒ ๐—ฝ๐˜‚๐—ฟ๐—ฝ๐—ผ๐˜€๐—ฒ?

Thatโ€™s where the discussion got interesting.

๐—ฅ๐—ฒ๐˜ƒ๐—ถ๐˜€๐—ถ๐˜๐—ถ๐—ป๐—ด ๐˜๐—ต๐—ฒ ๐—ณ๐—ผ๐˜‚๐—ฟ ๐—ฝ๐—ถ๐—น๐—น๐—ฎ๐—ฟ๐˜€ ๐—ผ๐—ณ ๐—ข๐—ข๐—ฃ

๐—”๐—ฏ๐˜€๐˜๐—ฟ๐—ฎ๐—ฐ๐˜๐—ถ๐—ผ๐—ป
Changing behavior without directly changing code, and how humans stay sane when dealing with complexity by focusing on what instead of how.

๐—˜๐—ป๐—ฐ๐—ฎ๐—ฝ๐˜€๐˜‚๐—น๐—ฎ๐˜๐—ถ๐—ผ๐—ป
Implementation hiding, or the separation of what from how. This applies at many levels, from functions and files to classes, components, subsystems, and entire systems.

๐—œ๐—ป๐—ต๐—ฒ๐—ฟ๐—ถ๐˜๐—ฎ๐—ป๐—ฐ๐—ฒ
Often one of the weakest links of OOP when overused. Instead of defaulting to inheritance, the focus shifted to design by contract and design by capability.

๐—ฃ๐—ผ๐—น๐˜†๐—บ๐—ผ๐—ฟ๐—ฝ๐—ต๐—ถ๐˜€๐—บ
Behavior chosen at runtime, not compile time. It often appears with inheritance, but it does not require it and can also be achieved through interfaces, composition, or pattern matching.

This framing made the principles feel less like rigid rules and more like design tools with real trade-offs.

๐——๐—ฒ๐˜€๐—ถ๐—ด๐—ป ๐—ฝ๐—ฟ๐—ถ๐—ป๐—ฐ๐—ถ๐—ฝ๐—น๐—ฒ๐˜€ ๐—ถ๐—ป ๐—ฎ๐—ฐ๐˜๐—ถ๐—ผ๐—ป

We explored the Open-Closed Principle and Dependency Inversion Principle.

A simple design evolved from:

OrderProcess -> CreditCard

to:

OrderProcess -> PaymentMethod <- CreditCard

Here, OrderProcess depends only on the abstraction, while CreditCard implements that abstraction, keeping high-level logic decoupled from concrete implementations.

๐—•๐˜‚๐˜ ๐˜๐—ต๐—ฒ๐—ป ๐—ฐ๐—ฎ๐—บ๐—ฒ ๐˜๐—ต๐—ฒ ๐—ฐ๐—ผ๐˜€๐˜

DRY was violated
Parallel class hierarchies appeared
Code size increased
Complexity grew quickly
Runtime errors became easier to introduce
Exception handling became painful

At some point, it felt like we were managing complexity by naming it.

That led to an important question:

When does the cost of complexity outweigh the benefits?

๐—Ÿ๐—ผ๐—ผ๐—ธ๐—ถ๐—ป๐—ด ๐—ฎ๐˜ ๐——๐—ข๐—ฃ

With DOP, the Open-Closed Principle is violated, and Dependency Inversion is not used.

But in return:

Much less code
No parallel hierarchies
DRY preserved
Simpler logic
Easier exception handling
Compile-time errors when new types are introduced

The contrast was clear.

OOP relies on polymorphism.
DOP relies on pattern matching.

๐— ๐—ผ๐—ฑ๐—ฒ๐—ฟ๐—ป ๐—๐—ฎ๐˜ƒ๐—ฎ ๐—ณ๐—ฒ๐—ฎ๐˜๐˜‚๐—ฟ๐—ฒ๐˜€

Records encourage immutability.
Sealed classes give compile-time guarantees by defining a closed set of allowed subtypes.
Switch becomes a powerful pattern-matching tool, not just a control-flow tool.

๐— ๐˜† ๐—ฏ๐—ถ๐—ด๐—ด๐—ฒ๐˜€๐˜ ๐˜๐—ฎ๐—ธ๐—ฒ๐—ฎ๐˜„๐—ฎ๐˜†

Both in life and in programming, trade-offs are unavoidable.

I felt this personally. To attend this session, I had to choose between a Toastmasters meeting and the JavaMUG meetup. I could not be in two places at the same time.

Another insight that really stayed with me was Dr. Venkatโ€™s perspective beyond any single language. He works with more than 15 programming languages, and his students often share that thinking beyond a fixed-language mindset helps them solve problems more effectively.

Focusing on thinking, not just syntax, was a powerful reminder.

No paradigm is perfect. What matters most is knowing when and why to use each one.

๐—–๐˜‚๐—ฟ๐—ถ๐—ผ๐˜‚๐˜€ ๐˜๐—ผ ๐—ต๐—ฒ๐—ฎ๐—ฟ ๐˜†๐—ผ๐˜‚๐—ฟ ๐˜๐—ต๐—ผ๐˜‚๐—ด๐—ต๐˜๐˜€
Are you experimenting with DOP in Java yet, or are you still mostly in the OOP world?

Top comments (0)