DEV Community

GabrielFMH
GabrielFMH

Posted on

BEHAVIOR PATTERNS

BEHAVIOR PATTERNS

Gabriel Melendez Huarachi, Piero Paja De la Cruz, Leandro Hurtado Ortiz
April 2024

Abstract
Este artículo examina la importancia y el proceso de aplicación de los patrones de comportamiento en el diseño y la implementación de sistemas de software. Los patrones de comportamiento ofrecen soluciones recurrentes a problemas comunes, permitiendo una organización eficiente, mantenible y escalable del código y las interacciones entre componentes. El artículo detalla los métodos para identificar problemas, seleccionar y diseñar patrones adecuados, así como su implementación, pruebas y ajustes. Además, se discute la importancia de la documentación para la comprensión y colaboración entre desarrolladores. También se mencionan diferentes tipos de patrones de comportamiento para ofrecer una visión general de las soluciones disponibles en este ámbito.

Abstract
This article examines the importance and process of applying behavioral patterns in the design and implementation of software systems. Behavior patterns offer recurring solutions to common problems, enabling efficient, maintainable, and scalable organization of code and interactions between components. The article details methods for identifying problems, selecting and designing suitable patterns, as well as their implementation, testing and tuning. Additionally, the importance of documentation for understanding and collaboration between developers is discussed. Different types of behavior patterns are also mentioned to provide an overview of the solutions available in this area.

Introduction
Behavioral patterns refer to recurring solutions to common problems in the design and implementation of software systems. These patterns offer a way to organize code and interactions between components in a way that is efficient, maintainable, and scalable.
Methods

Problem Identification: The first step is to identify a recurring problem in systems design for which a behavioral pattern could provide a solution.

Selecting the appropriate pattern: Select the behavior pattern that best suits your needs.

Pattern design: Once the pattern is selected, you must design how it will be integrated into your system. This involves identifying the system components that will be involved in the application of the pattern, as well as defining the interfaces and relationships between them.

Implementation: Next, you must implement the behavior pattern in your system code. This involves writing the necessary classes, methods and data structures according to the previously defined design.

Testing: It is important to perform testing to ensure that it works as expected and meets system requirements.

Refinement and adjustment: As the system evolves, you may need to refine or adjust the implementation of the behavior pattern to adapt to new requirements or changes in the system environment.

Documentation: The implementation of the behavior pattern in the system must be documented so that other developers can understand how it works and how it interacts with other components of the system.

Patterns types
Observer (Observador)
Observer is a behavioral design framework that makes it easy to create a subscription system to inform multiple entities about events that have occurred on another object they are observing.
Estructure
Notifier (Subject): Sends events to subscriber objects when changes in their state occur or when they execute certain behaviors. It contains a subscription infrastructure that allows subscribers to join or leave the list as needed.

Subscriber List: Stores the subscriber objects that want to receive notifications from the notifier.

Subscriber (Observer): Defines an interface that must be implemented by each object interested in receiving notifications from the notifier. Typically, this interface includes a method like "update" that will be called by the notifier when a relevant event occurs.

Concrete Subscribers (Concrete Observers): These are the specific implementations of the subscriber interface. Each of these objects performs specific actions in response to notifications issued by the notifier.

Client: Creates separate instances of Notifier and Subscriber objects, and handles the logic to register subscribers to the notifier and receive updates when relevant events occur.

Advantages and disadvantages
Advantages:
Open/closed principle: New subscriber classes can be added without having to modify the notifier code, and vice versa if a notifier interface exists. This promotes the flexibility and extensibility of the system.
Establishment of dynamic relationships: It allows establishing relationships between objects during the execution of the program, which provides greater adaptability and dynamism in the behavior of the system.
Desventajas:
Notifications in random order: Subscribers are notified in an unpredictable or random order, which can be problematic if a specific order is required to process notifications. This can make it difficult to predict and manage the flow of information in the system.

PSEUDOCODE
// La clase notificadora base incluye código de gestión de
// suscripciones y métodos de notificación.
class EventManager is
private field listeners: hash map of event types and listeners

method subscribe(eventType, listener) is
    listeners.add(eventType, listener)

method unsubscribe(eventType, listener) is
    listeners.remove(eventType, listener)

method notify(eventType, data) is
    foreach (listener in listeners.of(eventType)) do
        listener.update(data)
Enter fullscreen mode Exit fullscreen mode

// El notificador concreto contiene lógica de negocio real, de
// interés para algunos suscriptores. Podemos derivar esta clase
// de la notificadora base, pero esto no siempre es posible en
// el mundo real porque puede que la notificadora concreta sea
// ya una subclase. En este caso, puedes modificar la lógica de
// la suscripción con composición, como hicimos aquí.
class Editor is
public field events: EventManager
private field file: File

constructor Editor() is
    events = new EventManager()

// Los métodos de la lógica de negocio pueden notificar los
// cambios a los suscriptores.
method openFile(path) is
    this.file = new File(path)
    events.notify("open", file.name)

method saveFile() is
    file.write()
    events.notify("save", file.name)

// ...
Enter fullscreen mode Exit fullscreen mode

// Aquí está la interfaz suscriptora. Si tu lenguaje de
// programación soporta tipos funcionales, puedes sustituir toda
// la jerarquía suscriptora por un grupo de funciones.

interface EventListener is
method update(filename)

// Los suscriptores concretos reaccionan a las actualizaciones
// emitidas por el notificador al que están unidos.
class LoggingListener implements EventListener is
private field log: File
private field message: string

constructor LoggingListener(log_filename, message) is
    this.log = new File(log_filename)
    this.message = message

method update(filename) is
    log.write(replace('%s',filename,message))
Enter fullscreen mode Exit fullscreen mode

class EmailAlertsListener implements EventListener is
private field email: string
private field message: string

constructor EmailAlertsListener(email, message) is
    this.email = email
    this.message = message

method update(filename) is
    system.email(email, replace('%s',filename,message))
Enter fullscreen mode Exit fullscreen mode

// Una aplicación puede configurar notificadores y suscriptores
// durante el tiempo de ejecución.
class Application is
method config() is
editor = new Editor()

    logger = new LoggingListener(
        "/path/to/log.txt",
        "Someone has opened the file: %s")
    editor.events.subscribe("open", logger)

    emailAlerts = new EmailAlertsListener(
        "admin@example.com",
        "Someone has changed the file: %s")
    editor.events.subscribe("save", emailAlerts)
Enter fullscreen mode Exit fullscreen mode

Strategy (Estrategia)
Strategy is a behavioral design pattern that allows you to define a family of algorithms, put each one in a separate class, and make their objects interchangeable.
STRUCTURE
Contexto (Context): It's the class that interacts with the client and maintains a reference to a specific strategy. The context defines a common interface that is used by the client to invoke the algorithm.

Estrategia (Strategy): It's an interface (or an abstract class) that defines the structure for all concrete strategies. This interface generally contains a method that encapsulates the algorithm to be executed.

Estrategias concretas (Concrete Strategies): These are specific implementations of the Strategy interface. Each concrete strategy provides a unique implementation of the algorithm defined in the Strategy interface.

Cliente (Client):Creates a specific strategy object and passes it to the context class. The context class exposes a set modifier that allows clients to substitute the strategy associated with the context during runtime.

ADVANTAGES AND DISADVANTAGES
Advantages:
Flexibility and extensibility: The Strategy pattern allows adding, modifying, or removing strategies without affecting the client. This facilitates the adaptation of the system to new requirements or changes in algorithms.
Code reusability: Strategies can be shared and reused in different contexts, promoting modularity and avoiding code duplication.
Loose coupling: The Strategy pattern reduces coupling between the context and the strategies. The context only knows the interface of the strategy, without being directly coupled to concrete implementations.
Disadvantages:
Increased complexity: The Strategy pattern introduces an additional level of abstraction, which can increase the complexity of the code compared to a more direct solution.
Class and interface overhead: When using the Strategy pattern, several additional classes and interfaces can be created, resulting in more code and files.

PSEUDOCODE
// The strategy interface declares operations common to all supported versions of some algorithm. The context uses this interface to invoke the algorithm defined by the concrete strategies.
interface Strategy is
method execute(a, b)

// Concrete strategies implement the algorithm while following the base strategy interface. The interface makes them interchangeable in the context.
class ConcreteStrategyAdd implements Strategy is
method execute(a, b) is
return a + b

class ConcreteStrategySubtract implements Strategy is
method execute(a, b) is
return a - b

class ConcreteStrategyMultiply implements Strategy is
method execute(a, b) is
return a * b

// The context defines the interface of interest for clients.
class Context is
// The context maintains a reference to one of the strategy objects. The context doesn't know the concrete class of a strategy. It should work with all strategies via the strategy interface.
private strategy: Strategy

// Normally, the context accepts a strategy through the constructor and also provides a setter (modifier) to be able to change the strategy during runtime.
method setStrategy(Strategy strategy) is
    this.strategy = strategy

// The context delegates part of the work to the strategy object instead of implementing multiple versions of the algorithm on its own.
method executeStrategy(int a, int b) is
    return strategy.execute(a, b)
Enter fullscreen mode Exit fullscreen mode

// The client code chooses a concrete strategy and passes it to the context. The client must know the differences between strategies to choose the best option.
class ExampleApplication is
method main() is
Create context object.

    Read first number.
    Read last number.
    Read the desired action from user input.

    if (action == addition) then
        context.setStrategy(new ConcreteStrategyAdd())

    if (action == subtraction) then
        context.setStrategy(new ConcreteStrategySubtract())

    if (action == multiplication) then
        context.setStrategy(new ConcreteStrategyMultiply())

    result = context.executeStrategy(First number, Second number)

    Print result.
Enter fullscreen mode Exit fullscreen mode

State (Estado)
State is a behavioral design that enables an object to modify its behavior in response to changes in its internal state, giving the impression that the object is changing its class type.

Structure
Context: Stores a reference to one of the specific state objects and delegates all state-specific work to this object. It communicates with the state object through the state interface and provides a method to change the current state by assigning a new state object to it.

State Interface: Declares state-specific methods that must be implemented by all specific states. Ensures that each state has relevant methods that can be invoked by the context.

Concrete States: Provide implementations for the state-specific methods defined in the State interface. They can include intermediate abstract classes to encapsulate common behaviors and avoid code duplication.

State Transition: Both context and concrete states can dynamically change the state of the context, replacing the currently bound state object. This allows the context to modify its behavior as needed, using state transitions.

Advantages and disadvantages
Advantages:
Single Responsibility Principle: Improves code structure by grouping functions related to specific states into individual classes.
Open/closed principle: Facilitates system expansion by allowing the addition of new states without altering existing state classes or the context class.
Context code simplification: Increases code clarity and maintainability by eliminating long conditional blocks from state machines.
Disadvantages:
Application Considerations: Provides a warning about possible excessive complexity when applying the pattern in situations where the state machine is simple or changes rarely.

PSEUDOCODE:

using System;

namespace RefactoringGuru.DesignPatterns.State.Conceptual
{
// The Context defines the interface of interest to clients. It also
// maintains a reference to an instance of a State subclass, which
// represents the current state of the Context.
class Context
{
// A reference to the current state of the Context.
private State _state = null;

    public Context(State state)
    {
        this.TransitionTo(state);
    }

    // The Context allows changing the State object at runtime.
    public void TransitionTo(State state)
    {
        Console.WriteLine($"Context: Transition to {state.GetType().Name}.");
        this._state = state;
        this._state.SetContext(this);
    }

    // The Context delegates part of its behavior to the current State
    // object.
    public void Request1()
    {
        this._state.Handle1();
    }

    public void Request2()
    {
        this._state.Handle2();
    }
}

// The base State class declares methods that all Concrete State should
// implement and also provides a backreference to the Context object,
// associated with the State. This backreference can be used by States to
// transition the Context to another State.
abstract class State
{
    protected Context _context;

    public void SetContext(Context context)
    {
        this._context = context;
    }

    public abstract void Handle1();

    public abstract void Handle2();
}

// Concrete States implement various behaviors, associated with a state of
// the Context.
class ConcreteStateA : State
{
    public override void Handle1()
    {
        Console.WriteLine("ConcreteStateA handles request1.");
        Console.WriteLine("ConcreteStateA wants to change the state of the context.");
        this._context.TransitionTo(new ConcreteStateB());
    }

    public override void Handle2()
    {
        Console.WriteLine("ConcreteStateA handles request2.");
    }
}

class ConcreteStateB : State
{
    public override void Handle1()
    {
        Console.Write("ConcreteStateB handles request1.");
    }

    public override void Handle2()
    {
        Console.WriteLine("ConcreteStateB handles request2.");
        Console.WriteLine("ConcreteStateB wants to change the state of the context.");
        this._context.TransitionTo(new ConcreteStateA());
    }
}

class Program
{
    static void Main(string[] args)
    {
        // The client code.
        var context = new Context(new ConcreteStateA());
        context.Request1();
        context.Request2();
    }
}
Enter fullscreen mode Exit fullscreen mode

}

Command (Comando)
Command is a behavioral design pattern that encapsulates a request as an object, thus allowing parameterization of methods with various requests. This encapsulation facilitates postponing or queuing the execution of the request, as well as supporting operations that are not possible to perform otherwise.

Structure
The Issuer (or Invoker) class has the responsibility of initiating the requests. To do this, you must include a field that stores a reference to a command object. Instead of sending the request directly to the receiver, the broadcaster activates this command. It is important to note that the broadcaster does not have the responsibility of creating the command object, it usually receives a pre-created one from the client through the constructor.

The Command interface defines a single method to carry out the execution of the command.

The Concrete Commands address various types of requests. It is expected that a particular command will not directly perform the task, but rather pass the call to one of the business logic objects. However, to simplify the code, it is possible to merge these classes.

The parameters required to execute a method on a receiver object can be defined as fields in the particular command. By making command objects immutable, initialization of these fields is allowed only through the constructor.

The Receiver class houses the business logic and can be represented by almost any object. For the most part, the commands mainly handle the details related to transmitting the request to the receiver, leaving the latter to perform the actual actions.
The Client is responsible for creating and configuring the particular command objects, where it must provide all necessary parameters, including a receiver instance, during command initialization. Subsequently, the generated command can be linked to one or more issuers for execution.

Advantages and disadvantages
Advantages:
Single Responsibility Principle: Facilitates decoupling between classes that invoke operations and those that execute them, thus improving the modularity and readability of the code.
Open/closed principle: Allows the introduction of new commands in the application without modifying the existing code, which facilitates extensibility and maintenance of the system.
Implementation of undo/redo: Enables the implementation of undo and redo functionalities, which increases the flexibility and usability of the application.
Deferred execution of operations: Allows you to defer the execution of certain operations, which can be useful to optimize performance or to handle operations at a more appropriate time.
Simple Command Assembly: Facilitates the composition of simple commands to create a complex one, providing greater flexibility and customizability in application design.
Disadvantages:
Introduction of a new layer: Adds complexity to the code by introducing a new layer between senders and receivers, which can make the system difficult to understand and maintain if not managed properly.

PSEUDOCODE

using System;

namespace RefactoringGuru.DesignPatterns.Command.Conceptual
{
// The Command interface declares a method for executing a command.
public interface ICommand
{
void Execute();
}

// Some commands can implement simple operations on their own.
class SimpleCommand : ICommand
{
    private string _payload = string.Empty;

    public SimpleCommand(string payload)
    {
        this._payload = payload;
    }

    public void Execute()
    {
        Console.WriteLine($"SimpleCommand: See, I can do simple things like printing ({this._payload})");
    }
}

// However, some commands can delegate more complex operations to other
// objects, called "receivers."
class ComplexCommand : ICommand
{
    private Receiver _receiver;

    // Context data, required for launching the receiver's methods.
    private string _a;

    private string _b;

    // Complex commands can accept one or several receiver objects along
    // with any context data via the constructor.
    public ComplexCommand(Receiver receiver, string a, string b)
    {
        this._receiver = receiver;
        this._a = a;
        this._b = b;
    }

    // Commands can delegate to any methods of a receiver.
    public void Execute()
    {
        Console.WriteLine("ComplexCommand: Complex stuff should be done by a receiver object.");
        this._receiver.DoSomething(this._a);
        this._receiver.DoSomethingElse(this._b);
    }
}

// The Receiver classes contain some important business logic. They know how
// to perform all kinds of operations, associated with carrying out a
// request. In fact, any class may serve as a Receiver.
class Receiver
{
    public void DoSomething(string a)
    {
        Console.WriteLine($"Receiver: Working on ({a}.)");
    }

    public void DoSomethingElse(string b)
    {
        Console.WriteLine($"Receiver: Also working on ({b}.)");
    }
}

// The Invoker is associated with one or several commands. It sends a
// request to the command.
class Invoker
{
    private ICommand _onStart;

    private ICommand _onFinish;

    // Initialize commands.
    public void SetOnStart(ICommand command)
    {
        this._onStart = command;
    }

    public void SetOnFinish(ICommand command)
    {
        this._onFinish = command;
    }

    // The Invoker does not depend on concrete command or receiver classes.
    // The Invoker passes a request to a receiver indirectly, by executing a
    // command.
    public void DoSomethingImportant()
    {
        Console.WriteLine("Invoker: Does anybody want something done before I begin?");
        if (this._onStart is ICommand)
        {
            this._onStart.Execute();
        }

        Console.WriteLine("Invoker: ...doing something really important...");

        Console.WriteLine("Invoker: Does anybody want something done after I finish?");
        if (this._onFinish is ICommand)
        {
            this._onFinish.Execute();
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        // The client code can parameterize an invoker with any commands.
        Invoker invoker = new Invoker();
        invoker.SetOnStart(new SimpleCommand("Say Hi!"));
        Receiver receiver = new Receiver();
        invoker.SetOnFinish(new ComplexCommand(receiver, "Send email", "Save report"));

        invoker.DoSomethingImportant();
    }
}
Enter fullscreen mode Exit fullscreen mode

}

Iterator (Iterador)

Iterator is a behavioral design that enables the iteration of elements within a collection without revealing its internal structure, such as a list, a stack, a tree, among others.

Structure

The Iterator interface defines the essential methods for traversing a collection, including fetching the next element, retrieving the current position, restarting the iteration, and so on.

Concrete Iterators develop particular algorithms for the iteration of a collection, with the iterator object being responsible for managing its own progress along the path. This independence between iterators makes it possible for multiple instances to traverse the same collection simultaneously without interfering with each other.

The Collection interface specifies one or more methods to obtain iterators that are compatible with the collection. It is important to note that the return type of these methods must be declared as the iterator interface, thus allowing concrete collections to return different types of iterators as needed.

Concrete Collections provide new instances of a specific concrete iterator class each time the client requests them. You may be wondering: where is the rest of the collection code located? Don't worry, it's usually contained in the same class. However, these details are not essential to the pattern itself, so they are often omitted from the explanation.

The Client must interact with collections and iterators through its interfaces to avoid coupling to specific classes, enabling the use of multiple collections and iterators with the same client code. Typically, clients obtain iterators from collections rather than creating them directly, although they may sometimes create their own, especially when defining a specialized iterator.

Advantages and disadvantages

Advantages:
Single Responsibility Principle: Improves the clarity and organization of client code and collections by moving extensive traversal algorithms into separate classes, making them easier to maintain and reuse.
Open/Closed Principle: Makes it easy to add new types of collections and iterators to existing code without requiring modifications, improving system flexibility and extensibility.
It allows parallel iteration of the same collection by storing the iteration state in each iterator object, providing a more efficient and versatile approach to data processing.
The ability to delay and continue iteration as needed, thanks to the iteration state stored in each iterator object, offers greater flexibility in data manipulation.

Disadvantages:

Applying the pattern may be unnecessary if the application primarily handles simple collections, which could result in complexity overhead.
In some circumstances, using an iterator may be less efficient than directly traversing elements in certain specialized collections, which could impact performance in speed-sensitive applications.

PSEUDOCODE

using System;
using System.Collections;
using System.Collections.Generic;

namespace RefactoringGuru.DesignPatterns.Iterator.Conceptual
{
abstract class Iterator : IEnumerator
{
object IEnumerator.Current => Current();

    // Returns the key of the current element
    public abstract int Key();

    // Returns the current element
    public abstract object Current();

    // Move forward to next element
    public abstract bool MoveNext();

    // Rewinds the Iterator to the first element
    public abstract void Reset();
}

abstract class IteratorAggregate : IEnumerable
{
    // Returns an Iterator or another IteratorAggregate for the implementing
    // object.
    public abstract IEnumerator GetEnumerator();
}

// Concrete Iterators implement various traversal algorithms. These classes
// store the current traversal position at all times.
class AlphabeticalOrderIterator : Iterator
{
    private WordsCollection _collection;

    // Stores the current traversal position. An iterator may have a lot of
    // other fields for storing iteration state, especially when it is
    // supposed to work with a particular kind of collection.
    private int _position = -1;

    private bool _reverse = false;

    public AlphabeticalOrderIterator(WordsCollection collection, bool reverse = false)
    {
        this._collection = collection;
        this._reverse = reverse;

        if (reverse)
        {
            this._position = collection.getItems().Count;
        }
    }

    public override object Current()
    {
        return this._collection.getItems()[_position];
    }

    public override int Key()
    {
        return this._position;
    }

    public override bool MoveNext()
    {
        int updatedPosition = this._position + (this._reverse ? -1 : 1);

        if (updatedPosition >= 0 && updatedPosition < this._collection.getItems().Count)
        {
            this._position = updatedPosition;
            return true;
        }
        else
        {
            return false;
        }
    }

    public override void Reset()
    {
        this._position = this._reverse ? this._collection.getItems().Count - 1 : 0;
    }
}

// Concrete Collections provide one or several methods for retrieving fresh
// iterator instances, compatible with the collection class.
class WordsCollection : IteratorAggregate
{
    List<string> _collection = new List<string>();

    bool _direction = false;

    public void ReverseDirection()
    {
        _direction = !_direction;
    }

    public List<string> getItems()
    {
        return _collection;
    }

    public void AddItem(string item)
    {
        this._collection.Add(item);
    }

    public override IEnumerator GetEnumerator()
    {
        return new AlphabeticalOrderIterator(this, _direction);
    }
}

class Program
{
    static void Main(string[] args)
    {
        // The client code may or may not know about the Concrete Iterator
        // or Collection classes, depending on the level of indirection you
        // want to keep in your program.
        var collection = new WordsCollection();
        collection.AddItem("First");
        collection.AddItem("Second");
        collection.AddItem("Third");

        Console.WriteLine("Straight traversal:");

        foreach (var element in collection)
        {
            Console.WriteLine(element);
        }

        Console.WriteLine("\nReverse traversal:");

        collection.ReverseDirection();

        foreach (var element in collection)
        {
            Console.WriteLine(element);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

}

Chain of Responsibility (Cadena de responsabilidad)
Chain of Responsibility is a behavioral design pattern that allows you to pass requests along a chain of handlers. Upon receiving a request, each handler decides whether to process it or pass it to the next handler in the chain.
STRUCTURE
Manejador(Handler): It's an interface or abstract class that defines a method for handling requests. It may also contain a reference to the next object in the chain (known as "successor").

Manejador Base (Base Handler): It's a base class that implements the Handler interface and provides common basic implementation for concrete handlers. It may contain the logic to pass the request to the next handler in the chain, if it exists.

Manejador Concreto (Concrete Handler): These are concrete implementations of the Handler interface. Each handler is responsible for handling a specific request. If it can handle the request, it does so; otherwise, it passes the request to the next handler in the chain.

Client (Cliente): It's the object that initiates the request and sends it to the first handler in the chain.

ADVANTAGES AND DISADVANTAGES
Advantages:
Decoupling: The Chain of Responsibility pattern promotes low coupling between the client and the objects handling requests. The client only needs to know about the first object in the chain and is not directly coupled to specific handlers. This facilitates the modification, addition, or removal of new handlers without affecting the client or other components of the system.
Flexibility and extensibility: The chain of responsibility allows for easily adding or removing new handlers at any time without modifying the client or other existing handlers. This provides flexibility to adjust request handling logic and extend system behavior.
Separation of responsibilities: Each handler is responsible for handling a specific request. This allows for dividing the handling logic into smaller parts and focusing on a specific task in each handler. Additionally, the client and handlers do not need to know all the details of each other's components.
Disadvantages:
Handling guarantee: There is no guarantee that a request will be handled in the chain. If no handler can handle the request, it might go unnoticed or not be handled properly. It's important to carefully design the chain and its handlers to ensure adequate coverage of requests.
Performance: Using a chain of responsibility can introduce performance overhead due to the sequential passing of the request through different handlers. If the chain is very long or handlers perform costly operations, it could negatively affect the system's performance.
PSEUDOCODE
// The handler interface declares a method to execute a request.
interface ComponentWithContextualHelp is
method showHelp()

// The base class for simple components.
abstract class Component implements ComponentWithContextualHelp is
field tooltipText: string

// The component's container acts as the next link in the chain of handlers.
protected field container: Container

// The component shows a tooltip if it has assigned help text. Otherwise, it forwards the call to the container, if it exists.
method showHelp() is
    if (tooltipText != null)
        // Show the tooltip.
    else
        container.showHelp()
Enter fullscreen mode Exit fullscreen mode

// Containers can contain simple components and other containers as children. Chain relationships are established here. The class inherits the showHelp behavior from its parent.
abstract class Container extends Component is
protected field children: array of Component

method add(child) is
    children.add(child)
    child.container = this
Enter fullscreen mode Exit fullscreen mode

// Primitive components might be fine with the default help implementation...
class Button extends Component is
// ...

// But complex components can override the default implementation. If providing help text in a new way isn't possible, the component can always invoke the base implementation (see the Component class).
class Panel extends Container is
field modalHelpText: string

method showHelp() is
    if (modalHelpText != null)
        // Show a modal window with the help text.
    else
        super.showHelp()
Enter fullscreen mode Exit fullscreen mode

// ...same as above...
class Dialog extends Container is
field wikiPageURL: string

method showHelp() is
    if (wikiPageURL != null)
        // Open the wiki help page.
    else
        super.showHelp()
Enter fullscreen mode Exit fullscreen mode

// Client code.
class Application is
// Each application configures the chain differently.
method createUI() is
dialog = new Dialog("Budget Reports")
dialog.wikiPageURL = "http://..."
panel = new Panel(0, 0, 400, 800)
panel.modalHelpText = "This panel does..."
ok = new Button(250, 760, 50, 20, "OK")
ok.tooltipText = "This is an OK button that..."
cancel = new Button(320, 760, 50, 20, "Cancel")
// ...
panel.add(ok)
panel.add(cancel)
dialog.add(panel)

// Imagine what happens here.
method onF1KeyPress() is
    component = this.getComponentAtMouseCoords()
    component.showHelp()
Enter fullscreen mode Exit fullscreen mode

Template Method (Método plantilla)
Template Method is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but allows subclasses to override steps of the algorithm without changing its structure.
STRUCTURE
Clase Abstracta (Abstract Class):It's an abstract class that defines the skeleton of an algorithm in a method called "template method." This method contains common steps or stages of the algorithm and may include concrete or abstract methods that will be implemented by subclasses. The abstract class may also contain concrete methods that provide a default implementation for some steps of the algorithm.

Clase Concreta(Concrete Class): These are concrete classes that inherit from the abstract class and provide concrete implementations for the abstract methods defined in the abstract class. Each concrete class can provide a specific implementation for the algorithm steps defined in the abstract class.

ADVANTAGES AND DISADVANTAGES
Advantages:
Extensibility: Subclasses can extend or modify the base algorithm by overriding the abstract or hook methods defined in the abstract class. This provides flexibility to adapt the algorithm's behavior to different situations without affecting its overall structure.
Centralized control of the algorithm: The template method provides a single, controlled entry point for the algorithm. This allows the abstract class to define the sequence and steps of the algorithm, ensuring that a predetermined flow is followed. This can be useful for ensuring consistency and coherence in the execution of the algorithm across different subclasses.
Disadvantages:
Rigidity in the algorithm structure: The Template Method pattern defines a fixed structure for the algorithm in the abstract class. This can limit flexibility and make it difficult to adapt the algorithm to very different situations. If the algorithm steps need to change drastically in different contexts, the Template Method pattern may not be suitable.
Possible violation of the dependency inversion principle: If subclasses depend too much on the abstract class and a good separation of responsibilities is not defined, there may be a violation of the dependency inversion principle. This can make unit testing and individual modification of subclasses difficult.
PSEUDOCODE
// The abstract class defines a template method that contains a skeleton of some algorithm composed of calls, typically to abstract primitive operations. Concrete subclasses implement these operations but leave the template method itself intact.
class GameAI is
// The template method defines the skeleton of an algorithm.
method turn() is
collectResources()
buildStructures()
buildUnits()
attack()

// Some steps can be implemented directly in a base class.
method collectResources() is
    foreach (s in this.builtStructures) do
        s.collect()

// And some of them can be defined as abstract.
abstract method buildStructures()
abstract method buildUnits()

// A class can have multiple template methods.
method attack() is
    enemy = closestEnemy()
    if (enemy == null)
        sendScouts(map.center)
    else
        sendWarriors(enemy.position)

abstract method sendScouts(position)
abstract method sendWarriors(position)
Enter fullscreen mode Exit fullscreen mode

// Concrete classes must implement all abstract operations of the base class, but they shouldn't override the template method itself.
class OrcsAI extends GameAI is
method buildStructures() is
if (there are some resources) then
// Build farms, then barracks, and then fortress.

method buildUnits() is
    if (there are plenty of resources) then
        if (there are no scouts)
            // Create peon and add it to scout group.
        else
            // Create soldier and add it to warrior group.

// ...

method sendScouts(position) is
    if (scouts.length > 0) then
        // Send scouts to position.

method sendWarriors(position) is
    if (warriors.length > 5) then
        // Send warriors to position.
Enter fullscreen mode Exit fullscreen mode

// Subclasses can also override some operations with a default implementation.
class MonstersAI extends GameAI is
method collectResources() is
// Monsters don't collect resources.

method buildStructures() is
    // Monsters don't build structures.

method buildUnits() is
    // Monsters don't build units.
Enter fullscreen mode Exit fullscreen mode

Interpreter
Interpreter is a design pattern used to define a grammar for interpreting a language. It allows you to define how to interpret an expression, providing a way to evaluate statements in a specific language.
Structure
AbstractExpression: Declares an interpreter abstract operation that is common to all nodes of the abstract syntax tree.

TerminalExpression: An instance is required for each occurrence in a statement. implements an Interpreter operation associated with each terminal symbol.

NonTerminalExpression: For each rule, a class type is necessary.

Client: Builds the syntactic tree of ExpressionsNoTerminales, and instances of the ExpressionTerminal class.

Context: Contains global information for the interpreter.

Pros/Cons:
Advantages:

It allows the interpretation of complex expressions by defining a flexible and extensible grammar.

By separating the grammar and interpretation of expressions into independent classes, the pattern makes it easy to create a modular and maintainable design.

Allows you to reuse existing expression classes to build new compound expressions.

It is relatively easy to add new expression classes or modify existing ones to accommodate new system requirements.

Disadvantages:

Complexity of initial design: Implementation of the Interpreter pattern may require complex initial design, especially for very elaborate grammars or for systems that need to interpret multiple types of expressions.

Performance overhead: Interpreting expressions can introduce a performance overhead compared to more direct approaches, such as compiling expressions into machine code.

Difficulty with changing expressions: If the language's grammar or interpretation rules change frequently, it can be difficult to maintain and update existing expression classes. This can result in a lack of flexibility and difficulties adapting to changes in the system.

Potential for infinite recursion: Recursion in expression interpretation can lead to infinite recursion situations if not handled correctly. This can cause runtime errors or excessive system resource consumption.

Code:
import
java.util.*;

interface Expression {

publicvoid interprete(Stack s);

}

class ExpresionTerminal_Numero implements Expresion {

private int numero;

public ExpresionTerminal_Numero(int numero){
this.numero = numero;
}

publicvoid interprete(Stack s){
s.push(numero);
}

}

class ExpresionTerminal_Mas implements Expresion {

publicvoid interprete(Stack s){
s.push( s.pop() + s.pop());
}

}

class ExpresionTerminal_Menos implements Expresion {

publicvoid interpret(Stack s){
int tmp = s.pop();
s.push( s.pop() - tmp );
}
}

class Parser {

private ArrayList parseTree = new ArrayList();// Just a ExpresiónNoTerminal

Parser(String s) {
for (String token : s.split(" ")) {
if (token.equals("+"))
parseTree.add( new ExpresionTerminal_Mas() );
else if (token.equals("-"))
parseTree.add( new ExpresionTerminal_Menos() ); // ...
else parseTree.add( new ExpresionTerminal_Numero(Integer.valueOf(token)));
}
}

public int evalua() {
Stack contexto = new Stack();
for(Expression e : parseTree) e.interprete(contexto);
return contexto.pop();
}

}

class EjemploInterprete {

public static void main(String[] args) {
System.out.println("'42 2 1 - +' equals " + new Parser("42 2 1 - +").evalua());
}

}

Visitor
Visitor is a behavioral design pattern that allows you to separate algorithms from the objects they operate on.
Structure:

Visitor Interface: Declares a group of visitor methods that can take particular elements of an object structure as arguments. These methods can have the same names if the program is written in a language that supports overloading, but the types of their parameters must be different.

Concrete Visitor (ConcreteVisitors): Implements several versions of the same behaviors, customized for different concrete element classes.

Interface Element: declares a method to “accept” visitors. This method must have a parameter declared with the type of the visitor interface.

Concrete Element (ConcreteElement): must implement the accept method. The purpose of this method is to redirect the call to the appropriate visitor method corresponding to the current item class. Note that even if an element base class implements this method, all subclasses must override this method in their own classes and invoke the appropriate method on the visitor object.

Client: Typically represents a collection or some other complex object (for example, a Composite tree). Often, clients are not aware of all concrete item classes because they work with objects in that collection through an abstract interface.

Pros/Cons:

Advantages:

It allows algorithms to be separated from the data structure. This means that you can define new operations without modifying the classes of the elements on which they operate.

It is easy to add new operations to the system by introducing new classes of visitors. This makes the system more flexible and extensible without modifying the existing class hierarchy structure.

Facilitates the implementation of complex operations

Code tends to be easier to maintain and evolve over time, since changes to operations can be made without affecting the structure of the class hierarchy.

Disadvantages:
You must update all visitors each time a class is added or removed from the element hierarchy.

Visitors may lack necessary access to the private fields and methods of the elements they are supposed to work with.

Code:
// The interface element declares an accept method that
// takes the base visitor interface as an argument.
interface Shape is
method move(x, y)
method draw()
method accept(v: Visitor)

// Each concrete element class must implement the method
// accept in such a way that it invokes the visitor's method
// which corresponds to the class of the element.
class Dot implements Shape is
// ...

// Notice that we invoke `visitDot`, which matches the
// name of the current class. In this way, we let you know
// to the visitor the class of the element with which he works.
method accept(v: Visitor) is
    v.visitDot(this)
Enter fullscreen mode Exit fullscreen mode

class Circle implements Shape is
// ...
method accept(v: Visitor) is
v.visitCircle(this)

class Rectangle implements Shape is
// ...
method accept(v: Visitor) is
v.visitRectangle(this)

class CompoundShape implements Shape is
// ...
method accept(v: Visitor) is
v.visitCompoundShape(this)

// The Visitor interface declares a group of visit methods that
// correspond to element classes. The signature of a method
// visiting allows the visitor to identify the exact class
// of the element it deals with.
interface Visitor is
method visitDot(d: Dot)
method visitCircle(c: Circle)
method visitRectangle(r: Rectangle)
method visitCompoundShape(cs: CompoundShape)

// Specific visitors implement multiple versions of the
// same algorithm, which can work with all classes
// concrete elements.
//
// You can enjoy the greatest advantage of the Visitor pattern by
// you use with a complex structure of objects, such as a
// Composite tree. In this case, it may be helpful to store
// some intermediate state of the algorithm while you execute the
// visitor methods on various objects in the structure.
class XMLExportVisitor implements Visitor is
method visitDot(d: Dot) is
// Exports the dot ID and centers the
// coordinates.

method visitCircle(c: Circle) is
    // Export the circle ID and center the coordinates and
    // the radio.


method visitRectangle(r: Rectangle) is
    // Exports the ID of the rectangle, the coordinates of
    // top left, width and height.


method visitCompoundShape(cs: CompoundShape) is
    // Exports the ID of the shape, as well as the list of the
    // ID of your children.
Enter fullscreen mode Exit fullscreen mode

// Client code can execute visitor operations
// on any group of elements without knowing their classes
// concrete. The accept operation directs a call to the
// proper operation of the visitor object.
class Application is
field allShapes: array of Shapes

method export() is
    exportVisitor = new XMLExportVisitor()


    foreach (shape in allShapes) do
        shape.accept(exportVisitor)
Enter fullscreen mode Exit fullscreen mode

Memento (Memory)
Memento is a behavioral design pattern that allows you to save and restore the previous state of an object without revealing its implementation details.

Structure:

Originator class: can produce snapshots of its own state, as well as restore its state from snapshots when needed.

Memento: is a value object that acts as a snapshot of the originator's state. It is common practice to make the memento immutable and pass data to it only once, through the constructor.

The Caretaker: knows not only “when” and “why” to capture the originator state, but also when the state should be restored.

A caregiver can track the history of the originator by storing a stack of mementos. When the originator needs to go back in history, the caretaker will pop the top item in the stack and pass it to the originator's restore method.

In this implementation, the memento class is nested within the originator. This allows the originator to access the fields and methods of the memento class, even if they are declared private. On the other hand, the caregiver has very limited access to the fields and methods of the memento class, allowing it to store mementos on a stack but not alter its state.

Pros/Cons:

Advantages:

You can produce snapshots of the object's state without violating its encapsulation.

You can simplify the originator code by allowing the caregiver to maintain the history of the originator status.

Disadvantages:

The app can consume a lot of RAM if clients create moments too often.

Caregivers must track the life cycle of the originator in order to destroy obsolete items.

Most dynamic programming languages, such as PHP, Python, and JavaScript, cannot guarantee that the state within the memento remains intact.

Code:

// The originator contains important information that can
// change over time. It also defines a method to
// save its state within a memento, and another method to
// restore the state from it.
class Editor is
private field text, curX, curY, selectionWidth

method setText(text) is
    this.text = text


method setCursor(x, y) is
    this.curX = x
    this.curY = y


method setSelectionWidth(width) is
    this.selectionWidth = width


   // Save the current state within a memento.
method createSnapshot():Snapshot is
    // The memento is an immutable object; that is the reason
    // by which the originator passes its state to the
    // parameters of its constructor.
    return new Snapshot(this, text, curX, curY, selectionWidth)
Enter fullscreen mode Exit fullscreen mode

// The memento class stores the passed state of the editor.
class Snapshot is
private field editor: Editor
private field text, curX, curY, selectionWidth

constructor Snapshot(editor, text, curX, curY, selectionWidth) is
    this.editor = editor
    this.text = text
    this.curX = x
    this.curY = y
    this.selectionWidth = selectionWidth


// At some point, a previous state of the device can be restored.
// editor using a memento object.
method restore() is
    editor.setText(text)
    editor.setCursor(curX, curY)
    editor.setSelectionWidth(selectionWidth)
Enter fullscreen mode Exit fullscreen mode

// A command object can act as a caretaker. In this
// case, the command gets a memento just before changing the
// originator state. When undo is requested, restore
// the state of the originator as of the moment.
class Command is
private field backup: Snapshot

method makeBackup() is
    backup = editor.createSnapshot()


method undo() is
    if (backup != null)
        backup.restore()
// ...
Enter fullscreen mode Exit fullscreen mode

Conclusiones
Observer pattern offers a flexible and extensible solution for establishing a subscription system that informs multiple entities about events in another object. Although it promotes system adaptability by allowing the addition of new subscribers without modifying the notifier code, it can present challenges in managing the order of notifications. Despite this, it remains a valuable tool for designing systems with efficient and dynamic communication between components.

The Strategy pattern allows for dynamic selection and interchangeability of algorithms or behaviors at runtime, enabling greater flexibility and maintainability in software systems.

State pattern allows an object to change its behavior based on its internal state, providing flexibility and modularity to the system. Its structure, which includes a Context, a State Interface and Specific States, simplifies management and transition between states.

Command pattern provides an efficient structure for encapsulating requests as objects, allowing parameterization of methods and deferred execution of operations. Its advantages include decoupling, extensibility, and implementation of functionality such as undo/redo. However, its introduction can add complexity to the code by adding a new layer between senders and receivers. Overall, the Command pattern remains a valuable tool for designing flexible and modular systems, as long as its implementation is carefully managed.

Iterator pattern makes it easy to iterate elements in a collection without exposing its internal structure. Through specific interfaces and classes, it allows flexibility in the collection journey, promoting better code organization and system extensibility. Although it can introduce unnecessary complexity in situations with simple collections and, in some cases, be less efficient than direct element traversal, it is still a valuable tool for designing systems that are flexible and efficient in manipulating data.

The Chain of Responsibility pattern provides a way to decouple senders and receivers of requests, forming a chain of objects that can process the request sequentially or in parallel. This pattern promotes loose coupling and allows for the handling of requests in a flexible and extensible manner.

The Template Method pattern defines the skeleton of an algorithm in a base class, while allowing subclasses to provide specific implementations for certain steps. This pattern promotes code reuse and provides a structured approach to designing algorithms with varying implementations.

Interpreter provides a way to define grammars for interpreting languages. It is useful when you need to interpret expressions or sentences in a specific language, allowing you to define how these expressions should be evaluated.

Visitor focuses on separating algorithms from the objects they operate on. This is useful when you have different algorithms that must operate on a structure of objects, allowing new behaviors to be added without modifying existing classes.

Memento focuses on saving and restoring the previous state of an object without revealing its implementation details. It is useful when you need to implement undo/redo functionality, or simply when you need to maintain a history of the states of an object.

Bibliografía

Patrones de Diseño. (s/f). Juntadeandalucia.es. Recuperado el 19 de abril de 2024, de https://www.juntadeandalucia.es/servicios/madeja/contenido/subsistemas/desarrollo/patrones-diseno

Patrones de diseño. (s/f). Refactoring.guru. Recuperado el 19 de abril de 2024, de https://refactoring.guru/es/design-patterns

Top comments (0)