SOLID

Single-Responsibility Principle (SRP)

The single responsibility principle states that every module or class should have responsibility over a single part of the functionality provide by the software, and that responsibility should be entirely encapsulated by the class, module or function. All its services should be narrowly aligned with that responsibility 1 .

Everything should do just one thing

Orthogonality: … We want to design components that are self-contained: independent, and with a single, well-defined purpose ([…] cohesion). When components are isolated from one another, you know that you can change one without having to worry about the rest. @Hunt1999

Cohesion is a measure of the strength of association of the elements inside a module. A highly cohesive module is a collection of statements and data items that should be treated as a whole because they are so closely related. Any attempt to divide them up would only result in increased coupling and decreased readability 2 .

A class should have only one reason to change 3

SRP brings two benefits: @Hunt1999

  • Gain on productivity
  • Reduction of risk

We can improve the overall team productivity since the development and testing time for small components are relative short compare to larger counterparts. Smaller module means lesser code, easier to design and simpler to do unit test. Therewith, such component has great usability because it has a specific and well-tested responsibility, plus it doesn’t overlap with other modules.

In debugging sense, the job become simpler. Base on the fact that the component is rather isolated from the rest of the codebase, the symptoms are likely to be contained in one area. These components have a high chance to be well-tested since the tests themselves are not sophisticated. Besides the simplification of debugging process, this will reduce the reliance on particular vendor, product, or platform since the interface of them are insulated from the rest of the codebase.

Guideline: Prefer cohesive software entities. Everything that does not strictly belong together, should be separated. 4

Open-Closed Principle (OCP)

Software artefacts (classes, modules, functions etc.) should be open for extension, but closed for modification. 5

The main approach is procedural which is moving the corresponding member function to the outer scope and make it a non-member or free function.

Dynamic polymorphism with code injection and metaclasses could work on Object-Oriented approach without breaking SRP. 6

Guideline: Prefer software design that allows the addition of types or operations without the need to modify existing code. 4

Liskov Substitution Principle (LSP)

What is wanted here is something like the following substitution property: If for each object o1, of type S there is an object o2 of the type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T. 7

It is essentially about the behavioural subtyping, or IS-A relationship. The basic characteristics of this relationship are:

  • Contravariance (more derived type could change the property defined in less derived type) of method arguments in a subtype
  • Covariance (less derived type could change the property defined in more derived type) of return types in a subtype
  • Preconditions cannot be strengthened in a subtype
  • Postconditions cannot be weakened in a subtype
  • Invariants of super type must be preserved in a subtype

We can strengthen or even enforce the checking of these conditions using 202206301938#.

Guideline: 4

  • Make sure that inheritance is about behaviour not about data.
  • Make sure that the contract of base types is adhered to.
  • Make sure to adhere to the required concept.

Interface Segregation Principle (ISP)

Clients should not be forced to depend on methods that they do not use. 3

Many client specific interfaces are better than one general-purpose interface. 8

Write shy codes: don’t reveal yourself to others, and don’t interact with too many people. The Pragmatic Programmer By that, the interaction between the modules or interfaces is limited so if one get comprised, the others remained unaffected. Make sure the implementation of the interface is adhered to the 202207031635#.

Guideline: Make sure interfaces don’t induce unnecessary dependencies 4

Dependency Inversion Principle (DIP)

The Dependency Inversion Principle (DIP) tells us that the most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions. 9

  • High-level modules should not depend on low-level modules. Both should depend on abstractions

  • Abstractions should not depend on details. Details should depend on abstractions. 3

Make a local inversion of dependencies in the lower level abstraction and then move it to the higher level abstraction. The user should not directly interact with low level modules, instead they should rely on a general contractor with high abstractions, that encapsulate such dependencies, and use the module on the user’s behalf. The Pragmatic Programmer See more in 202207041054# or 202207041156#.

Guideline: Prefer to depend on abstractions (i.e. abstract classes or concepts) instead of concrete types. 4

Footnotes
1.
Wikipedia: Single-responsibility principle
2.
Structured Analysis and System Specification by Tom DeMacro
3.
Agile Software Development: Principles, Patterns, and Practices
4.
Breaking Dependencies: The SOLID Principles by Klaus
5.
Object-Oriented Software Construction by Bertrand Meyer
6.
Dynamic Polymorphism with Metaclasses and Code Injection by Sy
7.
Data Abstraction and Hierarchy by Barbara Liskov
8.
Wikipedia: Interface segregation principle
9.
Clean Architecture: A Craftsman’s Guide to Software Structure
Links to this page
  • Unix Philosophy

    Design small, simple but sharp tools with well-contained functionality. They don’t need to do more or less than what they claimed to do (similar to 202206301938). Let them accept one universal format - the text stream. And make these programs work together.

  • Unit Testing

    With unit testing in mind, developer will consciously design a program that is highly modular#. Regression Testing# could be implemented to further enhance the validity of the written unit tests to see whether they are adhered to the specified requirements.

  • The Pragmatic Programmer

    Modularise and hide its implementation behind small, well-documented interfaces the code that will be called by others guided with the 202202041514#.

    Orthogonality is a property where each component in the codebase is highly independent to each other that changes in one does affect others. This is uniform to the #Simple Responsibility Principle.

    Stick to the practice of 202206171004#, # and decoupling# to reduce the numbers of irreversible decisions.

  • Test Driven Development (TDD)

    The codes are not done if only the implementation of them are written into the codebase. The code can be said is done after all the tests ran. If the tests can’t be done on a component, this means that the component is not modular enough. Decouple# it further to make rooms for more modular testing.

  • Software Development Practices
  • Singleton Pattern

    However, Singleton Pattern’s massive drawback is it hides dependencies and increase coupling# as Singleton violates the Single Responsibility Principle (SRP). This makes Unit Testing on the Singleton and the codebase overall hard as there is hardly any circumvention to prevent creating Singleton without affecting the state of the program.

  • Refactoring

    Refactoring is a process of rewriting, reworking and re-architecting of the program’s codebase without changing its observable behaviour. It is often used to prevent or correct code duplication#, preserve the orthogonality# of the software, update the code base on the changes of requirements or better understanding on the underlying technology, or optimise. The ultimate goal of Refactoring is to make the software easy to read, have all logic specified in one and only one place (no duplications#), doesn’t allow changes to alter existing behaviour, and allow only simple conditional logic.

  • Publish/Subscribe Protocol

    We can further #decouple the model using Model-View-Controller (MVC)#.

  • Observer Pattern

    Observer Pattern is a #Design Pattern that could be used to implement #Views utilising the Publish/Subscribe Protocol. It is rather useful at decoupling# the presentations of the data from the data itself. There are two objects in this pattern: subject and observer. Observers don’t need to know each other’s existence, but depends on the subject. When there is a change happen in subject, the subscribed observers need to be notified to synchronise the change. This either done in subject to notify all observers that it keep track of (broadcast), or making it the observer’s responsibility to trigger the update when it is needed (centralised or peer-to-peer).

  • Model-View-Controller (MVC)

    MVC is an #decoupling idiom that separates the model from the view and the controller that manage the view. In this page, the model is defined as the abstract data model, for it to be a variable or #data-structure, that represent the target object which can be from multiple form of sources. View is defines as the user interface, either GUI, TUI or CLI, to the model, which could be nested. In other way, it is the interpretation of the model. Controller is the way or mean to handle the view and sometime could supply the model with new data.

  • Mediator Pattern

    Mediator Pattern is a #Design Pattern that encapsulates and localise how a set of objects interact with each other, especially the collective behaviour. A Mediator object can be seen as an intermediary between objects or hub of communication where they interact with each other indirectly. In this way, we could make changes to their behaviour independently without increasing coupling# between them. The reason is that with Object-Oriented Programming (OOP), we tend to have multiple objects that interconnected to each other (dependencies), which cause a change in one might affect others, thus the coupling between them can be too tight.

  • Mach

    Mach obliges the Object-Oriented Programming principle (OOP) where it treats all components in it as an object.

  • Design by Contract (DBC)

    DBC is a software engineering practice typically used in #202202041514 and #202207072153 that treat module design as designing a contract. The contract consists of three major conditions: precondition, postcondition and class invariant.

    With DCB, the program will do no more and no less than it claims to do. The contract should be put on the base class, and to be enforced to all its deriving classes. The new subclass must support the same method, and that method retains its meaning without much alteration (invariant must be preserved stated by #SOLID). It can, however, accept a wider range of input (precondition can be weakened) or make a stronger guarantee (postcondition can be strengthened) than its parent.

  • DRY Principle

    To avoid that, [The Pragmatic Programmer](lit/@Hunt1999) suggested to have a clear design, a strong technical project leader and a well-understood division of responsibilities (everyone know their part and how to do it) within the design. The authors also encourage the idea of module reusability.

  • Blackboard System

    A blackboard system is a highly #decoupled system based on tuple space which has the following characteristics:

#oop #cpp