The Gang of Four, a term derived from the pioneering book "Design Patterns: Elements of Reusable Object-Oriented Software" written by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, introduced 23 essential design patterns that offer solutions for common software design problems. These patterns are categorized into three groups: creational, structural, and behavioral patterns. Each pattern addresses specific challenges in software design, leading to more maintainable, flexible, and scalable code.
Creational patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation.
The Singleton Pattern restricts the instantiation of a class to one single instance. This is useful when exactly one object is needed to coordinate actions across your system.
Example:
class Singleton: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(Singleton, cls).__new__(cls) return cls._instance singleton1 = Singleton() singleton2 = Singleton() print(singleton1 is singleton2) # Output: True
The Factory Method Pattern defines an interface for creating an object, but let subclasses alter the type of objects that will be created.
Example:
class Car: def drive(self): return "Driving a car" class Truck: def drive(self): return "Driving a truck" class VehicleFactory: def get_vehicle(self, vehicle_type): if vehicle_type == "Car": return Car() elif vehicle_type == "Truck": return Truck() return None factory = VehicleFactory() vehicle = factory.get_vehicle("Car") print(vehicle.drive()) # Output: Driving a car
Structural patterns deal with object composition. They help ensure that if one part of a system changes, the entire system doesn't need to change.
The Adapter Pattern acts as a bridge between two incompatible interfaces. This is particularly useful when you want to use a class that does not implement the interface you need.
Example:
class EuropeanSocket: def voltage(self): return 230 class AmericanSocket: def voltage(self): return 120 class SocketAdapter: def __init__(self, socket): self.socket = socket def voltage(self): if isinstance(self.socket, EuropeanSocket): return 120 # Adapting the voltage return self.socket.voltage() euro_socket = EuropeanSocket() adapter = SocketAdapter(euro_socket) print(adapter.voltage()) # Output: 120
The Composite Pattern lets clients treat individual objects and compositions of objects uniformly. This is particularly useful for representing hierarchies.
Example:
class Graphic: def draw(self): pass class Circle(Graphic): def draw(self): return "Drawing a Circle" class Square(Graphic): def draw(self): return "Drawing a Square" class CompositeGraphic(Graphic): def __init__(self): self.graphics = [] def add(self, graphic): self.graphics.append(graphic) def draw(self): return " + ".join(graphic.draw() for graphic in self.graphics) circle = Circle() square = Square() composite = CompositeGraphic() composite.add(circle) composite.add(square) print(composite.draw()) # Output: Drawing a Circle + Drawing a Square
Behavioral patterns focus on communication between objects, what goes on between objects and how they operate together.
The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Example:
class Subject: def __init__(self): self.listeners = [] def attach(self, listener): self.listeners.append(listener) def notify(self, message): for listener in self.listeners: listener.update(message) class Listener: def update(self, message): print(f"Received message: {message}") subject = Subject() listener1 = Listener() listener2 = Listener() subject.attach(listener1) subject.attach(listener2) subject.notify("Hello Observers!") # Output: Received message: Hello Observers! # Received message: Hello Observers!
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This helps clients choose an algorithm from a family of algorithms at runtime.
Example:
class Strategy: def execute(self): pass class ConcreteStrategyA(Strategy): def execute(self): return "Strategy A executed" class ConcreteStrategyB(Strategy): def execute(self): return "Strategy B executed" class Context: def __init__(self, strategy): self.strategy = strategy def apply_strategy(self): return self.strategy.execute() context = Context(ConcreteStrategyA()) print(context.apply_strategy()) # Output: Strategy A executed context.strategy = ConcreteStrategyB() print(context.apply_strategy()) # Output: Strategy B executed
Understanding these patterns can significantly improve your design skills, making your systems easier to manage and evolve over time. The GoF design patterns provide a common language and shared understanding among developers, enhancing collaboration and productivity. Whether you're a novice eager to learn or a seasoned developer looking to refresh your skills, mastering these patterns is a crucial step towards effective software design.
06/09/2024 | Design Patterns
09/10/2024 | Design Patterns
12/10/2024 | Design Patterns
03/09/2024 | Design Patterns
12/10/2024 | Design Patterns
03/09/2024 | Design Patterns
09/10/2024 | Design Patterns
09/10/2024 | Design Patterns
09/10/2024 | Design Patterns
03/09/2024 | Design Patterns