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.