The Relationship Between Modularity and Polymorphism
Object-Oriented concepts and practices have a layered structure with both vertical and horizontal relationships between the layers and among items in each segment. The following chart shows this structure. The closer a layer is to the bottom of the inverted pyramid, the more important and fundamental it is.
As a reminder, the lowest layer consists of “Abstraction,” “Modularity,” “Encapsulation,” and “Hierarchy.” Those are the most fundamental tools in the Object-Oriented Paradigm; all other layers add more clarity to these principles. The second layer belongs to GRASP, an abbreviation for “General Responsibility Assignment Software Patterns.”
There are nine rules to help developers in making more accurate decisions during an object-oriented design process. These rules are “Information Expert,” “Creator,” “Indirection,” “Protect Variant,” “Polymorphism,” “High cohesion,” “Low coupling,” “Pure fabrication,” and “Controller.”
The third layer contains SOLID Principles, which is an abbreviation to some other abbreviations! These include SRP (Single Responsibility Principle), OCP (Open Closed Principle), LSP (Liskov Sub situation Principle), ISP (Interface Segregation Principle), and DIP (Dependency Indirection Principles). Finally, the highest level of the inverted pyramid contains: Object-Oriented design patterns. This was created in 1994 in the seminal book, “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, John Vlissides, Richard Helm, Ralph Johnson. Since then, many other design patterns have been invented and added to this layer.
In most documents about OOP, only horizontal relationships between the elements of each layer are focused on. Based on my experiences as a software developer and Object-Oriented Design instructor, if the vertical relationships between different parts of this layered structure are investigated, a deep insight into the paradigm emerges.
As a start, this article describes the relationship between the first layer of concepts (AMEH), with “Polymorphism” as a member of GRASP in the second layer. In the following paragraphs, I will describe the meaning and measures of modularity and polymorphism separately. I will explain how modularity can amplify Polymorphism to be a preamble to OCP in the third layer of the OOD concepts pyramid.
Modularity
Modularity as a member of primary concepts of the OOD and is defined as, "the degree to which a system’s components are made up of relatively independent components or parts which can be combined.” As modularity is closely related to encapsulation, it can be said that modularity is a way of mapping encapsulated abstractions into real, physical modules.
Grady Booch gives two goals for defining modules. First, make a module cohesive (shared data structures, similar classes) with an interface that allows for minimal inter-module coupling. Teamwork, security, and documentation are other considerations. If we map the definition of the modularity to the description of the component that Robert C. Martin describes in his recent book, “Clean Architecture,” the cohesion and coupling roles can be a practical guide for applying modularity in software.
Robert C. Martin defines a component as units of deployment, the smallest entities that make up software that can be deployed as Jar files or DLL files. The book introduces some principles for deciding how to modularize software, including coupling and cohesion rules for reusability.
Component Cohesion
As modularity and encapsulation are tied closely together, the central question in for drawing modules borders is, “Which classes belong in which components or modules?” The answer to this question should be based on the cohesion principles of the components. As it has been said in the “Clean Architecture,” these principles are:
- REP: The Reuse/Release Equivalence Principle.
- CCP: The Common Closure Principle.
- CRP: The Common Reuse Principle.
REP: The Reuse/Release Equivalence Principle
A component cannot merely consist of a bunch of randomly selected classes with no rational relation among them. Classes should belong to a cohesive group that is releasable together. In other words, these classes share the same version number and release documentation, so that, as a result, this collection of items in a component make sense both to the author and to the users.
It may seem a bit hard to measure the cohesion degree between elements of a component until a situation where breaking REP results in pain during the development or deployment process. In short, the granule of reuse is the granule of release.
CCP: The Common Closure Principle
This principle is an emphasis on the components’ single responsibility, which means each component should not have multiple reasons for change, and there should be only one axis of change in an elemental component.
Most of the time, maintainability is more critical than reusability, as a measure for maintainability is the amount of work needed for each deployment and release, so it worth to scarify some reusability to gather related classes to gather in a single component. This principle tries to keep a family of related classes in a component to increase the cohesion factor of the component.
Finally, Robert C. Martin says, “Gather together those things that change at the same times and for the same reasons. Separate those things that change at different times or for different reasons.”
CRP: The Common Reuse Principle
This principle states, “Don’t depend on things you don’t need and don’t force users of a component to depend on things they don’t need.”
As classes are seldom reused in isolation and don't collaborate with other classes that are part of the reusable abstraction, these highly-dependent classes belong together in the same component. Besides, CRP tells us which classes not to keep together in a component to avoid unnecessary dependencies between components to get rid of wasting significant effort in deploying a jungle of components without any reasonable relationship.
As the bottom line, CRP tells us more about which classes shouldn’t be together than about which classes should be together. This principle says that classes that are not tightly bound to each other should not be in the same component.
Components’ Coupling
Coupling and cohesion have been twins since the early days of software development. These two evaluator principles help to determine whether relationships between software elements are safe or not. There are three coupling indices for the relationships between components. These principles are:
- ADP: The Acyclic Dependencies Principle.
- SDP: The Stable Dependencies Principle.
- SAP: The Stable Abstractions Principle.
ADP: The Acyclic Dependencies Principle
This principle says that the dependency graphs of components should have no cycles. There are some strategies for breaking down a cyclic path in a component graph, including the dependency inversion principle and creating a new package and move the common dependencies there. The following images depict a cyclic dependency and the solution for eliminating it by creating a new component.
SDP: The Stable Dependencies Principle
Before digging into this principle, the meaning of “Stable” should be clear. A stable thing is something that needs a large amount of work than usual to change or move. The more an element is stable, the more work and energy is required to change. So, it can be said when a component is stable that applying a change to it needs a significant amount of work, including changing other parts of the program as well. For instance, look at this diagram.
What if the public services or interfaces of the “Component D” get changed? How many may other components need to change as a result? In the worst case, it may be required to change all other components as well, meaning that a change in the “Component D” requires a large amount of work in compression with other components, because it has many dependent components. Based on the definition of stability, “Component D” is the most stable component in this diagram.
SDP, says that if a component is a place of change and changes frequently, it is not a stable component, as each change in that component results in cascading changes in other components. These types of components are not safe to depend on and locate in the heart of component dependency graphs. Stability measure for a component can be quantified using the following formula:
In this formula the number, “Fout” shows the output edge from a component. This edge could be any connection between classes and interfaces, including Association, Generalization, Realization, and Dependency. “Fin” shows input connections for a component. As a result, if I = 0 indicates a maximally stable component, then I = 1 means a maximally unstable component. The following table shows the instability and stability factors for the previous component diagram in this article.
SDP can be summarized as, ”Depend in the direction of stability.”
SAP: The Stable Abstractions Principle
All parts of software are not necessarily equal. There are pieces of software that contain the most important business-critical or technical architecture that we do not want to change frequently. On the other hand, there are other parts and components, which are subject of change. In those cases, it is desired to be changeable in an affordable way.
In other words, the first category of components are the heart of the software, and their stability has competitive advantages for our product. Agility in changing the second part helps us to align business and technical issues cheaply. In such a case, the more stable a component, the more abstract it should be. This means that there is no concrete decision about the technique and technology in the heart of the software. The abstraction level of a component can be measured using the following formula:
Na: The number of abstract classes and interfaces in the component.
Nc: The number of classes in the component.
A: Abstractness, this value would be a number between 0 and 1.
When SAP and SDP are calculated simultaneously, it results in a combined measure which shows that the more a component is stable, the more it should be abstract. Such a component has a reasonable level of decoupling with other components that resist change propagation.
Polymorphism
Polymorphism is one of the essential techniques in a class’ responsibility assignment activity in the Object-Oriented Design. This principle can be traced back mainly to the Abstraction, Hierarchy and Encapsulation principles in the underlying AMEH principles. This technique uses inheritance and realization as tools to the implementation of a unique behavior in different classes and keeps the client of the action safe.
Polymorphism states that if there is an indication that the implementation of a unique behavior can vary, it’s better and more reliable to have separate classes for each implementation instead of integrating them in a single class. This approach for various implementations of a unique behavior is the backbone of DIP and OCP as members of S.O.L.I.D in the next layer of the OO concepts pyramid. I suggest you read my previous article about the role of inheritance to get more detailed information about the inheritance usage for creating reusable software.
Designers use polymorphism based on two main pieces of criteria: first, code needs to be readable; second, extension points of a module shoulde be exposed without any need to change it. In the first approach, designers don’t plan to extend or change the implementation of component behavior outside of the current module. Instead, they use polymorphism to avoid stacking different implementations for behavior in a single class.
Usually, in this case, the interface of a behavior and its implementation are in the same module. Those using the component can then implement to that behavior. This usage of polymorphism results in a local extendibility, which means that although adding a new implementation to a specific behavior results in some changes in working software, these changes are effortless.
On the other hand, if applying polymorphism is intended to leave the doors open for external implementation for the behavior of a module without changing itself, modularity comes in. As previously mentioned, modularity does not have necessarily have a direct role in polymorphism. Modularity can boost polymorphism to present extension points of a module without local changes in the target component. In this case, coupling and cohesion between models have a crucial impact on the quality of applying polymorphism.
If the usage of polymorphism is to apply local extendability and code manageability, adding new modules and other techniques in packaging "just because" does not have any benefit and adds needless complexity in both the development and deployment stages. Vice versa, a module will not be extendable without correct modularity unless it uses polymorphism to implement a specific behavior.
All in all, the concepts of the object-oriented paradigm are weaved together vertically and horizontally. Based on requirements and strategies, designers can solve problems in different levels of difficulty to satisfy those requirements at a reasonable level of complexity in the solution.
Further Reading
- Patterns of Modular Architecture.
- CDI (Part 2): Qualifiers in Java: Polymorphism in DI.