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
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 :
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 :
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 :
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:
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:
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 :
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.