Design Pattern Overview

Design patterns are like proven solutions to common problems that software developers face. They give guidelines on structuring code and solving design issues flexibly and efficiently. Design patterns make code easier to read, reuse, and scale, making software development more efficient and reliable.

They are helpful to developers who focus on essential elements of the design instead of getting caught up in small details. The design patterns are a set of best practices developers can use to improve their software.

They promote good coding habits and make it easier to build software that can be easily modified and expanded upon in the future.

Types of Design Patterns

Design Pattern – Overview

1. Creational Patterns: These patterns focus on how objects are created. They provide flexible and controlled ways to create objects. Examples consist of Factory, Singleton, and Prototype patterns.

Singleton Pattern Example

The Singleton pattern ensures a single instance of a class is globally accessible.

Source Code:

class Singleton:

    _instance = None

    def __new__(cls):

        if not cls._instance:

            cls._instance = super().__new__(cls)

        return cls._instance

# Usage

instance1 = Singleton()

instance2 = Singleton()

print(instance1 is instance2)

Output :

Design Pattern – Overview

Factory Pattern Example :

from abc import ABC, abstractmethod

class Animal(ABC):

    @abstractmethod

    def speak(self):

        pass

class Dog(Animal):

    def speak(self):

        return "Woof!"

class Cat(Animal):

    def speak(self):

        return "Meow!"

class AnimalFactory:

    @staticmethod

    def create_animal(animal_type):

        if animal_type == 'dog':

            return Dog()

        elif animal_type == 'cat':

            return Cat()

        else:

            raise ValueError(f"Invalid animal type: {animal_type}")

# Usage

factory = AnimalFactory()

dog = factory.create_animal('dog')

print(dog.speak())

cat = factory.create_animal('cat')

print(cat.speak())

Output :

Design Pattern – Overview

 Prototype Pattern :

The Prototype pattern allows for creating new objects by existing objects, promoting reusability and reducing the need for complex initialization processes or subclassing.

Source Code :

import copy

class Prototype:

    def clone(self):

        pass

class Rectangle(Prototype):

    def __init__(self, width, height):

        self.width = width

        self.height = height

    def clone(self):

        return copy.deepcopy(self)

    def __str__(self):

        return f"Rectangle: width={self.width}, height={self.height}"

# Usage

rectangle = Rectangle(10, 5)

print(rectangle)

clone_rectangle = rectangle.clone()

print(clone_rectangle)

clone_rectangle.width = 7

print(clone_rectangle)

Output :

Design Pattern – Overview

2. Structural Patterns: These patterns organize and compose classes and objects. They enhance the functionality and relationships between objects. Examples consist of Adapters, decorators, and Composite patterns

Adapter Pattern: The Adapter pattern converts the interface of one class into another interface that clients expect, enabling objects with incompatible interfaces to work together.

Source Code :

# Adaptee class

class LegacyCalculator:

    def add_numbers(self, x, y):

        return x + y

# Target interface

class Calculator:

    def add(self, x, y):

        pass

# Adapter class

class CalculatorAdapter(Calculator):

    def __init__(self, legacy_calculator):

        self.legacy_calculator = legacy_calculator

    def add(self, x, y):

        return self.legacy_calculator.add_numbers(x, y)

# Usage

legacy_calculator = LegacyCalculator()

adapter = CalculatorAdapter(legacy_calculator)

result = adapter.add(5, 3)

print(result)

Output:

Design Pattern – Overview

Composite Pattern :

The Composite pattern enables you to create hierarchical structures of objects where individual and group objects can be treated uniformly.

Source Code :

# Component interface

class Component:

    def operation(self):

        pass

# Leaf class

class Leaf(Component):

    def operation(self):

        print("Leaf operation")

# Composite class

class Composite(Component):

    def __init__(self):

        self.children = []

    def add(self, component):

        self.children.append(component)

    def remove(self, component):

        self.children.remove(component)

    def operation(self):

        print("Composite operation")

        for child in self.children:

            child.operation()

# Usage

leaf1 = Leaf()

leaf2 = Leaf()

composite = Composite()

composite.add(leaf1)

composite.add(leaf2)

composite.operation()

Output:

Design Pattern – Overview

3. Behavioral Patterns: These patterns focus on how objects communicate and interact with each other. They define patterns of communication and collaboration to ensure practical object cooperation. Examples includes Observer, Strategy, and Command patterns.

Command Pattern:

The Command sample encapsulates requests as objects, decoupling senders from receivers and allowing for flexible request handling and undoable operations.

Source Code:

# Receiver class

class Light:

    def turn_on(self):

        print("Light is on.")

    def turn_off(self):

        print("Light is off.")

# Command interface

class Command:

    def execute(self):

        pass

# Concrete command classes

class TurnOnCommand(Command):

    def __init__(self, light):

        self.light = light

    def execute(self):

        self.light.turn_on()

class TurnOffCommand(Command):

    def __init__(self, light):

        self.light = light

    def execute(self):

        self.light.turn_off()

# Usage

light = Light()

turn_on_command = TurnOnCommand(light)

turn_off_command = TurnOffCommand(light)

# Execute the commands

turn_on_command.execute()

turn_off_command.execute()

Output :

Design Pattern – Overview

Advantages of Design Patterns

1. Problem-Solving: Design patterns offer proven solutions to recurring problems. By following established patterns, we can efficiently address common challenges, saving time and effort in finding solutions from scratch.

2. Best Practices: Design patterns encapsulate best practices and guidelines from experienced developers. The industry knowledge and collective wisdom ensure that we adopt attempted-and-examined approaches that lead to robust, maintainable, and efficient solutions.

3. Code Readability and Maintainability: Design patterns promote code organization and structure. They improve code readability, making it easier for developers to understand and maintain.

4. Collaboration and Communication: Design patterns provide a common language and vocabulary for developers. They serve as a communication tool, enabling teams to discuss and share design decisions effectively. This shared understanding fosters collaboration, teamwork, and a smoother development process.

5. Flexibility and Adaptability: Design patterns enhance the adaptability and flexibility of software systems. By designing applications with patterns, we can more easily accommodate changes, new features, and evolving requirements. Patterns facilitate modularity, making modifying or replacing specific components easier without affecting the entire system.

6. Maintainable Code: Design patterns improve code maintainability by separating concerns, encapsulating functionality, and promoting modularity. They make it easier to manage changes and keep the codebase organized.

7. Scalability and Performance: Certain design patterns improve scalability and performance. For example, the sample reduces memory usage by sharing common data, while the pattern controls resource access, enhancing performance.