In this post, I summarize the different programming paradigms which I learned from reading the book “Clean Architecture A Craftsman’s guide to Software Structure and Design” by Robert Martin. The programming paradigms are structured programming, object oriented programming and functional programming.
In the late 1900s, Böhm–Jacopini proved that using just the three control structures which are sequences, selection and iteration is enough to build any program. This idea becomes the basis of structured programming. If a module is complex, programmers can break it into subunits that are small enough to develop using just the three control structures.
When developing a program, one of the goals is to keep the software modules small enough such that they are testable, following the ideas of structured programming.
The use of certain goto statements are discouraged because they inhibit decomposing a complex module into smaller units; thereby making it difficult to show correctness of the program through tests.
When talking about OOP, people usually refer to the three concepts: encapsulation, polymorphisms, and inheritance.
In the book, the author shows that Object Oriented (OO) concepts actually exist before OOP languages. For instance, C language provides mechanisms to implement polymorphism, encapsulation and inheritance. The author argues that C++ and Java have weaker encapsulation than in C. In C++, encapsulation is weaker because you need to declare variables in a header file. As such, the header still has some details about the implementation. In Java, you don’t even have a separation between implementation and header files.
Objected Oriented (OO) languages provide mechanisms to hide or encapsulate the internal data and methods of a class or module. For instance, through the use of private accessor in C#, one can make a method or variable of a class inaccessible to outside of the class. This reduce the couplings of the program because the internal parts of the class can change without affecting other classes, provided the public interfaces remain the same.
Inheritance provides a mechanism to reuse codes. If a class inherits from another class, the inherited class contains all the non-private functionalities and variables from which it inherits, without having to redeclare the methods or variables.
Poly means many. In real life, different things can appear in different forms. A person can be a student in school, a husband at home, an employee at work etc … A sofa bed can work as a sofa or a bed. In programming, polymorphisms occur when an object appears in multiple forms.
One way OOP supports polymorphism is through the use of interfaces and abstract classes. For example, in C#, a class can implement multiple interfaces and can work as an instance of any of the interfaces it implements.
Using polymorphism effectively can make reduce couplings in a program and make the codes more generic. For example, in Java, you can use the InputStream and OutputStream classes to copy data from a source to a destination. You don’t care whether the source comes from a remote location, a database, your local hard drive etc… as long as the class which models the source extends from the InputStream class and provides a read operation. Similarly, as long as the class which models the destination extends from the OutputStream class and provides a write operation, your program can work with it. In this way, the various sources and destinations act as plugins to your program.
Before polymorphisms, source code dependencies follow the flow of control. Polymorphisms provides a mechanism to control or invert the direction of the dependencies in a way that reduces the couplings in a program.
Dependency Inversion means that you have a module that depends on an interface at compile time. At run time, you module calls an implementation of the interface. The flow of control at runtime is that the module is going to call the function in the implementation class. However, the flow of dependency is inverse in the sense that both the module and the implementation depend on the interface. The inversion can be demonstrated by a diagram.
In the above diagram, the yellow line shows the direction of the flow of control. The green lines show the direction of the dependencies at runtime. At runtime, A.F() calls G() of class C. However, class A does not have an explicit dependency on Class C. In fact, both Class A and Class C depend on the interface G() at runtime. But Class A does not depend on Class C at compile time. As you see, the direction of dependency is opposite to the direction of the flow; hence the name dependency inversion.
In clean architecture, the business logic does not need to depend directly on the codes that interface with external systems and databases. This is possible through dependency inversion.
OO is the ability, through the use of polymorphism, to gain absolute control over every source code dependency in the system. It allows the architect to create a plugin architecture, in which modules that contain high-level policies are independent of modules that contain low-level details.
Martin, Robert C. “Chapter 5 Object Oriented Programming.” Clean Architecture: A Craftsman’s Guide to Software Structure and Design, Pearson Education, Boston, MA, 2018.
One of the main benefits of functional programming is immutability. Issues with concurrency often involve mutating a variable. If storage space and processing speed are abundant, we should always strive for immutability in our applications. However, often it’s not possible or practical to have 100% immutability, especially if you work with a non functional programming language. A compromise is to separate the components that mutate from the components that do not mutate, put as much logic into the immutable components, and protect the mutated variables.
All race conditions, deadlock conditions, and concurrent update problems are due to mutable variables.
Martin, Robert C. “Chapter 5 Object Oriented Programming.” Clean Architecture: A Craftsman’s Guide to Software Structure and Design, Pearson Education, Boston, MA, 2018.
Event Sourcing is the technique in which we store the transactions, events that change the data. If we need to retrieve the state of the data, we just apply those events from the beginning to the end. We can also store snapshots of the data and only apply the events starting from a snapshot. Using event sourcing is one way we can achieve immutability in our application as we never allow updates. We only allow inserts and reads.
In conclusion, the author argues that the three programming paradigms show us what not to do. Structure programming discourages the use of goto statements. Object oriented programming discourages a programmer from using function pointers. Functional programming discourages a programmer from mutating variables unnecessarily.
Clean Architecture: A Craftsman’s Guide to Software Structure and Design
Notes on The Dependency Inversion Principle
The Open Closed Principle
Common frameworks, libraries and design patterns I use
Notes on component coupling
Notes on Component Cohesion
Notes on The Clean Architecture
Notes on the Interface Segregation Pattern
Notes on Barbara Liskov paper on data abstraction and hierarchy