Tuesday, September 19, 2023

Combining software development principles and patterns with GRASP

As software development has evolved over the years, developers have formulated best practices, principles, and design patterns to create more robust and maintainable systems. In this article, we will explore the differences between software development principles and design patterns, and then dive into the GRASP principles. We will also discuss how GRASP principles are combining principles and patterns, and how they can help us to decide what to use. 

What is the difference between software development principles and design patterns? They are both essential concepts in software engineering, but they serve different purposes and operate at different levels of abstraction.

Software development principles are essential for creating high-quality software that is efficient, maintainable, and scalable. By following these principles, development teams can reduce costs, speed up development, and create better products that meet user needs. The principles are high-level guidelines or best practices that inform the software development process. They are often broad and language-agnostic, applying to various programming languages and paradigms. Principles are promoting qualities like maintainability, modularity, efficiency, and simplicity.

You should have a very good reason any time you choose not to follow principles.

Software development design patterns help developers create better software by offering efficient, reusable solutions to common problems that arise during software design. Design patterns lead to improved code quality, easier maintainability, and more effective communication among team members. They also promote scalability, and adaptability, and serve as valuable learning tools for developers. They are more concrete and detailed than principles, providing implementation guidelines for specific design challenges. They may be more closely tied to a particular programming paradigm (e.g., object-oriented, functional, etc.).

You should have a very good reason any time you choose to implement a pattern.

One specific set of principles from object design that offers an interesting way how to think about connecting the principles and patterns is GRASP (General Responsibility Assignment Software Patterns). They were described by Craig Larman in his book Applying UML and Patterns (1997).

It addresses specific development challenges and collects proven programming principles of object-oriented design, rather than being just a set of criteria for creating better software (like SOLID).

It is more a collection of best practices answers to frequently encountered coding challenges and serves as a guide for making design decisionsIt consists of nine principles, answering specific questions:

Creator

 - Who creates an object or a new instance of a class?

 - Assign the responsibility of creating an object to a class that is closely related to it. 

Related patterns are Factory Method or Abstract FactoryThese patterns encapsulate the object creation logic, assigning the responsibility of creating objects to a dedicated factory class.
They promote low coupling and high cohesion by keeping related object-creation logic within a single class.

Information Expert

 - What responsibilities can be assigned to an object?

 - Assign responsibility to the class that has the information necessary to fulfill it. 

Helps us to increase cohesion, promotes encapsulation, and promotes maintainability.

Low Coupling

 - How are objects connected to each other? How to support low dependency, low change impact, and increased reuse?

 - Design classes with minimal dependencies on other classes to promote modularity and improve maintainability and improve reuse potential. 

The Adapter is a design pattern that helps to achieve low coupling. It introduces an adapter class that acts as an intermediary between the incompatible interfaces, reducing the coupling between the classes.

Controller

 - How are input events delegated from the UI/API layer to the domain layer, including coordinating a system operation? 

 - Assign the responsibility of handling system events to a class that represents the overall system, a subsystem, or a use case. The controller is defined as the first object beyond the UI layer that receives and coordinates a system operation. This principle helps in managing system complexity by separating UI from business logic.

The related principle is Pure Fabrication. The related design patterns are, for example, Command and Facade, or Model-View-Controller (MVC).

High Cohesion

 - How are the operations of elements functionally related? How to keep objects focused, understandable, and manageable?

 - The responsibilities of a given set of elements should be strongly related and highly focused on a rather specific topic. Breaking programs into classes and subsystems, if correctly done, is an example of activities that increase cohesion. Classes with closely related responsibilities are more understandable, maintainable, and robust.


Polymorphism

 - How to handle alternative elements based on type? How to create pluggable software components?

 - Assign the responsibility of defining a common interface to related classes, allowing them to be used interchangeably. This principle supports reusability and flexibility. Polymorphic operations should be used instead of explicit branching based on type.

You can use the Strategy pattern here. It defines a common interface for the varying algorithms, allowing them to be used interchangeably. Polymorphism is achieved by using the common interface for different implementations.

Indirection

 - How to avoid a direct coupling between two or more elements and increase reuse potential?

 - Introduce an intermediate class to mediate between other classes, thus reducing coupling and promoting flexibility.

When you want to reduce coupling between a group of classes that communicate with each other, you can apply the Mediator pattern. This pattern introduces a mediator class that acts as an intermediary, managing the communication and relationships between the classes. Another related pattern is, for example, the already mentioned Adapter.

Pure Fabrication

 - How to achieve high cohesion and low coupling of problem domain elements?

 - Assign a responsibility to an artificial class, created just for the purpose of achieving High Cohesion and Low CouplingCalled a ‘service’ in domain-driven design, this class does not represent anything from the problem domain but is created to ensure High Cohesion and Low Coupling are achieved.

Protected Variations

 - How to design objects, subsystems, and systems so that variations in these elements do not impact other elements?

 - Design the system in a way that it is stable in the face of changes by encapsulating variations.

Protected Variations help us to achieve the Robustness of our system. 

You can use the Bridge pattern to ensure that changes in one class hierarchy don't affect another. This pattern separates an abstraction from its implementation, protecting the variations by encapsulating them within separate class hierarchies.


Understanding software development principles, design patterns, and GRASP principles is crucial for developers to create maintainable, scalable, and robust software systems. Applying GRASP principles helps in making better design decisions and potentially choosing the right design pattern for specific problems. 

For instance, if you need a way to create objects of different types based on input data, consider the Factory pattern (based on the Creator and Polymorphism principles).

By following these guidelines, developers can improve the overall quality of the code.