Design & Architecture


The world of software development is now nearing a century. Over these years, software industry has seen drastic changes in the technologies and domains. Starting from mechanically operated binary switches to serverless computing on the cloud, it has been a long journey. But some of the core principles have and will remain untouched and unchanged. One of the most important (and also most neglected) one is the importance of a good Design & Architecture.

Its importance can never be exaggerated. Unfortunately, that is where projects squeeze their time and end up in disaster. Here, we will look into several core principles of design.

These years have seen some fundamental changes in software development. The complexity of software applications has grown exponentially. The trade-offs between memory, network and processing have changed their base. There was a drastically but asymmetric growth in each of them. Naturally, there were changes in the principles and patterns of design. But not in its importance.

Before proceeding, we must note and understand an important point. Using Design Patterns does not imply a good design. Just because we have used several patterns does not mean that we can develop highly maintable and reusable code. Certainly not. These patterns can at best, provide a good, tested way of going around a design component. But it is important to understand which pattern is meaningful in a given design. Without this understanding, they are meaningless.

Design Patterns


Each phase of software development brought forth a new set of design principles that replaced the older ones. These were not any better or worse. Just that some are more relevant in the given constraints of the application development. Let us have a brief look at some of the important ones.

Design Patterns in Functional Programming


Functional desigh patterns can be classified into Data Structures and Algorithms.

Object Oriented Design Patterns


Object Oriented Design Patterns can be classified into three main types:

Creational Patterns


Structural Patterns


  • Adapter - allows classes with incompatible interfaces to work together by wrapping its own interface around that of an already existing class.
  • Bridge - decouples an abstraction from its implementation so that the two can vary independently.
  • Composite - composes zero-or-more similar objects so that they can be manipulated as one object.
  • Decorator - dynamically adds/overrides behaviour in an existing method of an object.
  • Facade - provides a simplified interface to a large body of code.
  • Flyweight - reduces the cost of creating and manipulating a large number of similar objects.
  • Proxy - provides a placeholder for another object to control access, reduce cost, and reduce complexity.

Behavioral Patterns


  • Chain of responsibility - delegates commands to a chain of processing objects.
  • Command - creates objects which encapsulate actions and parameters.
  • Interpreter - implements a specialized language.
  • Iterator - This provides accesses the elements of an object sequentially without exposing its underlying representation. This pattern is redundant because of the elaborate collections framework in almost all recent languages.
  • Mediator - allows loose coupling between classes by being the only class that has detailed knowledge of their methods.
  • Memento - provides the ability to restore an object to its previous state (undo).
  • Observer - is a publish/subscribe pattern which allows a number of observer objects to see an event.
  • State - allows an object to alter its behavior when its internal state changes.
  • Strategy - allows one of a family of algorithms to be selected on-the-fly at runtime.
  • Template method - defines the skeleton of an algorithm as an abstract class, allowing its subclasses to provide concrete behavior.
  • Visitor - separates an algorithm from an object structure by moving the hierarchy of methods into one object.

The list can never be exhaustive. Many more patterns will evolve as the constraints and requirements change. But the fundamental concepts of design and the importance of a good architecture will never vary.

Changes in Paradigm


Long long ago, we started with assembly code on microprocessors. Here, everything was restricted. The code size was limited by the ROM size. The processing power was limited by the clock speed and the RAM size. And there was barely any concept of connectivity. The design in those days was focused on reducing the code size and resources - not on reuse. That was possible because the applications were never so complex and redevelopment of modules was hardly a problem.

Over the years, as the processing power increased, it was possible to increase the complexity of the software. That brought in programming languages. It brought in the concept of reuse of software. Now, it was important to create software modules that could be reused. This changed the requirements of a good design. Another criteria was added to the concept of a good design - reuse. The others like performance were relaxed a bit.

Over the years, as the processing power and complexity increased further, we had to do more. Just reuse was not enough. It had to be maintainable. What one person developed was maintained and enhanced by another. So we had to develop code that was readable and maintainable and reusable. All this came at a price of performance and that was tolerable.

This trend followed, and today, most of the applications work on micro-services on the cloud. One barely knows how a functionality is implemented. The code just invokes an API and expects that the job is done. Most of this works over the network. This is a terribly inefficient use of network bandwidth and processing power. But we can afford it because of the amount of complexity, reusability, maintainability we can pack into our application.