Mastering Modularity and SOLID Principles in OO Design
SOLID Principles and Software Modularity
The lecturer is skeptical of SOLID as a strict design recipe. Instead, they emphasize that the real goal of software engineering is modularity.
Single Responsibility Principle (SRP)
- Defined as “single responsibility” or “one reason to change.”
- Definitions are often vague and changing, making it hard to apply consistently.
- It is better to think in terms of concepts and roles.
Open/Closed Principle (OCP)
- Software should be open for extension but closed for modification.
- This allows developers to change behavior without editing the original class.
- It is often achieved through polymorphism or the template method.
Liskov Substitution Principle (LSP)
- A subtype must be a valid substitute for its parent or interface.
- This requires more than just matching method signatures.
- Developers must check where the client actually calls the method (e.g., the abstract bird class example).
Interface Segregation Principle (ISP)
- A client should depend only on the smallest interface it needs.
- The focus is on minimizing client dependency, not just creating tiny interfaces.
Dependency Inversion Principle (DIP)
- Depend on abstractions rather than hardwired concrete details.
- The most important issue is identifying where concrete choices are fixed.
- Injected or passed-in dependencies help preserve flexibility.
Key Object-Oriented Design Metrics
Coupling and Response Metrics
Coupling Between Objects (CBO)
- Counts class coupling to other classes to measure dependency and independence.
- High CBO indicates more coupling, though definitions may vary across tools.
Response For a Class (RFC)
- Includes methods in a class plus the methods they may trigger.
- It attempts to measure behavioral response size.
- High RFC may suggest more complexity, though it is often awkward and hard to interpret cleanly.
Cohesion and Complexity Metrics
Lack of Cohesion in Methods (LCOM)
- Based on whether methods share field use.
- High LCOM indicates low cohesion.
- It tries to detect disconnected or unrelated class behavior, though many versions and edge cases exist.
Weighted Methods per Class (WMC)
- The sum of method complexities used to measure class size and complexity.
- The result depends on how “method complexity” is defined.
Inheritance in Object-Oriented Design
Depth and Breadth of Inheritance
Depth of Inheritance Tree (DIT)
- The longest path upward in the inheritance hierarchy.
- It attempts to quantify inheritance, reuse, and complexity, though quality interpretation is often unclear.
Number of Children (NOC)
- The number of immediate subclasses.
- It captures how much a class is extended or reused, but it is often not very informative alone.
The Role of Inheritance
Inheritance is not the core definition of Object-Oriented (OO) programming; OO is primarily about objects and message passing. Inheritance is simply a design tool.
Evaluating Inheritance Metrics
Old OO advice often treated inheritance as essential. However, the lecturer asks: When does it actually improve modularity?
- DIT: Depth to root.
- NOC: Number of immediate children.
Both are per-class metrics and are weak for answering design-level questions regarding “how much inheritance” is appropriate. Evidence linking DIT to maintainability is inconsistent.
Better Design-Level Ideas
A better approach is to measure the proportion of classes involved in inheritance:
- Head of the arrow: The class being inherited from.
- Tail of the arrow: The class using inheritance.
This provides a clearer interpretation for the whole design.
Best Practices for Inheritance and Composition
Proper Use of Inheritance
Inheritance is justified when it provides:
- External reuse
- Internal reuse
- Subtype benefits
If none of these apply, inheritance is probably unjustified.
Composition Over Inheritance
Reuse-only inheritance can often be replaced by composition or delegation. The subtype relationship is the stronger reason to keep inheritance.
Case Study: A Bad Example
In Java, the Stack class extends Vector and implements List. While this achieves reuse, it creates the wrong subtype relationship. Consequently, it is not a true stack abstraction.
