← Back to list

Part 3/3 Basics of Software Architecture and Design Patterns

Intro 3

Florian Zeba · 2025-02-26 11:47 · 0 claps · 5.8 min read
#software-architecture #data-architecture #software-engineering #design-patterns #design-pattern-in-python
Open on Medium ↗
Wiki topics: 🏛️ · Architecture

Part 3/3 Basics of Software Architecture and Design Patterns

Intro 3

The last part of the design pattern series is all about examples and practical implementations. We will dive into the different types and principles of design patterns and how they can be used in your projects.

Design Patterns in Software Development

1. Creational Patterns (Object Creation Mechanisms)

  • Singleton: Ensures only one instance of a class is created and provides a global point of access to it.
  • Factory Method: Creates objects without specifying the exact class to create.
  • Abstract Factory: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
  • Builder: Separates the construction of a complex object from its representation.
  • Prototype: Creates new objects by copying an existing object, known as the prototype.

2. Structural Patterns (Composition of Classes or Objects)

  • Adapter (Wrapper): Allows incompatible interfaces to work together.
  • Bridge: Separates an object’s abstraction from its implementation.
  • Composite: Composes objects into tree structures to represent part-whole hierarchies.
  • Decorator: Adds new functionality to an object dynamically.
  • Facade: Provides a simplified interface to a complex system.
  • Flyweight: Reduces memory usage by sharing common parts of state between multiple objects.
  • Proxy: Provides a placeholder for another object to control access to it.

3. Behavioral Patterns (Communication Between Objects)

  • Chain of Responsibility: Passes requests along a chain of handlers.
  • Command: Encapsulates a request as an object, allowing for parameterization of requests.
  • Interpreter: Defines a grammar for interpreting sentences in a language.
  • Iterator: Provides a way to access elements of a collection sequentially.
  • Mediator: Reduces coupling between classes by centralizing communication.
  • Memento: Captures and restores an object’s internal state.
  • Observer (Publish-Subscribe): Defines a dependency between objects so that when one changes state, all dependents are notified.
  • State: Allows an object to alter its behavior when its internal state changes.
  • Strategy: Defines a family of algorithms, encapsulates each one, and makes them interchangeable.
  • Template Method: Defines the skeleton of an algorithm, deferring steps to subclasses.
  • Visitor: Adds new operations to a class hierarchy without modifying the classes.

4. Concurrency Patterns (Managing Multi-threaded Applications)

  • Active Object: Decouples method execution from method invocation.
  • Balking: Prevents an operation from being executed if the object is in an improper state.
  • Double-Checked Locking: Reduces overhead when initializing resources in a multithreaded environment.
  • Guarded Suspension: Manages operations that require preconditions to be met.
  • Monitor Object: Synchronizes access to an object across multiple threads.
  • Read-Write Lock: Allows multiple readers or one writer at a time.
  • Thread Pool: Manages a pool of worker threads to efficiently handle multiple tasks.

5. Architectural Patterns (High-Level Structures of Software Systems)

  • Layered Architecture (n-tier): Organizes the system into layers with specific responsibilities.
  • Client-Server: Separates the client and server roles.
  • Master-Slave: Separates distributed processes into masters and slaves.
  • Pipe and Filter: Breaks down processes into a sequence of processing stages.
  • Model-View-Controller (MVC): Separates concerns into Model, View, and Controller.
  • Model-View-ViewModel (MVVM): Separates logic and UI, common in frameworks like WPF.
  • Microservices Architecture: Structures an application as a collection of small, independent services.
  • Event-Driven Architecture: Uses events to trigger communication between decoupled services.
  • Space-Based Architecture: Reduces the load on databases by using in-memory data grids.
  • Service-Oriented Architecture (SOA): Builds systems from reusable services.

6. Cloud-Native and Distributed Systems Patterns

  • Circuit Breaker: Prevents repeated execution of failed requests.
  • API Gateway: Acts as a single entry point for all microservices.
  • Service Mesh: Manages service-to-service communication.
  • Sidecar Pattern: Attaches additional functionality to a service without modifying it.
  • Saga Pattern: Manages distributed transactions using compensating transactions.
  • CQRS (Command Query Responsibility Segregation): Separates commands from queries.
  • Event Sourcing: Stores the state changes as a sequence of events.

7. Enterprise Integration Patterns

  • Aggregator: Combines multiple messages into one.
  • Message Broker: Routes messages between services.
  • Message Queue: Manages the delivery of messages between services.
  • Content-Based Router: Routes messages based on their content.
  • Publish-Subscribe Channel: Sends messages to multiple subscribers.

Now let’s dive into some practical examples of these design patterns in Python!

1. Creational Patterns

Singleton Pattern

Ensures a class has only one instance and provides a global point of access.

Example in Python:

class Singleton:
    _instance = None
def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
# Usage
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)  # True

Factory Method

Defines an interface for creating objects but lets subclasses alter the type of objects that will be created.

Example:

from abc import ABC, abstractmethod
class Product(ABC):
    @abstractmethod
    def operation(self):
        pass
class ConcreteProductA(Product):
    def operation(self):
        return "Product A"
class ConcreteProductB(Product):
    def operation(self):
        return "Product B"
class Factory:
    @staticmethod
    def create_product(type_):
        if type_ == "A":
            return ConcreteProductA()
        elif type_ == "B":
            return ConcreteProductB()
        raise ValueError("Unknown product type")
# Usage
product = Factory.create_product("A")
print(product.operation())  # "Product A"

Abstract Factory

Provides an interface for creating families of related or dependent objects.

Example:

class AbstractFactory(ABC):
    @abstractmethod
    def create_product(self):
        pass
class ConcreteFactoryA(AbstractFactory):
    def create_product(self):
        return ConcreteProductA()
class ConcreteFactoryB(AbstractFactory):
    def create_product(self):
        return ConcreteProductB()
# Usage
factory = ConcreteFactoryA()
product = factory.create_product()
print(product.operation())  # "Product A"

Builder Pattern

Separates object construction from its representation.

Example:

class Product:
    def __init__(self):
        self.parts = []
def add(self, part):
        self.parts.append(part)
    def show(self):
        print(", ".join(self.parts))
class Builder:
    def build_part(self):
        pass
class ConcreteBuilder(Builder):
    def __init__(self):
        self.product = Product()
    def build_part(self):
        self.product.add("Part A")
        self.product.add("Part B")
    def get_result(self):
        return self.product
# Usage
builder = ConcreteBuilder()
builder.build_part()
product = builder.get_result()
product.show()  # "Part A, Part B"

Prototype Pattern

Creates objects by cloning an existing object.

Example:

import copy
class Prototype:
    def clone(self):
        return copy.deepcopy(self)
class ConcretePrototype(Prototype):
    def __init__(self, value):
        self.value = value
# Usage
prototype = ConcretePrototype([1, 2, 3])
clone = prototype.clone()
print(clone.value)  # [1, 2, 3]

2. Structural Patterns

Adapter Pattern

Allows incompatible interfaces to work together.

Example:

class OldSystem:
    def specific_request(self):
        return "Old system output"
class Adapter:
    def __init__(self, old_system):
        self.old_system = old_system
    def request(self):
        return self.old_system.specific_request()
# Usage
adapter = Adapter(OldSystem())
print(adapter.request())  # "Old system output"

Bridge Pattern

Separates abstraction from implementation.

Example:

class Implementation:
    def operation(self):
        pass
class ConcreteImplementationA(Implementation):
    def operation(self):
        return "ConcreteImplementationA"
class Abstraction:
    def __init__(self, implementation):
        self.implementation = implementation
    def operation(self):
        return self.implementation.operation()
# Usage
implementation = ConcreteImplementationA()
abstraction = Abstraction(implementation)
print(abstraction.operation())  # "ConcreteImplementationA"

Decorator Pattern

Dynamically adds behavior to objects.

Example:

class Component:
    def operation(self):
        pass
class ConcreteComponent(Component):
    def operation(self):
        return "ConcreteComponent"
class Decorator(Component):
    def __init__(self, component):
        self.component = component
    def operation(self):
        return f"Decorator({self.component.operation()})"
# Usage
component = ConcreteComponent()
decorated = Decorator(component)
print(decorated.operation())  # "Decorator(ConcreteComponent)"

Facade Pattern

Provides a simplified interface to a complex subsystem.

Example:

class SubsystemA:
    def operation(self):
        return "SubsystemA"
class SubsystemB:
    def operation(self):
        return "SubsystemB"
class Facade:
    def __init__(self):
        self.subsystemA = SubsystemA()
        self.subsystemB = SubsystemB()
    def operation(self):
        return f"{self.subsystemA.operation()} + {self.subsystemB.operation()}"
# Usage
facade = Facade()
print(facade.operation())  # "SubsystemA + SubsystemB"

3. Behavioral Patterns

Observer Pattern

Allows objects to notify others of state changes.

Example:

class Subject:
    def __init__(self):
        self._observers = []
def attach(self, observer):
        self._observers.append(observer)
    def notify(self, message):
        for observer in self._observers:
            observer.update(message)
class Observer:
    def update(self, message):
        print(f"Observer received: {message}")
# Usage
subject = Subject()
observer = Observer()
subject.attach(observer)
subject.notify("Hello, World!")  # "Observer received: Hello, World!"

Command Pattern

Encapsulates a request as an object.

Example:

class Command:
    def execute(self):
        pass
class ConcreteCommand(Command):
    def __init__(self, receiver):
        self.receiver = receiver
    def execute(self):
        self.receiver.action()
class Receiver:
    def action(self):
        print("Action executed")
# Usage
receiver = Receiver()
command = ConcreteCommand(receiver)
command.execute()  # "Action executed"

State Pattern

Allows an object to change its behavior when its internal state changes.

Example:

class State:
    def handle(self):
        pass
class ConcreteStateA(State):
    def handle(self):
        return "State A"
class ConcreteStateB(State):
    def handle(self):
        return "State B"
class Context:
    def __init__(self, state):
        self.state = state
    def request(self):
        return self.state.handle()
# Usage
context = Context(ConcreteStateA())
print(context.request())  # "State A"
context.state = ConcreteStateB()
print(context.request())  # "State B"

3. Behavioral Patterns (continued)

Chain of Responsibility Pattern

Passes requests along a chain of handlers.

Example:

class Handler:
    def __init__(self, successor=None):
        self.successor = successor
def handle_request(self, request):
        if self.successor:
            self.successor.handle_request(request)
class ConcreteHandlerA(Handler):
    def handle_request(self, request):
        if request == "A":
            print("Handled by HandlerA")
        else:
            super().handle_request(request)
class ConcreteHandlerB(Handler):
    def handle_request(self, request):
        if request == "B":
            print("Handled by HandlerB")
        else:
            super().handle_request(request)
# Usage
handler_chain = ConcreteHandlerA(ConcreteHandlerB())
handler_chain.handle_request("B")  # "Handled by HandlerB"

Mediator Pattern

Reduces coupling by centralizing communication between objects.

THE REST CAN BE FOUND HERE fzeba.com.

(To copy, paste and correct 50 code blocks into medium is just too much. If you are interested in the topic and the full list of design patterns, kindly visit my personal site with the full article. THANK YOU!)

Conclusion

You now have a comprehensive guide to software architecture design patterns, complete with:

  • Creational Patterns: Singleton, Factory, Builder, Prototype
  • Structural Patterns: Adapter, Bridge, Composite, Decorator, Facade
  • Behavioral Patterns: Observer, Command, Strategy, State, Visitor
  • Concurrency Patterns: Thread Pool, Read-Write Lock, Circuit Breaker
  • Cloud-Native Patterns: API Gateway, Saga, CQRS, Event Sourcing
  • Enterprise Integration Patterns (EIP): Aggregator, Message Broker, Content-Based Router, Pub-Sub

Read this article and more on fzeba.com.


메타데이터
post_id
72019dde2eb6
slug
part-3-3-basics-of-software-architecture-and-design-patterns-72019dde2eb6
url
https://medium.com/@flnzba/part-3-3-basics-of-software-architecture-and-design-patterns-72019dde2eb6
canonical_url
https://medium.com/@flnzba/part-3-3-basics-of-software-architecture-and-design-patterns-72019dde2eb6
author_url
https://medium.com/@flnzba
status
ok
fetched_at
2026-06-15 20:49:13