Skip to content

Software Design Guide

This guide covers the foundational principles of Object-Oriented Programming (OOP), the SOLID principles for robust design, and a catalog of common design patterns.

Table of Contents

  1. Object-Oriented Programming (OOP)
  2. SOLID Principles
  3. Design Patterns

Object-Oriented Programming (OOP)

Good object-oriented designs are reusable, extensible, and maintainable. The goal is to implement classes in a way that prevents unauthorized access to or modification of the original contents of a class by its instances.

Core Concepts

  • Class: A blueprint or template for creating objects. It defines the attributes (state) and methods (behavior) that its objects will have.
  • Object: An instance of a class. While a class is a blueprint, an object is the actual entity that exists in memory and has its own state.
  • Method: A function associated with an object that can modify its state or perform an action.
  • Access Modifiers: Keywords (public, private, protected) that define the scope and visibility of classes, methods, and attributes.

The Four Pillars of OOP

1. Encapsulation

  • What it is: The bundling of data (attributes) and the methods that operate on that data into a single unit, the class. It restricts direct access to an object’s state, a principle known as data hiding.
  • How it works: Other objects cannot access the private state directly. Instead, they must interact through public methods (getters and setters). This allows an object to manage its own state.
  • Advantages:
    • Simplifies maintenance and modification.
    • Improves security by hiding internal data.
    • Increases modularity.

2. Abstraction

  • What it is: The process of hiding complex implementation details and exposing only the essential features of an object. It shows what an object does, not how it does it.
  • How it works: Achieved using abstract classes and interfaces. This simplifies complex systems from the user’s perspective.
  • Advantages:
    • Reduces complexity.
    • Promotes code reusability and extensibility.
    • Improves maintainability.

3. Inheritance

  • What it is: The mechanism by which one class (the child or subclass) acquires the properties and methods of another class (the parent or superclass). This models an “is-a” relationship.
  • How it works: A new class can be created from an existing class, reusing its fields and methods while adding new functionality. The extends keyword is commonly used.
  • Types of Inheritance:
    • Single: A class extends from one parent class.
    • Multi-level: A class is derived from a class that is also derived from another class (a chain).
    • Hierarchical: Multiple classes extend from the same parent class.
    • Multiple: A class is derived from more than one parent class. (Not directly supported in languages like Java, but can be achieved with interfaces).
    • Hybrid: A combination of two or more types of inheritance.
  • Advantages:
    • Promotes code reusability.
    • Simplifies code modification.
    • Provides extensibility.

4. Polymorphism

  • What it is: The ability of an object to take on many forms. It allows a single action or method name to be used for different types of objects, with each object responding in its own way.
  • Types of Polymorphism:
    • Static / Compile-Time Polymorphism: Achieved via method overloading, where multiple methods have the same name but different parameters (either in number or type).
    • Dynamic / Run-Time Polymorphism: Achieved via method overriding, where a subclass provides a specific implementation for a method that is already defined in its parent class. The correct method to call is resolved at runtime.

Other Key OOP Concepts

  • Abstract Class: A class that cannot be instantiated and may contain abstract methods (methods without an implementation). Subclasses must provide an implementation for all abstract methods.
  • Interface: A contract that defines a set of methods a class must implement. It specifies what a class must do, but not how. An interface can only contain final static variables and abstract method signatures.

Interface vs. Abstract Class

  • Use an interface when you want to define a contract that can be implemented by unrelated classes.
  • Use an abstract class when you want to provide a base implementation and share code among closely related subclasses.
  • Casting:
    • Upcasting: Casting a subclass reference to a superclass type (e.g., Vehicle v = new Car();). This is done implicitly.
    • Downcasting: Casting a superclass reference back to its original subclass type (e.g., Car c = (Car)v;). This must be done explicitly and can fail if the object is not of the target type.

SOLID Principles

A set of five design principles that guide the development of software that is understandable, flexible, and maintainable.

  • S - Single Responsibility Principle: A class should have only one reason to change. It should be responsible for a single part or functionality of the system.
  • O - Open/Closed Principle: Software entities (classes, modules, functions) should be open for extension but closed for modification. New functionality should be added by writing new code, not by changing existing code.
  • L - Liskov Substitution Principle: Objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.
  • I - Interface Segregation Principle: Clients should not be forced to depend on interfaces they do not use. It’s better to have many small, client-specific interfaces than one large, general-purpose one.
  • D - Dependency Inversion Principle: High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g., interfaces). Abstractions should not depend on details; details should depend on abstractions.

Design Patterns

Design patterns are reusable solutions to commonly occurring problems within a given context in software design.

Creational Design Patterns

Patterns that deal with object creation mechanisms, trying to create objects in a manner suitable to the situation.

  • Singleton: Ensures a class has only one instance and provides a global point of access to it.
    • Use Cases: Services, databases, configuration objects.
  • Factory Method: Defines an interface for creating an object but lets subclasses alter the type of objects that will be created.
    • Use Cases: When the exact type of objects needed cannot be anticipated beforehand.
  • Abstract Factory: Lets you produce families of related objects without specifying their concrete classes.
    • Use Cases: When your system needs to be independent of how its products are created, composed, and represented.
  • Builder: Lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.
    • Use Cases: Creating complex objects with many optional parts or configurations.
  • Prototype: Lets you copy existing objects without making your code dependent on their classes.
    • Use Cases: When the cost of creating an object is more expensive than copying one.

Structural Design Patterns

Patterns that explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient.

  • Adapter: Allows objects with incompatible interfaces to collaborate.
    • Use Cases: To make existing classes work with others without modifying their source code.
  • Bridge: Lets you split a large class or a set of closely related classes into two separate hierarchies—abstraction and implementation—which can be developed independently of each other.
    • Use Cases: To extend a class in several independent dimensions; to change an implementation at runtime.
  • Composite: Lets you compose objects into tree structures and then work with these structures as if they were individual objects.
    • Use Cases: To represent part-whole hierarchies of objects (e.g., UI elements, file systems).
  • Decorator: Lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.
    • Use Cases: To add responsibilities to individual objects dynamically and transparently.
  • Facade: Provides a simplified interface to a library, a framework, or any other complex set of classes.
    • Use Cases: To provide a simple entry point to a complex subsystem.
  • Flyweight: Lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object.
    • Use Cases: To reduce memory usage when creating a large number of similar objects.
  • Proxy: Lets you provide a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets to the original object.
    • Use Cases: Caching, lazy initialization, access control.

Behavioral Design Patterns

Patterns that are concerned with algorithms and the assignment of responsibilities between objects.

  • Chain of Responsibility: Lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
    • Use Cases: When a request can be handled by different objects, and the exact handler isn’t known beforehand.
  • Command: Turns a request into a stand-alone object that contains all information about the request. This transformation lets you parameterize methods with different requests, delay or queue a request’s execution, and support undoable operations.
    • Use Cases: To queue operations, support undo/redo, and keep a history of requests.
  • Iterator: Lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).
    • Use Cases: To provide a uniform way to traverse different collections.
  • Mediator: Lets you reduce chaotic dependencies between objects. The pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object.
    • Use Cases: To reduce coupling between a large number of objects and centralize control.
  • Observer: Lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.
    • Use Cases: To implement event-driven systems where the state of one object affects others.
  • Visitor: Lets you separate algorithms from the objects on which they operate. It allows you to add new operations to a class hierarchy without modifying the classes themselves.
    • Use Cases: To perform an operation on all elements of a complex object structure.