Padrão de visitante C#

Resumo : neste tutorial, você aprenderá sobre o padrão C# Visitor e como usá-lo para adicionar novos comportamentos a várias classes relacionadas sem modificá-las diretamente.

Introdução ao padrão C# Visitor

O padrão Visitor é um padrão de design comportamental que permite adicionar novos comportamentos a classes existentes sem modificá-las. O padrão Visitor faz isso separando os comportamentos das classes e movendo-os para classes separadas chamadas Visitor.

O padrão Visitor é útil quando você tem muitas classes relacionadas e deseja adicionar novos comportamentos a todas elas. Portanto, em vez de modificar cada classe individual separadamente, você pode usar o padrão Visitor para definir os novos comportamentos nas classes Visitor.

O diagrama UML a seguir ilustra o padrão Visitante:

Padrão de design de visitante C#

Aqui estão os participantes:

  • Elementé a interface ou classe abstrata que define Accept()o método para aceitar um Visitorobjeto.
  • ElementAe ElementBsão classes concretas da Elementclasse ou implementações da Elementinterface. Eles fornecem a implementação real do Accept()método. Eles também têm seus próprios métodos específicos.
  • Visitoré a interface ou classe abstrata que define o comportamento executado nos Elementobjetos. Ele define um conjunto de Visit*métodos, cada um correspondendo a um tipo de elemento ( ElementAe ElementB).
  • ConcreteVisitoré a implementação da Visitorinterface ou da classe concreta da Visitorclasse abstrata. A ConcreteVisitorclasse fornece a implementação real dos Visit*métodos para cada elemento.

Exemplo de padrão de visitante C#

Suponha que você tenha a Employeeclasse como classe base das classes BackOfficeEmployeee SalesEmployee. A Employeeclasse tem duas propriedades Namee Salary.

A BackOfficeEmployeeclasse adiciona uma nova propriedade, Bonusenquanto a SalesEmployeeclasse possui outra nova propriedade, Commission.

public class Employee
{
    public string Name { get;  set; }
    public decimal Salary { get;  set; }

    public Employee(string name, decimal salary)
    {
        Name = name;
        Salary = salary;
    }
}

public class BackOfficeEmployee : Employee
{
    public decimal Bonus {   get; set; }
    public BackOfficeEmployee(string name, decimal salary, decimal bonus) : base(name, salary)
    {
        Bonus = bonus;
    }
}

public class SalesEmployee : Employee
{
    public decimal Commission { get; set; }
    public SalesEmployee(string name, decimal salary, decimal commission) : base(name, salary)
    {
        Commission = commission;
    }
}Linguagem de código:  C#  ( cs )

Imagine que você precise calcular a remuneração total de todos os funcionários. Para fazer isso, você pode adicionar o GetTotalCompensationmétodo às classes BackOfficeEmployeee SalesEmployee.

A remuneração total dos BackOfficeEmployeeseria a soma do salário e bônus, enquanto a remuneração total dos SalesEmployeeseria a soma do salário e comissão.

Posteriormente, você precisará calcular as opções de ações que ambos BackOfficeEmployeepodem SalesEmployeeobter. Para fazer isso, você pode adicionar outro método chamado GetStockOptionspara ambas as classes.

Fazer isso viola o princípio aberto-fechado porque a classe deve ser aberta para extensão e fechada para modificação.

Também violará o princípio da responsabilidade única porque BackOfficeEmployeeas SalesEmployeeclasses não são apenas representações do Funcionário de Back Office e do Vendedor, mas também são responsáveis ​​pelo cálculo da remuneração total e das opções de ações.

Para resolver isso, você pode usar o padrão Visitor conforme descrito no diagrama UML a seguir:

Exemplo de padrão de visitante C#

O programa a seguir ilustra como usar o padrão Visitante para adicionar função de cálculo de remuneração e opções de ações:

namespace VisitorPattern;

public interface IVisitableElement
{
    void Accept(IVisitor visitor);
}

public interface IVisitor
{
    void Visit(BackOfficeEmployee e);
    void Visit(SalesEmployee e);
}

public class Employee
{
    public string Name { get; set; }
    public decimal Salary { get; set; }
    public Employee(string name, decimal salary)
    {
        Name = name;
        Salary = salary;
    }

}

public class BackOfficeEmployee : Employee, IVisitableElement
{
    public decimal Bonus { get; set;}
    public BackOfficeEmployee(string name, decimal salary, decimal bonus) : base(name, salary)
    {
        Bonus = bonus;
    }

    public void Accept(IVisitor visitor) => visitor.Visit(this);
}

public class SalesEmployee : Employee, IVisitableElement
{
    public decimal Commission { get; set; }
    public SalesEmployee(string name, decimal salary, decimal commission) : base(name, salary)
    {
        Commission = commission;
    }
    public void Accept(IVisitor visitor) => visitor.Visit(this);
}

public class CompensationVisitor : IVisitor
{
    public decimal TotalCompensation { get; private set; } = 0;
    public void Visit(BackOfficeEmployee e)
    {
        TotalCompensation += e.Salary + e.Bonus;
    }
    public void Visit(SalesEmployee e)
    {
        TotalCompensation += e.Salary + e.Commission;
    }
}

public class EmployeeStockOptionVisitor : IVisitor
{
    public decimal TotalUnit { get; private set; } = 0;
    public void Visit(BackOfficeEmployee e)
    {
        var totalCompensation = e.Salary + e.Bonus;
        TotalUnit += totalCompensation > 100000 ? 1000 : 500;

    }

    public void Visit(SalesEmployee e)
    {
        var totalCompensation = e.Salary + e.Commission;
        TotalUnit += totalCompensation > 100000 ? 1000 : 500;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        var employees = new List<IVisitableElement>
        {
            new BackOfficeEmployee("John",80000,10000),
            new BackOfficeEmployee("Jane",120000,10000),
            new SalesEmployee("Bob",90000,40000),
        };

        // Calculating total compensation
        var compensationVisitor = new CompensationVisitor();

        employees.ForEach(e => e.Accept(compensationVisitor));
        Console.WriteLine($"{compensationVisitor.TotalCompensation:C}");


        // Calculating total stock options
        var esoVisitor = new EmployeeStockOptionVisitor();
        employees.ForEach(e => e.Accept(esoVisitor));

        Console.WriteLine($"{esoVisitor.TotalUnit}");

    }
}Linguagem de código:  C#  ( cs )

Como funciona.

Primeiro, defina a IVisitableElementinterface que possui o Accept()método com um IVisitorargumento:

public interface IVisitableElement
{
    void Accept(IVisitor visitor);
}Linguagem de código:  C#  ( cs )

Segundo, defina a IVisitorinterface que possui dois Visitmétodos, cada um aceitando o objeto BackOfficeEmployeeou SalesEmployeerespectivamente:

public interface IVisitor
{
    void Visit(BackOfficeEmployee e);
    void Visit(SalesEmployee e);
}Linguagem de código:  C#  ( cs )

Terceiro, implemente a IVisitableElementinterface das classes BackOfficeEmployeee SalesEmployee:

public class BackOfficeEmployee : Employee, IVisitableElement
{
    public decimal Bonus { get; set;}
    public BackOfficeEmployee(string name, decimal salary, decimal bonus) : base(name, salary)
    {
        Bonus = bonus;
    }

    public void Accept(IVisitor visitor) => visitor.Visit(this);
}

public class SalesEmployee : Employee, IVisitableElement
{
    public decimal Commission { get; set; }
    public SalesEmployee(string name, decimal salary, decimal commission) : base(name, salary)
    {
        Commission = commission;
    }
    public void Accept(IVisitor visitor) => visitor.Visit(this);
}Linguagem de código:  C#  ( cs )

Quarto, defina o CompensationVisitorque implementa a IVisitorinterface. Os Visit*métodos calculam a remuneração total dos objetos BackOfficeEmployeee SalesEmployee:

public class CompensationVisitor : IVisitor
{
    public decimal TotalCompensation { get; private set; } = 0;
    public void Visit(BackOfficeEmployee e)
    {
        TotalCompensation += e.Salary + e.Bonus;
    }
    public void Visit(SalesEmployee e)
    {
        TotalCompensation += e.Salary + e.Commission;
    }
}Linguagem de código:  C#  ( cs )

Quinto, defina a interface EmployeeStockOptionVisitorque implementa IVisitor. Os Visit()métodos calculam a unidade total de opções de ações necessária:

public class EmployeeStockOptionVisitor : IVisitor
{
    public decimal TotalUnit { get; private set; } = 0;
    public void Visit(BackOfficeEmployee e)
    {
        var totalCompensation = e.Salary + e.Bonus;
        TotalUnit += totalCompensation > 100000 ? 1000 : 500;
    }

    public void Visit(SalesEmployee e)
    {
        var totalCompensation = e.Salary + e.Commission;
        TotalUnit += totalCompensation > 100000 ? 1000 : 500;
    }
}Linguagem de código:  C#  ( cs )

Por fim, crie uma lista de IVisitableElementobjetos incluindo BackOfficeEmployeee SalesEmployeee use CompensationVisitore EmployeeStockOptionVisitorpara calcular a remuneração total e as opções de ações:

public class Program
{
    public static void Main(string[] args)
    {

        var employees = new List<IVisitableElement>
        {
            new BackOfficeEmployee("John",80000,10000),
            new BackOfficeEmployee("Jane",120000,10000),
            new SalesEmployee("Bob",90000,40000),
        };

        // Calculating total compensation
        var compensationVisitor = new CompensationVisitor();
        employees.ForEach(e => e.Accept(compensationVisitor));

        Console.WriteLine($"{compensationVisitor.TotalCompensation:C}");


        // Calculating total stock options
        var esoVisitor = new EmployeeStockOptionVisitor();
        employees.ForEach(e => e.Accept(esoVisitor));

        Console.WriteLine($"{esoVisitor.TotalUnit}");

    }
}Linguagem de código:  C#  ( cs )

Resumo

  • Use o padrão Visitor para adicionar novo comportamento a um grupo de classes relacionadas sem modificá-las diretamente.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *