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:
Aqui estão os participantes:
Element
é a interface ou classe abstrata que defineAccept()
o método para aceitar umVisitor
objeto.ElementA
eElementB
são classes concretas daElement
classe ou implementações daElement
interface. Eles fornecem a implementação real doAccept()
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 nosElement
objetos. Ele define um conjunto deVisit*
métodos, cada um correspondendo a um tipo de elemento (ElementA
eElementB
).ConcreteVisitor
é a implementação daVisitor
interface ou da classe concreta daVisitor
classe abstrata. AConcreteVisitor
classe fornece a implementação real dosVisit*
métodos para cada elemento.
Exemplo de padrão de visitante C#
Suponha que você tenha a Employee
classe como classe base das classes BackOfficeEmployee
e SalesEmployee
. A Employee
classe tem duas propriedades Name
e Salary
.
A
classe adiciona uma nova propriedade, BackOfficeEmployee
Bonus
enquanto a
classe possui outra nova propriedade, SalesEmployee
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 GetTotalCompensation
método às classes BackOfficeEmployee
e SalesEmployee
.
A remuneração total dos BackOfficeEmployee
seria a soma do salário e bônus, enquanto a remuneração total dos SalesEmployee
seria a soma do salário e comissão.
Posteriormente, você precisará calcular as opções de ações que ambos BackOfficeEmployee
podem SalesEmployee
obter. Para fazer isso, você pode adicionar outro método chamado GetStockOptions
para 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 BackOfficeEmployee
as SalesEmployee
classes 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:
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 IVisitableElement
interface que possui o Accept()
método com um IVisitor
argumento:
public interface IVisitableElement
{
void Accept(IVisitor visitor);
}
Linguagem de código: C# ( cs )
Segundo, defina a IVisitor
interface que possui dois Visit
métodos, cada um aceitando o objeto BackOfficeEmployee
ou SalesEmployee
respectivamente:
public interface IVisitor
{
void Visit(BackOfficeEmployee e);
void Visit(SalesEmployee e);
}
Linguagem de código: C# ( cs )
Terceiro, implemente a IVisitableElement
interface das classes BackOfficeEmployee
e 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 CompensationVisitor
que implementa a IVisitor
interface. Os Visit*
métodos calculam a remuneração total dos objetos BackOfficeEmployee
e 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 EmployeeStockOptionVisitor
que 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 IVisitableElement
objetos incluindo BackOfficeEmployee
e SalesEmployee
e use CompensationVisitor
e EmployeeStockOptionVisitor
para 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.