• HINDI
  •    
  • Saturday, 17-Jan-26 04:38:00 IST
Tech Trending :
* 🤖How OpenAI + MCP Servers Can Power the Next Generation of AI Agents for Automation * 📚 Book Recommendation System Using OpenAI Embeddings And Nomic Atlas Visualization

⚡ Understanding Design Patterns in Software Development-The Memento Pattern,The Observer Pattern,The Strategy Pattern

Contents

Table of Contents

    Contents
    ⚡ Understanding Design Patterns in Software Development-The Memento Pattern,The Observer Pattern,The Strategy Pattern

    ⚡ Understanding Design Patterns in Software Development-The Memento Pattern,The Observer Pattern,The Strategy Pattern

    In the world of software engineering, challenges often repeat themselves across different projects. Instead of reinventing the wheel every time, developers rely on design patterns—proven blueprints that provide elegant and reusable solutions to common problems.


    📘 What Are Design Patterns?

    A design pattern is a general, reusable solution to a recurring problem in a specific context of software design. Unlike ready-made code snippets, patterns are conceptual templates that guide how to structure your classes, objects, and system interactions.

    Think of them as architectural blueprints for a house. They don’t build the house for you, but they outline the framework and relationships between different parts, making it easier to build, modify, and scale.


    🚀 Key Benefits of Design Patterns

    ♻️ Code Reusability

    Design patterns provide solutions that have been tested, refined, and proven across multiple projects.

    🛠️ Maintainability

    By encouraging clean, understandable, and structured code, patterns make systems easier to maintain and extend.

    🔑 Communication

    Patterns act as a shared vocabulary for developers. Instead of long explanations, saying “Let’s use the Singleton pattern here” immediately conveys intent.

    📈 Scalability

    Well-structured code prepares systems for growth, reducing the amount of refactoring needed when applications expand.

    ⏱️ Efficiency

    Since patterns are widely accepted and well-tested, they save time and effort.


    🌟 Why Design Patterns Matter

    At their core, design patterns improve collaboration, reduce complexity, and promote best practices in software design. Whether you’re designing enterprise-level applications or small-scale tools, patterns provide a solid foundation for sustainable, efficient, and scalable codebases.


    🧩 Understanding the Types of Design Patterns

    Design patterns are the building blocks of clean and scalable software. They provide time-tested solutions to common design problems, helping developers write code that’s both efficient and maintainable. Broadly, design patterns are classified into three main types — Creational, Structural, and Behavioral.


    ⚙️ Creational Patterns

    These patterns focus on object creation mechanisms, ensuring flexibility and reusability when creating objects.
    Examples: Factory Pattern, Singleton Pattern, Builder Pattern.

    💡 Use Case: A document factory that generates different file types — Word, PDF, or Excel — based on input parameters.


    🧱 Structural Patterns

    Structural patterns deal with how classes and objects are composed to form larger, more complex structures.
    Examples: Adapter Pattern, Composite Pattern, Decorator Pattern.

    💡 Use Case: Using the Adapter Pattern to connect a new API with an existing system seamlessly.


    🔄 Behavioral Patterns

    These patterns focus on communication between objects and define how responsibilities are distributed.
    Examples: Observer Pattern, Strategy Pattern, Command Pattern.

    💡 Use Case: A notification system using the Observer Pattern, where modules subscribe to and react to system events.

    🔄 Behavioral Design Pattern: The Memento Pattern

    In software design, behavioral patterns focus on how objects communicate and interact, managing the flow of information between entities. They simplify complex control flows by defining clear communication and behavior among objects.

    These patterns help manage relationships and communication protocols between objects to promote loose coupling and enhanced flexibility.


    💡 What Are Behavioral Patterns?

    Behavioral patterns are used for:

    • Coordinating interactions between multiple objects.

    • Managing state transitions efficiently.

    • Defining communication logic that keeps systems modular and flexible.

    Common examples include the Observer, Strategy, Command, and Memento patterns.

    In this blog, we’ll explore one of the most practical and easy-to-understand behavioral patterns — the Memento Pattern.


    🧠 The Memento Pattern — Capturing and Restoring State

    🧩 Problem

    How can we provide undo/redo or state restoration functionality without exposing an object’s internal state and breaking encapsulation?

    ✅ Solution

    The Memento Pattern captures an object’s internal state in a separate object (memento) so that the original object can restore its state later — without exposing private details.

    This allows developers to implement powerful features like undo/redo, checkpoint restore, and session rollback cleanly.


    ⚙️ Memento Pattern Structure

    ComponentDescription
    OriginatorThe object whose state needs to be saved and restored.
    MementoCaptures and stores the internal state of the originator.
    CaretakerManages and stores mementos without directly modifying them.

    🧾 Real-World Applications

    • Undo/Redo Functionality: Used in text editors, IDEs, and graphics tools.

    • Game Checkpoints: Saving and loading game progress.

    • System Recovery: Rolling back to previous system states after failures.

    🔍 Example Use Cases

    • 🎮 Games: Saving and reloading checkpoints.

    • 📝 Document Editors: Undo/redo for editing history.


    💻 Sample Java Implementation: Text Editor Using Memento Pattern

    Let’s implement a simple text editor that supports saving and undoing edits using the Memento Pattern.

    package texteditor.MementoPattern; public class TextEditorMain { public static void main(String[] args) { TextEditor editor = new TextEditor(); CareTaker careTaker = new CareTaker(); // History or State Management Work editor.write("Hello World!"); careTaker.saveState(editor); editor.write("Hello Everyone!"); careTaker.saveState(editor); System.out.println("Current Content: " + editor.getContent()); // Undo the last change careTaker.undo(editor); System.out.println("After Undo: " + editor.getContent()); } }

    package texteditor.MementoPattern; /* Text Editor where user can do formatting, editing, and deleting. The editor stores snapshots of its state after each change, allowing the user to revert to a previous state. */ public class TextEditor { private String content; public void write(String text) { this.content = text; } public String getContent() { return content; } // Save the current state of editor public EditorMemento save() { return new EditorMemento(content); } // Restore (Memento -> Update the state of current content) public void restore(EditorMemento memento) { content = memento.getContent(); } }

    package texteditor.MementoPattern; // It stores the internal state of text editor public class EditorMemento { private final String content; public EditorMemento(String content) { this.content = content; } public String getContent() { return content; } }

    package texteditor.MementoPattern; import java.util.Stack; // It manages state history (snapshots) of the Text Editor public class CareTaker { private final Stack<EditorMemento> history = new Stack<>(); public void saveState(TextEditor editor) { history.push(editor.save()); } public void undo(TextEditor editor) { if (!history.empty()) { history.pop(); if (!history.empty()) { editor.restore(history.peek()); } } } }

    🚀 Summary

    The Memento Pattern is one of the most practical behavioral design patterns that enables state management, undo/redo, and checkpoint restoration without breaking encapsulation.

    By separating the concerns of saving, storing, and restoring state, this pattern keeps your code clean, modular, and maintainable.

    If you’ve ever pressed Ctrl + Z — you’ve experienced the Memento Pattern in action!

    👁️ Behavioral Design Pattern: The Observer Pattern

    In many software systems, multiple components need to react when another component’s state changes. For instance, if a weather station updates its temperature reading, multiple display devices or mobile apps may need to show the new value — all without being tightly bound to each other.

    That’s exactly what the Observer Pattern helps us achieve.


    💡 Problem

    There is a need to notify multiple objects about a change in state without tightly coupling them to the object that changes.

    Without a proper design, the notifying component must directly reference every dependent component — making the system rigid, hard to maintain, and difficult to extend.


    ✅ Solution

    The Observer Pattern establishes a one-to-many dependency between objects.
    When one object (the Subject) changes its state, it automatically notifies all its dependents (Observers).

    This is the foundation of many modern systems like event listeners, notification services, and pub-sub messaging frameworks.


    ⚙️ Observer Pattern Structure

    ComponentDescription
    SubjectMaintains a list of observers and notifies them when its state changes.
    ObserverDefines an interface for receiving updates from the subject.
    Concrete SubjectThe real object being observed; notifies observers when its data changes.
    Concrete ObserverImplements the observer interface and reacts to updates.

    🚫 Without the Observer Pattern

    In this approach, the WeatherStation directly holds a reference to a specific DisplayDevice.
    If we add new devices (e.g., mobile, desktop), we’d need to modify the WeatherStation class — creating tight coupling.

    package org.prateek.BehaviouralPatterns.ObserverPattern; class DisplayDevice { public void showTemp(float temp) { System.out.println("Current Temp: " + temp + " C"); } } class WeatherStation { private float temperature; private DisplayDevice displayDevice; // tightly coupled to one device public WeatherStation(DisplayDevice displayDevice) { this.displayDevice = displayDevice; } public void setTemperature(float temp) { this.temperature = temp; notifyDevice(); } public void notifyDevice() { displayDevice.showTemp(temperature); } } public class WithoutObserverPattern { public static void main(String[] args) { DisplayDevice device = new DisplayDevice(); WeatherStation station = new WeatherStation(device); // Tight coupling between WeatherStation and DisplayDevice station.setTemperature(26); station.setTemperature(30); } }

    ✅ With the Observer Pattern

    By introducing Observer and Subject interfaces, we achieve loose coupling — the WeatherStationIn no longer depends on specific devices.
    Any observer that implements the Observer interface can receive updates dynamically.

    package org.prateek.BehaviouralPatterns.ObserverPattern; import java.util.ArrayList; import java.util.List; // Observer Interface interface Observer { void update(float temp); } // Subject Interface interface Subject { void attach(Observer obs); void detach(Observer obs); void notifyObservers(); }

    Concrete Implementations

    // Weather Station (Concrete Subject) class WeatherStationIn implements Subject { private float temperature; private List<Observer> observerList; public WeatherStationIn() { observerList = new ArrayList<>(); } public void setTemperature(float temperature) { this.temperature = temperature; notifyObservers(); } @Override public void attach(Observer obs) { observerList.add(obs); } @Override public void detach(Observer obs) { observerList.remove(obs); } @Override public void notifyObservers() { for (Observer obs : observerList) { obs.update(temperature); // Run-time polymorphism } } } // Concrete Observers class DisplayDeviceIn implements Observer { String name; public DisplayDeviceIn(String deviceName) { this.name = deviceName; } @Override public void update(float temp) { System.out.println("Temp on " + name + " device is " + temp); } } class MobileDevice implements Observer { @Override public void update(float temp) { System.out.println("Temp on mobile is " + temp); } }

    Main Class

    public class ObserverPatternExample { public static void main(String[] args) { // Create a Publisher (Subject) WeatherStationIn weatherStationIn = new WeatherStationIn(); // Create Subscribers (Observers) DisplayDeviceIn device = new DisplayDeviceIn("SamsungLCD"); MobileDevice mobileDevice = new MobileDevice(); // Attach observers weatherStationIn.attach(device); weatherStationIn.attach(mobileDevice); // Notify all observers weatherStationIn.setTemperature(25); // Detach one observer weatherStationIn.detach(mobileDevice); // Notify remaining observers weatherStationIn.setTemperature(26); } }

    🧠 Output

    Temp on SamsungLCD device is 25.0 Temp on mobile is 25.0 Temp on SamsungLCD device is 26.0

    🧩 Benefits of the Observer Pattern

    • 🔗 Loose Coupling: The subject never needs to know details of observers.

    • Easy Scalability: Add or remove observers anytime without changing core logic.

    • ⚙️ Flexibility: Works well in event-driven and real-time systems.


    🌍 Common Use Cases

    • 🖱️ GUI Event Listeners — for button clicks, input fields, etc.

    • 💹 Stock Price Monitoring — notifying all subscribers when prices update.

    • 📰 News or Blog Feeds — pushing new articles to all followers.

    • 📱 Social Media Notifications — followers get updates on new posts.

    • 🧾 Logging Systems — multiple log targets observing the same events.

      🚀 Summary

      The Observer Pattern is a powerful behavioral design pattern that enables clean, event-driven architectures.
      It helps you design loosely coupled, modular, and easily extendable systems — ideal for modern applications involving notifications, events, and data updates.

      Whenever you’re designing a feature that needs to react to state changes automatically, think Observer Pattern! ⚡

      💳 Behavioral Design Pattern: The Strategy Pattern

      When a class contains multiple hardcoded algorithms or behaviors, it often becomes rigid and difficult to maintain. Adding new algorithms usually means modifying existing code — which violates the Open/Closed Principle.

      The Strategy Pattern solves this by allowing you to select an algorithm dynamically at runtime, without altering the core logic of the class.


      💡 Problem

      Hardcoded algorithms within a class lead to:

      • 🔁 Code duplication across similar logic blocks.

      • 🧩 Increased complexity when switching between algorithms.

      • 🚫 Violation of Open/Closed Principle — modifications are needed every time a new algorithm is introduced.

      Let’s look at an example that demonstrates the problem.


      🚫 Without Strategy Pattern

      In the code below, the PaymentService class decides which payment method to use using multiple if-else statements.
      Every time we add a new payment type (e.g., NetBanking, Wallet), we must modify this class.

      package org.prateek.BehaviouralPatterns.StrategyPattern; class PaymentService { public void processPayment(String paymentMethod) { if (paymentMethod.equals("Credit Card")) { System.out.println("Making payment via credit card"); } else if (paymentMethod.equals("Debit Card")) { System.out.println("Making payment via debit card"); } else if (paymentMethod.equals("UPI")) { // huge algorithm System.out.println("Making payment via UPI"); } else { System.out.println("Unsupported Payment method"); } } } public class WithoutStrategyPattern { public static void main(String[] args) { PaymentService paymentService = new PaymentService(); paymentService.processPayment("UPI"); } }

      ⚠️ Problems in the Above Code

      • Adding a new payment method requires changing the PaymentService class.

      • Logic for each payment method is tightly coupled within one class.

      • Difficult to test or extend individual algorithms.


      ✅ With Strategy Pattern

      The Strategy Pattern separates the algorithm (payment type) from the context (payment service).
      It allows different payment methods to be implemented as interchangeable strategies that the client can switch at runtime.


      🧩 Structure

      ComponentDescription
      ContextThe client class that uses a strategy (e.g., PaymentServiceII).
      Strategy InterfaceDefines the operations that all strategies must implement (e.g., PaymentStrategy).
      Concrete StrategiesDifferent algorithm implementations (e.g., CreditCardPayment, DebitCardPayment, UPIPayment).

      💻 Implementation Example

      package org.prateek.BehaviouralPatterns.StrategyPattern; interface PaymentStrategy { void processPayment(); } // Concrete Strategy: Credit Card class CreditCardPayment implements PaymentStrategy { @Override public void processPayment() { System.out.println("Making payment via Credit Card"); } } // Concrete Strategy: Debit Card class DebitCardPayment implements PaymentStrategy { @Override public void processPayment() { System.out.println("Making payment via Debit Card"); } } // Concrete Strategy: UPI class UPIPayment implements PaymentStrategy { @Override public void processPayment() { System.out.println("Making payment via UPI"); } }

      🧠 Context Class

      class PaymentServiceII { private PaymentStrategy strategy; public void setPaymentStrategy(PaymentStrategy strategy) { this.strategy = strategy; } public void pay() { strategy.processPayment(); // Polymorphic Behavior } }

      ▶️ Main Class

      public class StrategyPattern { public static void main(String[] args) { PaymentServiceII paymentService = new PaymentServiceII(); // Switch between strategies dynamically paymentService.setPaymentStrategy(new UPIPayment()); paymentService.pay(); paymentService.setPaymentStrategy(new CreditCardPayment()); paymentService.pay(); paymentService.setPaymentStrategy(new DebitCardPayment()); paymentService.pay(); } }

      🧾 Output

      Making payment via UPI Making payment via Credit Card Making payment via Debit Card

      ⚙️ Benefits of the Strategy Pattern

      • 🧩 Open/Closed Principle: Add new algorithms without changing existing code.

      • 🔁 Flexibility: Algorithms can be swapped dynamically at runtime.

      • 🧼 Clean Code: Removes cluttered conditional statements (if-else, switch-case).

      • 🧠 Testability: Each strategy can be tested independently.


      📍 When to Use the Strategy Pattern

      • When you have multiple interchangeable algorithms.

      • To avoid conditional statements in the client code.

      • When a class exhibits multiple behaviors that vary independently.


      🚀 Summary

      The Strategy Pattern allows a class’s behavior to be selected at runtime.
      It’s perfect when you need to support multiple algorithms or variations of logic without making your code messy or rigid.

      By decoupling algorithm implementations from the client logic, the Strategy Pattern promotes flexibility, extensibility, and clean code architecture.

      Whenever you find yourself writing long if-else blocks — consider refactoring with the Strategy Pattern! ⚡