Software Design Principles and Architectural Patterns

Module Design

What is good vs. bad design?

Good design is characterized by high cohesion and low coupling, meaning each module has a clear, focused responsibility and minimal dependency on other modules. This leads to systems that are easier to understand, test, reuse, and maintain. Good design also emphasizes clarity, simplicity, and well-defined interfaces.

Bad design has low cohesion and high coupling, where modules perform unrelated tasks and depend heavily on each other. This results in systems that are difficult to modify, test, and scale, and changes in one module often affect many others.

Fundamentals of module design

Module design is based on decomposing a system into smaller, manageable parts (modules), each responsible for a specific functionality. Key fundamentals include:

  • Abstraction: Focusing on what a module does, not how it does it.
  • Modularity: Dividing the system into independent units.
  • Information hiding: Hiding internal details of modules.
  • Clear interfaces: Defining how modules interact.

These fundamentals ensure that modules can be developed and tested independently.

Fundamental design principles

The main principles are:

  • High cohesion: Keep related functionality together.
  • Low coupling: Minimize dependencies between modules.
  • Abstraction: Define behavior without exposing implementation.
  • Modularity: Divide the system into logical components.

They are applied by designing modules with a single responsibility, defining clear interfaces, and avoiding unnecessary interactions between modules.

Identifying modules and abstract data types

Modules are identified by analyzing system functionality and grouping related responsibilities together. Each module represents a logical part of the system.

Abstract Data Types (ADTs) are defined by:

  • The operations they provide.
  • The behavior they guarantee.

The internal implementation is hidden, which allows flexibility and reuse.

Extracting classes, attributes, and operations

From a problem description:

  • Nouns → Classes or objects.
  • Attributes → Properties of those classes.
  • Verbs → Operations (methods).

This process transforms requirements into a structured design model.

Component-Based Software Engineering (CBSE)

CBSE is an approach where systems are constructed using pre-built, reusable components. Each component is an independent unit that provides specific functionality and can be reused across multiple systems, improving development speed and reliability.

CBSE and design principles

CBSE relies heavily on:

  • Low coupling so components can function independently.
  • High cohesion so components have a clear purpose.
  • Well-defined interfaces to allow interaction.

These principles ensure components are reusable and easily integrated.

Components and interfaces

A component is a reusable software unit that encapsulates functionality. An interface defines how that component is accessed and used by other parts of the system, acting as a contract between components.

Coupling, cohesion, fan-in, and fan-out

  • Cohesion: How closely related elements within a module are (high is good).
  • Coupling: How dependent modules are on each other (low is good).
  • Fan-in: Number of modules that use a given module (high indicates reuse).
  • Fan-out: Number of modules a module depends on (high increases complexity).

USES and COMPRISES diagrams

  • USES diagram: Shows dependencies between modules (which module uses another).
  • COMPRISES diagram: Shows how a system is structured and composed of modules.

USES focuses on interaction; COMPRISES focuses on structure.

Integration test plan

An integration test plan defines how multiple modules are tested together to ensure they interact correctly. It ensures that interfaces between modules function as expected and that the overall system behaves properly.

Structural vs. behavioral models

  • Structural models describe the system’s components and relationships (e.g., class diagrams).
  • Behavioral models describe how the system behaves over time (e.g., interactions, sequences).

Structural = what the system is; Behavioral = what the system does.

Architectural Design

Basic architectural styles

Architectural styles differ in how components interact and how data flows:

  • Layered architecture: System divided into layers, each providing services to the next.
  • Client-server: Clients request services, servers respond.
  • Pipe-and-filter: Data flows through a sequence of processing components.
  • Repository: Components share and interact through a central data store.

Architecture vs. module design

Architecture defines the overall system structure and organization, including how major components interact. Module design focuses on the internal design of individual components. Architecture is high-level; module design is detailed.

Choosing an architectural style

By analyzing system requirements such as data flow, performance needs, scalability, and maintainability, you choose an architecture that best supports these requirements and the way components interact.

Implementation

Programming style and clarity

Programming style and clarity focus on making code easy to read, understand, and maintain:

  • Code must be clear and readable, not just correct.
  • Use meaningful and consistent names for variables, functions, and modules.
  • Follow a consistent structure and formatting style.
  • Keep code simple and avoid unnecessary complexity.
  • Organize code so that the logic is easy to follow.

Choosing a programming language

The choice depends on practical and system-related factors:

  • Application domain (e.g., web, embedded, systems).
  • Performance requirements.
  • Availability of libraries and tools.
  • Ease of development and maintenance.
  • Team experience and familiarity.

Defensive programming

Defensive programming is writing code assuming that errors, unexpected inputs, or misuse will occur, and handling them safely by validating inputs, checking for invalid states, and preventing system crashes.

Assertions

Assertions are checks placed in the code to verify that certain conditions are true during execution. They are used to detect logical errors early and ensure assumptions about the program are correct.

Version control tools

Version control tools are systems used to track changes in source code over time, allowing multiple developers to collaborate, keep history, and enable rollbacks.

Mapping design to implementation

Mapping design to implementation means translating modules and their responsibilities into actual code structures:

  • Each module becomes a class, function, or component.
  • Interfaces defined in design become method signatures or APIs.
  • Relationships between modules become function calls or dependencies.

Kinds of comments

  • Header comments: Describe the purpose of a file, module, or class.
  • Inline comments: Explain specific parts of the code.

Comments should explain why something is done, not just repeat what the code already shows.

Design Patterns

Issues addressed by design patterns

Design patterns address common recurring problems in software design, including tight coupling, lack of flexibility, difficulty reusing code, and complex system structures.

Essential elements of a design pattern

  • Name: Identifies the pattern.
  • Problem: The situation the pattern solves.
  • Solution: The general design approach.
  • Structure: How components are organized.
  • Participants: The elements involved and their roles.

When to apply design patterns

Patterns are applied when a known design problem appears repeatedly, a system needs more flexibility or extensibility, or components need to interact in a structured and decoupled way.

Types of design patterns

  • Creational: Deal with object creation.
  • Structural: Deal with how components are connected.
  • Behavioral: Deal with how components interact.

Design principles reinforced by patterns

Design patterns reinforce low coupling, high cohesion, abstraction, and flexibility.

Observer Pattern

What is the Observer pattern?

A behavioral pattern where one object (Subject) maintains a list of dependent objects (Observers) and notifies them automatically when its state changes.

Structure and participants

  • Subject: The object being observed.
  • Observer: Objects that receive updates.
  • Concrete Observers: Specific implementations of observers.

This creates a one-to-many relationship.

Adapter Pattern

What is the Adapter pattern?

A structural pattern that allows two incompatible interfaces to work together by introducing an adapter that translates between them.

Structure and participants

  • Target: Expected interface.
  • Adapter: Translates the interface.
  • Adaptee: Existing incompatible component.

The adapter acts as a bridge between the two.