Why Object‑Oriented Design Still Matters: From Machine Code to Domain‑Driven Architecture
This article traces the evolution of programming paradigms—from low‑level machine code through procedural and object‑oriented approaches—explains the pitfalls of poor abstraction, introduces domain‑driven design, and reviews key software design principles such as SOLID, OCP, DIP, PLOA and KISS.
Preface
During the European Renaissance, Copernicus proposed the heliocentric model, which was initially rejected but later validated. A similar story unfolded in software engineering: Simula introduced object‑oriented programming half a century ago, yet it was not immediately accepted. Later thinkers like Robert C. Martin and Martin Fowler reaffirmed its value.
Evolution of Programming Paradigms
From the 1950s to today, programming languages have progressed from machine‑oriented to procedural and finally to object‑oriented, while the core purpose of programming remains unchanged.
1 Machine‑oriented
Early computers executed binary machine code directly (e.g., 0000 for read, 0001 for save). Although fastest, it was unmaintainable, leading to the invention of assembly language, which remained low‑level and costly.
2 Procedural
Procedural programming focuses on breaking a problem into a sequence of steps implemented as functions, improving clarity and development speed compared to machine code. However, as business logic grows, procedural code becomes harder to maintain and extend.
3 Object‑Oriented
Object‑oriented programming (OOP) abstracts commonality, encapsulates logic, and uses polymorphism for extensibility. While OOP may be overkill for simple systems, it excels in complex domains where reusable components and clear boundaries are needed.
Domain‑Driven Design
Is It Really Object‑Oriented?
public String pick(String salesId, String customerId){
// Verify sales role
Operator operator = dao.find("db_operator", salesId);
if(!"SALES".equals(operator.getRole())){
return "operator not sales";
}
// Check sales capacity
int hold = dao.find("sales_hold", salesId);
List<CustomerVo> customers = dao.find("db_sales_customer", salesId);
if(customers.size() >= hold){
return "hold is full";
}
// Verify customer can be picked
Opportunity opp = dao.find("db_opportunity", customerId);
if(opp.getOwnerId() != null){
return "can not pick other's customer";
}
// Pick the customer
opp.setOwnerId(salesId);
dao.save(opp);
return "success";
}This Java snippet appears object‑oriented but actually follows a procedural, event‑driven style with little encapsulation, making it hard to reuse and extend.
In early system design, simple procedural code may suffice, but as complexity grows, maintenance costs explode, turning such code into technical debt.
Domain‑Driven Design
By introducing a “Opportunity” model that links customers and sales, and encapsulating behaviors like pick, open, and distribute, we can use OOP features to define boundaries, abstract, and encapsulate business logic.
Domain‑driven design treats the model as the language backbone; changes to the model drive code refactoring.
Use the model as the backbone of a language, Recognize that a change in the language is a change to the model. Then refactor the code, renaming classes, methods, and modules to conform to the new model. --- Eric Evans, *Domain‑Driven Design Reference*
Software Complexity
Robert C. Martin distinguishes data‑driven from domain‑driven development. Simple data‑driven approaches keep initial complexity low but can cause a steep rise as the system evolves. Starting with domain modeling and OOP can control complexity, though it incurs higher upfront cost.
The goal of software architecture is to minimize the human resources required to build and maintain the required system. --- Robert C. Martin, *Clean Architecture*
Quality of Abstractions
1 Coupling
Coupling measures the strength of inter‑module relationships; high coupling hinders independent understanding and modification. Reducing unnecessary dependencies lowers complexity.
Complexity is caused by two things: dependencies and obscurity. --- John Ousterhout, *A Philosophy of Software Design*
2 Cohesion
Cohesion measures how closely related elements within a module are. Functional cohesion—where elements work together to provide a clear behavior—is ideal.
3 Sufficiency
Sufficiency requires a class or module to expose enough features to be useful; missing essential operations render it ineffective.
4 Completeness
Completeness ensures a module captures all meaningful aspects of an abstraction, beyond the minimal sufficient set.
5 Fundamentality
Fundamentality concerns providing the most basic, effective operations. Redundant or overly specific methods (e.g., an “add2” when “add” exists) violate this principle.
Software Design Principles
1 Open‑Closed Principle (OCP)
Software entities should be open for extension, but closed for modification. --- Bertrand Meyer, *Object Oriented Software Construction*
Violating OCP leads to code that must be edited for every new case. Using abstractions (e.g., strategy pattern) allows adding new behavior without modifying existing modules.
public List<User> sort(List<User> users, Enum type){
if(type == AGE){
users = resortListByAge(users);
} else if(type == NAME){
users = resortListByName(users);
} else if(type == HEALTH){
users = resortListByHealth(users);
}
return users;
}The above example tightly couples sorting logic to the method, breaching OCP.
2 Dependency Inversion Principle (DIP)
High‑level modules should not depend upon low‑level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions. --- Robert C. Martin, *C++ Report 1996*
DIP encourages depending on interfaces rather than concrete implementations, making systems more flexible.
3 Other Principles
Beyond SOLID, other useful principles include:
PLOA Minimal Surprise Principle
If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature. --- Michael F. Cowlishaw
Code that surprises developers should be reconsidered. Example:
public synchronized void setFormatter(Formatter newFormatter) throws SecurityException {
checkPermission();
// Check for a null pointer:
newFormatter.getClass();
formatter = newFormatter;
}KISS Simple Principle
Keep it Simple and Stupid. --- Robert S. Kaplan
Simplicity reduces cognitive load; over‑engineering makes systems harder to understand.
Conclusion
The ultimate goal of software design is to reduce complexity. Learn the fundamental rules first, then know when it is appropriate to break them.
You should not slavishly follow these rules, but violate them only occasionally and with good reason. Learning the art of programming, like most other disciplines, consists of first learning the rules and then learning when to break them. --- Josh Bloch, *Effective Java*
References
Object Oriented Analysis and Design with Applications
Clean Architecture
A Philosophy of Software Design
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
