Skip to the content.
Skip to the content.

Padrões de Projeto - Padrões Comportamentais 

Neste post, vamos explorar os padrões de projeto comportamentais, que se concentram na interação e responsabilidade entre objetos.

Conhecer os padrões comportamentais aumenta a nossa capacidade de criar sistemas flexíveis e eficientes, permitindo que os objetos se comuniquem de maneira eficaz e que as responsabilidades sejam distribuídas de forma adequada.

Chain of Responsibility (Corrente de Responsabilidade)

O padrão Chain of Responsibility é um padrão de design comportamental que permite que uma solicitação seja passada ao longo de uma cadeia de objetos até que um deles a processe. Cada objeto na cadeia tem a oportunidade de lidar com a solicitação ou passá-la para o próximo objeto na cadeia. Isso promove o desacoplamento entre o remetente da solicitação e os objetos que a processam, permitindo uma maior flexibilidade e extensibilidade no sistema.

Chain of Responsibility

Abaixo, temos um exemplo de implementação do padrão Chain of Responsibility em C#.

Defina uma classe para a requisição e uma classe abstrata para os manipuladores da cadeia.

copy
public class Request(string name)
{
    public string Name { get; } = name;
}
copy
public abstract class Handler
{
    protected Handler? Sucessor { get; private set; }

    public Handler SetSucessor(Handler sucessor)
    {
        Sucessor = sucessor;
        return sucessor;
    }

    public abstract void HandleRequest(Request request);
}

Depois, criamos classes concretas para os manipuladores específicos.

copy
public class ConcreteHandler1 : Handler
{
    public override void HandleRequest(Request request)
    {
        if (request.Name == "Request 1")
        {
            Console.WriteLine("ConcreteHandler1 tratou a request.");
        }
        else
        {
            Sucessor?.HandleRequest(request);
        }
    }
}
copy
public class ConcreteHandler2 : Handler
{
    public override void HandleRequest(Request request)
    {
        if (request.Name == "Request 2")
        {
            Console.WriteLine("ConcreteHandler2 tratou a request.");
        }
        else
        {
            Sucessor?.HandleRequest(request);
        }
    }
}
copy
public class ConcreteHandler3 : Handler
{
    public override void HandleRequest(Request request)
    {
        if (request.Name == "Request 3")
        {
            Console.WriteLine("ConcreteHandler3 tratou a request.");
        }
        else
        {
            Sucessor?.HandleRequest(request);
        }
    }
}

Finalmente, podemos configurar a cadeia de responsabilidade e processar as requisições.

copy
public class Program
{
    public static void Main(string[] args)
    {
        // Criando os manipuladores concretos
        var handler1 = new ConcreteHandler1();
        var handler2 = new ConcreteHandler2();
        var handler3 = new ConcreteHandler3();

        // Configurando a cadeia de responsabilidade
        handler1.SetSucessor(handler2).SetSucessor(handler3);

        // Criando as requisições
        var request1 = new Request("Request 1");
        var request2 = new Request("Request 2");
        var request3 = new Request("Request 3");

        // Processando as requisições
        handler1.HandleRequest(request1);
        handler1.HandleRequest(request2);
        handler1.HandleRequest(request3);

        /* Saída:
        ConcreteHandler1 tratou a request.
        ConcreteHandler2 tratou a request.
        ConcreteHandler3 tratou a request.
        */
    }
}

Command (Comando)

O padrão Command é um padrão de design comportamental que encapsula uma solicitação como um objeto, permitindo que você parametrize clientes com diferentes solicitações, enfileire ou registre solicitações e suporte operações de desfazer. Ele promove o desacoplamento entre o remetente da solicitação e o objeto que a processa, permitindo uma maior flexibilidade e extensibilidade no sistema.

Command

Abaixo, temos um exemplo de implementação do padrão Command em C#:

Defina uma interface para o comando e uma classe concreta para o comando específico.

copy
public interface ICommand
{
    void Execute();
}
copy
public class ConcreteCommandA : ICommand
{
    private readonly Receiver _receiver;

    public ConcreteCommandA(Receiver receiver)
    {
        _receiver = receiver;
    }

    public void Execute()
    {
        Console.WriteLine("Executando Command A");
        _receiver.Action();
    }
}

Defina a classe do receptor que contém a lógica de negócios.

copy
public class Receiver
{
    public void Action()
    {
        Console.WriteLine("Receptor executando ação.");
    }
}

Agora, podemos criar um invocador que irá chamar o comando.

copy
public class Invoker
{
    private ICommand? _command;

    public void SetCommand(ICommand command)
    {
        _command = command;
    }

    public void ExecuteCommand()
    {
        _command?.Execute();
    }
}

Finalmente, podemos configurar o comando e executá-lo.

copy
public class Program
{
    public static void Main(string[] args)
    {
        // Criando o receptor
        var receiver = new Receiver();

        // Criando o comando concreto
        var commandA = new ConcreteCommandA(receiver);

        // Criando o invocador e configurando o comando
        var invoker = new Invoker();
        invoker.SetCommand(commandA);

        // Executando o comando
        invoker.ExecuteCommand();

        /* Saída:
        Executando Command A
        Receptor executando ação.
        */
    }
}

Interpreter (Intérprete)

O padrão Interpreter é um padrão de design comportamental que define uma representação para a gramática de uma linguagem e um interpretador que usa essa representação para interpretar sentenças na linguagem. Ele é útil para criar interpretadores para linguagens específicas, como linguagens de consulta, linguagens de expressão ou linguagens de configuração. O padrão Interpreter promove a separação de preocupações, permitindo que a gramática e a lógica de interpretação sejam tratadas de forma independente. O padrão interpreter é empregado em Expressões Regulares, SQL, linguagens de configuração, entre outros.

Interpreter

Vamos implementar um exemplo simples do padrão Interpreter em C#.

Primeiro vamos definir o contexto e a classe abstrata para as expressões.

copy
public class Context
{
    // Contém informação que é global para o interpretador.
}
copy
public abstract class AbstractExpression
{
    public abstract int Interpret(Context context);
}

Agora, vamos criar expressões concretas para números e operações.

copy
public class TerminalExpression : AbstractExpression
{
    private int _value;

    public TerminalExpression(int value)
    {
        _value = value;
    }

    public override int Interpret(Context context)
    {
        return _value;
    }
}
copy
public class NonterminalExpression : AbstractExpression
{
    private AbstractExpression _left;
    private AbstractExpression _right;

    public NonterminalExpression(AbstractExpression left, AbstractExpression right)
    {
        _left = left;
        _right = right;
    }

    public override int Interpret(Context context)
    {
        // Exemplo: Soma o resultado das expressões esquerda e direita
        return _left.Interpret(context) + _right.Interpret(context);
    }
}

Finalmente, podemos usar as expressões para interpretar uma sentença.

copy
public class Program
{
    public static void Main(string[] args)
    {
        Context context = new Context();

        // Cria expressões terminais
        AbstractExpression cinco = new TerminalExpression(5);
        AbstractExpression dez = new TerminalExpression(10);

        // Cria uma expressão não terminal que soma as duas expressões terminais
        AbstractExpression adicao = new NonterminalExpression(cinco, dez);

        // Interpreta a expressão
        int resultado = adicao.Interpret(context);
        Console.WriteLine($"O resultado da expressão é: {resultado}"); 
        // Saída: O resultado da expressão é: 15
    }
}

Expressões Terminais: Representam os elementos básicos da linguagem, como números ou variáveis. No exemplo, TerminalExpression é uma expressão terminal que retorna um valor inteiro.

Expressões Não Terminais: Representam as regras de produção da gramática, combinando expressões terminais e outras expressões não terminais. No exemplo, NonterminalExpression é uma expressão não terminal que soma os resultados de duas expressões.

Iterator (Iterador)

O padrão Iterator é um padrão de design comportamental que fornece uma maneira de acessar os elementos de um objeto agregado sequencialmente, sem expor sua representação subjacente. Ele permite que você percorra uma coleção de objetos sem precisar conhecer a estrutura interna da coleção. O padrão Iterator promove a encapsulação e a flexibilidade, permitindo que diferentes tipos de coleções sejam percorridos de maneira uniforme.

Iterator

Abaixo, temos um exemplo de implementação do padrão Iterator em C#:

Defina uma interface para o iterador e uma classe concreta para a coleção.

copy
public interface IIterator
{
    void First();
    void Next();
    bool IsDone();
    object CurrentItem();
}
copy
public abstract class Aggregate
{
    public abstract IIterator CreateIterator();
}
copy
public class ConcreteAggregate : Aggregate
{
    private List<object> _items = new List<object>();

    public int Count => _items.Count;

    public object this[int index]
    {
        get { return _items[index]; }
        set { _items.Insert(index, value); }
    }

    public override IIterator CreateIterator()
    {
        return new ConcreteIterator(this);
    }
}

Implementamos o iterador concreto que percorre a coleção.

copy
public class ConcreteIterator : IIterator
{
    private ConcreteAggregate _aggregate;
    private int _current = 0;

    public ConcreteIterator(ConcreteAggregate aggregate)
    {
        _aggregate = aggregate;
    }

    public void First()
    {
        _current = 0;
    }

    public void Next()
    {
        _current++;
    }

    public bool IsDone()
    {
        return _current >= _aggregate.Count;
    }

    public object CurrentItem()
    {
        if (IsDone())
            throw new InvalidOperationException();
        return _aggregate[_current];
    }
}

Finalmente, podemos usar o iterador para percorrer a coleção.

copy
public class Program
{
    public static void Main(string[] args)
    {
        ConcreteAggregate aggregate = new ConcreteAggregate();
        aggregate[0] = "Item 1";
        aggregate[1] = "Item 2";
        aggregate[2] = "Item 3";

        IIterator iterator = aggregate.CreateIterator();

        for (iterator.First(); !iterator.IsDone(); iterator.Next())
        {
            Console.WriteLine(iterator.CurrentItem());
        }

        /* Saída:
        Item 1
        Item 2
        Item 3
        */
    }
}

Mediator (Mediador)

O padrão Mediator é um padrão de design comportamental que define um objeto que encapsula a forma como um conjunto de objetos interage. Ele promove o desacoplamento entre os objetos, evitando que eles se refiram uns aos outros explicitamente e permitindo que eles se comuniquem por meio do mediador. O padrão Mediator é útil para reduzir a complexidade das interações entre objetos e promover uma maior flexibilidade no sistema.

Mediator

Vamos implementar um exemplo simples do padrão Mediator em C#.

Defina uma abstração para o mediador e uma classe concreta para o mediador específico.

copy
public abstract class Mediator
{
    public abstract void Send(string message, Colleague colleague);
}
copy
public abstract class Colleague
{
    protected Mediator Mediator;

    public Colleague(Mediator mediator)
    {
        Mediator = mediator;
    }

    public void Send(string message)
    {
        Mediator.Send(message, this);
    }

    public abstract void Notify(string message);
}

Agora, vamos criar uma implementação concreta do mediador e dos colegas.

copy
public class ComponentA : Colleague
{
    public ComponentA(Mediator mediator) : base(mediator) { }

    public override void Notify(string message)
    {
        Console.WriteLine("ComponentA recebeu a mensagem: " + message);
    }
}
copy
public class ComponentB : Colleague
{
    public ComponentB(Mediator mediator) : base(mediator) { }

    public override void Notify(string message)
    {
        Console.WriteLine("ComponentB recebeu a mensagem: " + message);
    }
}
copy
public class ConcreteMediator : Mediator
{
    public ComponentA ComponentA { get; set; }
    public ComponentB ComponentB { get; set; }

    public override void Send(string message, Colleague colleague)
    {
        if (colleague == ComponentA)
        {
            ComponentB.Notify(message);
        }
        else if (colleague == ComponentB)
        {
            ComponentA.Notify(message);
        }
    }
}

Finalmente, podemos configurar o mediador e os colegas, e enviar mensagens entre eles.

copy
public class Program
{
    public static void Main(string[] args)
    {
        var mediator = new ConcreteMediator();

        var componentA = new ComponentA(mediator);
        var componentB = new ComponentB(mediator);

        mediator.ComponentA = componentA;
        mediator.ComponentB = componentB;

        componentA.Send("Olá do Component A");
        componentB.Send("Olá do Component B");

        /* Saída:
        ComponentB recebeu a mensagem: Olá do Component A
        ComponentA recebeu a mensagem: Olá do Component B
        */
    }
}

Memento (Memento)

O padrão Memento é um padrão de design comportamental que permite capturar e externalizar o estado interno de um objeto sem violar o encapsulamento, para que o objeto possa ser restaurado a esse estado posteriormente. Ele é útil para implementar funcionalidades de desfazer ou para salvar o estado de um objeto em um momento específico. O padrão Memento promove a separação de preocupações, permitindo que a lógica de captura e restauração do estado seja tratada de forma independente.

Memento

Vamos começar o exemplo, definindo a classe Memento que irá armazenar o estado do objeto.

copy
public class Memento
{
    private string _state;

    public Memento(string state)
    {
        _state = state;
    }

    public string GetState()
    {
        return _state;
    }

    public void SetState(string state)
    {
        _state = state;
    }
}

Agora, vamos criar a classe Originator que é o objeto cujo estado queremos salvar e restaurar.

copy
public class Originator
{
    private string _state;

    public Originator(string state)
    {
        _state = state;
    }

    public Memento CreateMemento()
    {
        return new Memento(_state);
    }

    public void SetMemento(Memento memento)
    {
        _state = memento.GetState();
    }
}

Agora podemos criar a classe Caretaker que irá gerenciar os mementos.

copy
public class Caretaker
{
    private readonly List<Memento> _mementos = new List<Memento>();

    public void SaveMemento(Memento memento)
    {
        _mementos.Add(memento);
    }

    public Memento GetMemento(int index)
    {
        return _mementos[index];
    }
}

Finalmente, podemos usar as classes para salvar e restaurar o estado do objeto.

copy
public class Program
{
    public static void Main(string[] args)
    {        
        var originator = new Originator("Initial State");
        var caretaker = new Caretaker();

        // Save the initial state
        caretaker.SaveMemento(originator.CreateMemento());

        originator.SetMemento(new Memento("State 1"));
        caretaker.SaveMemento(originator.CreateMemento());

        originator.SetMemento(new Memento("State 2"));
        caretaker.SaveMemento(originator.CreateMemento());

        Console.WriteLine(caretaker.GetMemento(0).GetState()); // Saída: Initial State
        Console.WriteLine(caretaker.GetMemento(1).GetState()); // Saída: State 1
        Console.WriteLine(caretaker.GetMemento(2).GetState()); // Saída: State 2
    }
}

Observer (Observador)

O padrão Observer é um padrão de design comportamental que define uma dependência um-para-muitos entre objetos, de modo que quando um objeto muda de estado, todos os objetos dependentes são notificados e atualizados automaticamente. Ele é útil para implementar sistemas de eventos ou para criar uma comunicação eficiente entre objetos. O padrão Observer promove o desacoplamento entre o remetente da notificação e os objetos que a recebem, permitindo uma maior flexibilidade e extensibilidade no sistema.

Observer

Vamos implementar um exemplo simples do padrão Observer em C#.

Defina uma abstração para o observador e outra para o sujeito.

copy
public abstract class Observer
{
    public abstract void Update();
}
copy
public abstract class Subject
{
    private readonly List<Observer> _observers = new List<Observer>();

    public void Attach(Observer observer)
    {
        _observers.Add(observer);
    }

    public void Detach(Observer observer)
    {
        _observers.Remove(observer);
    }

    public void Notify()
    {
        foreach (var observer in _observers)
        {
            observer.Update();
        }
    }
}

Agora, vamos criar uma implementação concreta do sujeito e dos observadores.

copy
public class ConcreteSubject : Subject
{
    private string _state;

    public string State
    {
        get { return _state; }
        set
        {
            _state = value;
            Notify();
        }
    }
}
copy
public class ConcreteObserver : Observer
{
    private readonly string _name;
    private readonly ConcreteSubject _subject;

    public ConcreteObserver(string name, ConcreteSubject subject)
    {
        _name = name;
        _subject = subject;
        _subject.Attach(this);
    }

    public override void Update()
    {
        Console.WriteLine($"{_name} recebeu atualização: {_subject.State}");
    }
}

Finalmente, podemos criar o sujeito e os observadores, e alterar o estado do sujeito para ver os observadores sendo notificados.

copy
public class Program
{
    public static void Main(string[] args)
    {
        var subject = new ConcreteSubject();
        var observer1 = new ConcreteObserver("Observer 1", subject);
        var observer2 = new ConcreteObserver("Observer 2", subject);

        subject.State = "State 1";
        /*
        Saída:
        Observer 1 recebeu atualização: State 1
        Observer 2 recebeu atualização: State 1
        */

        subject.State = "State 2";
        /*
        Saída:
        Observer 1 recebeu atualização: State 2
        Observer 2 recebeu atualização: State 2
        */

        subject.Detach(observer1);
        subject.State = "State 3";
        /*
        Saída:
        Observer 2 recebeu atualização: State 3
        */

        subject.Attach(observer1);
        subject.State = "State 4";
        /*
        Saída:
        Observer 1 recebeu atualização: State 4
        Observer 2 recebeu atualização: State 4
        */

        /* Saída completa:
        Observer 1 recebeu atualização: State 1
        Observer 2 recebeu atualização: State 1
        Observer 1 recebeu atualização: State 2
        Observer 2 recebeu atualização: State 2
        Observer 2 recebeu atualização: State 3
        Observer 2 recebeu atualização: State 4
        Observer 1 recebeu atualização: State 4
        */
    }
}

State (Estado)

O padrão State é um padrão de design comportamental que permite que um objeto altere seu comportamento quando seu estado interno muda. Ele é útil para implementar máquinas de estado ou para criar objetos que podem ter diferentes comportamentos dependendo de seu estado. O padrão State promove a separação de preocupações, permitindo que a lógica de comportamento seja tratada de forma independente do estado do objeto.

State

Começando o nosso exemplo, vamos definir uma abstração para os estados e uma classe concreta para o contexto.

copy
public abstract class State
{
    public abstract void Handle(Context context);
}
copy
public class Context
{
    private State _state;

    public Context(State state)
    {
        _state = state;
    }

    public State State
    {
        get { return _state; }
        set
        {
            _state = value;
            Console.WriteLine($"State alterado para: {_state.GetType().Name}");
        }
    }

    public void Request()
    {
        _state.Handle(this);
    }
}

Agora, vamos criar implementações concretas dos estados.

copy
public class ConcreteStateA : State
{
    public override void Handle(Context context)
    {
        Console.WriteLine("State A tratando a solicitação.");
        context.State = new ConcreteStateB();
    }
}
copy
public class ConcreteStateB : State
{
    public override void Handle(Context context)
    {
        Console.WriteLine("State B tratando a solicitação.");
        context.State = new ConcreteStateA();
    }
}

Finalmente, podemos criar o contexto e fazer algumas solicitações para ver os estados alternando.

copy
public class Program
{
    public static void Main(string[] args)
    {
        var context = new Context(new ConcreteStateA());
        context.Request();
        context.Request();
        context.Request();

        /* Saída:
        State A tratando a solicitação.
        State alterado para: ConcreteStateB
        State B tratando a solicitação.
        State alterado para: ConcreteStateA
        State A tratando a solicitação.
        State alterado para: ConcreteStateB
        */
    }
}

Strategy (Estratégia)

O padrão Strategy é um padrão de design comportamental que define uma família de algoritmos, encapsula cada um deles e os torna intercambiáveis. Ele permite que o algoritmo varie independentemente dos clientes que o utilizam. O padrão Strategy promove a flexibilidade e a extensibilidade, permitindo que diferentes algoritmos sejam usados de maneira uniforme.

Strategy

Construindo o exemplo, vamos definir uma abstração para a estratégia e algumas implementações concretas.

copy
public abstract class Strategy
{
    public abstract void AlgorithmInterface();
}
copy
public class ConcreteStrategyA : Strategy
{
    public override void AlgorithmInterface()
    {
        Console.WriteLine("ConcreteStrategyA: Implementação do algoritmo A.");
    }
}
copy
public class ConcreteStrategyB : Strategy
{
    public override void AlgorithmInterface()
    {
        Console.WriteLine("ConcreteStrategyB: Implementação do algoritmo B.");
    }
}

Agora criamos a classe Context que irá usar a estratégia.

copy
public class Context
{
    private Strategy _strategy;

    public Context(Strategy strategy)
    {
        _strategy = strategy;
    }

    public void SetStrategy(Strategy strategy)
    {
        _strategy = strategy;
    }

    public void ExecuteStrategy()
    {
        _strategy.AlgorithmInterface();
    }
}

Finalmente, podemos criar o contexto e usar diferentes estratégias.

copy
public class Program
{
    public static void Main(string[] args)
    {
        var context = new Context(new ConcreteStrategyA());
        context.ExecuteStrategy();

        context.SetStrategy(new ConcreteStrategyB());
        context.ExecuteStrategy();

        /*Saída:
        ConcreteStrategyA: Implementação do algoritmo A.
        ConcreteStrategyB: Implementação do algoritmo B.
        */  
    }
}

Template Method (Método Template)

O padrão Template Method é um padrão de design comportamental que define o esqueleto de um algoritmo em uma operação, deixando alguns passos para serem implementados pelas subclasses. Ele permite que as subclasses redefinam certos passos de um algoritmo sem alterar a estrutura geral do algoritmo. O padrão Template Method promove a reutilização de código e a flexibilidade, permitindo que diferentes implementações de um algoritmo sejam usadas de maneira uniforme.

Template Method

Vamos começar o exemplo, definindo uma classe abstrata que contém o método template e os passos do algoritmo.

copy
public abstract class AbstractClass
{
    public void TemplateMethod()
    {
        PrimitiveOperation1();
        PrimitiveOperation2();
    }

    protected abstract void PrimitiveOperation1();
    protected abstract void PrimitiveOperation2();
}

Agora, vamos criar implementações concretas da classe abstrata.

copy
public class ConcreteClass : AbstractClass
{
    protected override void PrimitiveOperation1()
    {
        Console.WriteLine("ConcreteClass: Implementando PrimitiveOperation1.");
    }

    protected override void PrimitiveOperation2()
    {
        Console.WriteLine("ConcreteClass: Implementando PrimitiveOperation2.");
    }
}

Finalmente, podemos criar uma instância da classe concreta e chamar o método template.

copy
public class Program
{
    public static void Main(string[] args)
    {
        var concreteClass = new ConcreteClass();
        concreteClass.TemplateMethod();

        /* Saída:
        ConcreteClass: Implementando PrimitiveOperation1.
        ConcreteClass: Implementando PrimitiveOperation2.
        */
    }
}

Visitor (Visitante)

O padrão Visitor é um padrão de design comportamental que permite que você adicione novas operações a objetos sem modificar suas classes. Ele define uma operação a ser realizada em elementos de uma estrutura de objetos, permitindo que você separe um algoritmo da estrutura de objetos em que ele opera. O padrão Visitor promove a flexibilidade e a extensibilidade, permitindo que novas operações sejam adicionadas sem alterar as classes dos objetos.

Visitor

Vamos começar o exemplo, definindo uma abstração para os elementos e uma classe concreta para cada tipo de elemento.

copy
public abstract class Element
{
    public abstract void Accept(Visitor visitor);
}
copy
public class ElementA : Element
{
    public override void Accept(Visitor visitor)
    {
        visitor.VisitConcreteElementA(this);
    }

    public void OperationA()
    {
        Console.WriteLine("ElementA está realizando sua operação.");
    }
}
copy
public class ElementB : Element
{
    public override void Accept(Visitor visitor)
    {
        visitor.VisitConcreteElementB(this);
    }

    public void OperationB()
    {
        Console.WriteLine("ElementB está realizando sua operação.");
    }
}

Agora, vamos criar a classe Visitor que irá definir as operações a serem realizadas nos elementos, e as implementações concretas do visitante.

copy
public abstract class Visitor
{
    public abstract void VisitConcreteElementA(ElementA element);
    public abstract void VisitConcreteElementB(ElementB element);
}
copy
public class ConcreteVisitorA : Visitor
{
    public override void VisitConcreteElementA(ElementA element)
    {
        Console.WriteLine("Visitor está processando ElementA");
        element.OperationA();
    }

    public override void VisitConcreteElementB(ElementB element)
    {
        Console.WriteLine("Visitor está processando ElementB");
        element.OperationB();
    }
}
copy
public class ConcreteVisitorB : Visitor
{
    public override void VisitConcreteElementA(ElementA element)
    {
        Console.WriteLine("Visitor está processando ElementA de uma maneira diferente");
        element.OperationA();
    }

    public override void VisitConcreteElementB(ElementB element)
    {
        Console.WriteLine("Visitor está processando ElementB de uma maneira diferente");
        element.OperationB();
    }
}

Finalmente, podemos criar os elementos e os visitantes, e usar o método Accept para realizar as operações.

copy
public class Program
{
    public static void Main(string[] args)
    {
        var elements = new List<Element> { new ElementA(), new ElementB() };
        var visitorA = new ConcreteVisitorA();
        var visitorB = new ConcreteVisitorB();
        foreach (var element in elements)
        {
            element.Accept(visitorA);
            element.Accept(visitorB);
        }

        /* Saída:
        Visitor está processando ElementA
        ElementA está realizando sua operação.
        Visitor está processando ElementA de uma maneira diferente
        ElementA está realizando sua operação.
        Visitor está processando ElementB
        ElementB está realizando sua operação.
        Visitor está processando ElementB de uma maneira diferente
        ElementB está realizando sua operação.
        */
    }
}

Com isso, concluímos a implementação dos padrões comportamentais. Cada um desses padrões tem suas próprias vantagens e casos de uso específicos, e conhecer esses padrões pode ajudar a criar sistemas mais flexíveis, reutilizáveis e fáceis de manter.


O código fonte completo você encontra no repositório do GitHub

Aqui estão algumas recomendações relacionadas ao conteúdo. Esses itens podem ser comprados na Amazon com o meu link de associado.

Confira minhas recomendações de livros e outros itens na página de recomendações.