Code Quality Design Help

Some Object-Oriented Design Heuristics - Riel (updated)

Below is the updated markdown document with modernized terminology and the appropriate SOLID annotations appended where applicable.

Chapter 2. Classes and Objects: The Building Blocks of the Object-Oriented Paradigm

Heuristic 2.1
Encapsulate all data within its class.

Heuristic 2.2
Clients of a class should rely solely on its public API, while the class remains independent of its consumers. ( Dependency Inversion Principle DIP)

Heuristic 2.3
Reduce the number of operations exposed in a class’s public protocol. (Interface Segregation Principle ISP)

Heuristic 2.4
Design a minimal yet universal public interface that every class can implement (e.g., operations such as deep versus shallow copying, equality checks via the Equals Equals method, formatted output using ToString ToString, or parsing from text—all typically inherited from the base Object Object class in languages like C#)..

Heuristic 2.5
Avoid exposing internal implementation details or helper functions in the public API.

Heuristic 2.6
Keep the public interface clean and focused; do not include functions that are irrelevant or inaccessible to the class’s users. (Interface Segregation Principle ISP)

Heuristic 2.8
A class should represent a single, coherent abstraction. (Single Responsibility Principle SRP)

Heuristic 2.9
Co-locate related data and behavior in a single class. (Single Responsibility Principle SRP)

Heuristic 2.10
Separate unrelated concerns into distinct classes to avoid coupling dissimilar behaviors. (Single Responsibility Principle SRP)

Chapter 3. Application Architectures: Procedural Versus Object-Oriented Paradigms

Heuristic 3.1
Distribute system logic evenly across top-level classes so that each shares an equal part of the workload.

Heuristic 3.2
Avoid “god objects”; be cautious of classes whose names include terms like Driver, Manager, System, or Subsystem.

Heuristic 3.3
If a class exposes many accessor methods, it may indicate that related data and behavior are not properly integrated. (Single Responsibility Principle SRP)

Heuristic 3.4
Watch out for classes where methods operate on only a subset of its data; such noncommunicating behavior can be symptomatic of poor cohesion. (Single Responsibility Principle SRP)

Heuristic 3.5
In applications that combine an object-oriented model with a graphical user interface (GUI), the model should remain independent of the GUI layer. The GUI should depend on the model for data and operations, but the model must not import or invoke any GUI-related code.

Heuristic 3.6
Model real-world entities and relationships whenever feasible.
(This guideline is sometimes compromised to evenly distribute system logic, avoid god objects, or maintain cohesion.)

Heuristic 3.9
Avoid encapsulating a single operation as an independent class. Be wary of classes named after actions or verbs—especially if they encapsulate only one significant behavior (excluding trivial getters or setters).

Chapter 4. Relationships Between Classes and Objects

Heuristic 4.6
Most class methods should operate on most of its data members. (Single Responsibility Principle SRP)

Heuristic 4.7
Classes should not manage more components than can be effectively handled in short-term memory. A common guideline is around six elements.

Heuristic 4.8
Design your system using composition by creating deep, narrow hierarchies—each class contains only one or two closely related items, rather than one class managing many components.

Heuristic 4.13
A class should be aware of its own members, but remain ignorant of the context in which it is contained.

Heuristic 4.14
Objects defined within the same scope should avoid direct dependencies on one another.

Chapter 5. The Inheritance Relationship

Heuristic 5.1
Use inheritance solely for representing specialization hierarchies. (Liskov Substitution Principle LSP)

Heuristic 5.2
While derived classes naturally depend on their base classes, base classes should remain agnostic of their subclasses.

Heuristic 5.3
Keep all base class data private; avoid using protected members.

Heuristic 5.4
Theoretically, deeper inheritance hierarchies can seem ideal—the deeper, the better.

Heuristic 5.5
In practice, however, inheritance hierarchies should remain shallow enough for practical comprehension; typically, around six levels is advisable.

Heuristic 5.6
All abstract classes should serve as base classes.

Heuristic 5.7
All base classes should be abstract.

Heuristic 5.8
Extract common data, behavior, or interfaces as high as possible in the inheritance chain.

Heuristic 5.9
If classes share only data (without behavior), encapsulate that data in a separate class which is then composed within each class.

Heuristic 5.10
If classes share both data and behavior, they should inherit from a common base class that encapsulates these elements.

Heuristic 5.11
If classes share only a set of method signatures with no shared implementation, create a common base class only when you need to treat them polymorphically. Otherwise, avoid enforcing an inheritance relationship solely for the sake of a common interface.

Heuristic 5.12
Avoid explicit type checking; favor polymorphism to manage different object types. (Liskov Substitution Principle LSP)

Heuristic 5.13
Rather than using conditional logic based on attribute values, decompose the class into distinct subclasses where each represents a specific attribute value. This leverages polymorphism to handle behavioral differences.

Heuristic 5.15
Avoid converting individual objects into subclasses. Exercise caution with subclasses that are instantiated only once.

Car Manufacturer InheritanceCarManufacturerProduceCar(): voidGeneralMotorsFordChrysler

Heuristic 5.16
If you feel the need to generate new classes at runtime, reconsider the design: you are likely dealing with objects that should be generalized within an existing class.

Heuristic 5.17
Derived classes should not override base class methods with no-operation (NOP) implementations. (Liskov Substitution Principle LSP)

Heuristic 5.18
Do not substitute optional composition with inheritance; using inheritance to model optional behavior can lead to an excessive number of classes.

This example underscores the preference for composition over inheritance. While composition may require separate classes for individual features, it prevents the exponential growth of subclasses that arises when every feature combination demands its own subclass. Instead, a single container class can dynamically integrate various feature classes as needed.

Inheritance-Based ApproachHouseHeatingCoolingHouseHeatingElectricHouseCoolingPlumbingHouseElectricPlumbingHouse
Composition-Based ApproachHouseHeatingCoolingElectricPlumbing

Heuristic 5.19
A Problem with No Optimal Solution
Ensure that the underlying issue is genuine before exploring solutions. This problem often occurs due to naming problems of methods.

Fruit Hierarchy with Specialized PreparationFruitprint(): voidcost(): doubleApplecore(): voidOrangesection(): voidBananapeel(): void
Fruit Hierarchy with a Single Prepare MethodFruitprint(): voidcost(): doubleprepare(): voidAppleOrangeBanana

Assuming the issue is valid, options become limited. Many designers opt to define a core method in the Fruit class that acts as a no-operation (NOP) by default. This design pattern is often referred to as the “fat interface” solution.

Fruit Hierarchy with NOP 'core()' in Base ClassFruitprint(): voidcost(): doublecore(): void // NOP in FruitAppleprint(): voidcost(): doublecore(): voidOrangeprint(): voidcost(): doubleBananaprint(): voidcost(): double

Chapter 6. Multiple Inheritance

Heuristic 6.1
If your design includes multiple inheritance, assume it is erroneous until you can justify its correctness.

Heuristic 6.3
In designs featuring multiple inheritance, ensure that none of the base classes are themselves derived from another base class.

Multiple Inheritance: Problematic vs. PreferredBase1Base2Derived1Problematic:Base2 is derived from Base1!BaseABaseBDerived2Preferred:Both BaseA and BaseB are independent.

See Also:

18 June 2025