O Strategy é um padrão de projeto comportamental que permite que você defina uma família de algoritmos, coloque-os em classes separadas, e faça os objetos deles serem intercambiáveis.
Características:
- Context
- Tem a referencia para estratégia que sera utilizada.
- IStrategy
- Define a interface para a estratégia dada.
- Strategy
- Implementação concentra para a estratégia.
Exemplos teórico
Cenário 1: Calcular as taxas para diferentes países:
- O programa irá executar "IStrategy.GetTax(order)" [Context=order]
- O contrato(interface) define: "GetTaxFor(Order oder)" [IStrategy]
- Estratégicas: "SwedenSalesTaxStrategy, USAStateSalesTaxStrategy" [Strategy]
Cenário 2: Criar uma fatura:
- O programa irá executar "IStrategy.CreateInvoice(order)" [Context=order]
- O contrato(interface) define: "CreateInvoice(Order oder)" [IStrategy]
- Estratégicas: "PDFInvoiceStrategy, EmailInvoiceStrategy, PrintInvoiceStrategy" [Strategy]
Cenário 3: Enviar notificação:
- O programa irá executar "IStrategy.SendNotification(notification)" [Contexto=notification]
- O contrato(interface) define: "SendNotification(Notification notification)" [IStrategy]
- Estratégicas: "EmailNotificationStrategy, SmsNotificationStrategy, PushNotificationStrategy" [Strategy]
Resumo:
Com o Design Pattern Strategy, podemos selecionar uma implementação em tempo de execução(runtime) com base em uma abstração(interface), como por exemplo a entrada do usuário, sem precisar estender a classe.
O nosso contexto, por exemplo order citado no cenário 1 e 2, ou notification citado no cenário 3, tem referencia para a interface da estratégia de como calculator a taxa e enviar a fatura no cenário 1 e 2 respectivamente, ou enviar uma notificação no cenário 3. O contexto não precisa saber como a estratégia será resolvida, e sim executa-la.
Show me the code:
Vamos utilizar o cenário 3 para exemplificar o padrão Strategy, o código fonte pode ser encontrado nesse repositório: https://github.com/paulowalravendev/article-design-pattern-strategy
Imaginemos que voce está implementando uma feature que será responsável por mandar notificações, voce cria as seguintes classes de modelo:
// NotificationChannels.cs
public enum NotificationChannels
{
Email,
Sms,
PushNotification,
}
// UserNotificationSettings.cs
public class UserNotificationSettings
{
public UserNotificationSettings(NotificationChannels notificationChanel)
{
NotificationChanel = notificationChanel;
EnableNotification = true;
}
public NotificationChannels NotificationChanel { get; set; }
public bool EnableNotification { get; set; }
}
// Notification.cs
public class Notification
{
public Notification(string content, UserNotificationSettings settings)
{
Content = content;
Settings = settings;
}
public string Content { get; set; }
public UserNotificationSettings Settings { get; set; }
public void Send()
{
if (Settings.EnableNotification)
{
switch (Settings.NotificationChanel)
{
case NotificationChannels.Email:
Console.WriteLine($"Send notification: \"{Content}\" by email");
break;
case NotificationChannels.Sms:
Console.WriteLine($"Send notification: \"{Content}\" by sms");
break;
case NotificationChannels.PushNotification:
Console.WriteLine($"Send notification: \"{Content}\" by push notification");
break;
default:
throw new ArgumentOutOfRangeException();
}
return;
}
Console.WriteLine("Cannot send notification because the user has disabled");
}
}
// Program.cs
var notification1 = new Notification("Hello Strategy", new UserNotificationSettings(NotificationChannels.Email));
var notification2 = new Notification("Hello Strategy", new UserNotificationSettings(NotificationChannels.Sms));
var notification3 =
new Notification("Hello Strategy", new UserNotificationSettings(NotificationChannels.PushNotification));
notification1.Send();
notification2.Send();
notification3.Send();
Temos dois grandes problemas nesse código de exemplo acima, a classe Notificatino no método Send está sabendo como envia a notificação para vários canais que o programa tem hoje, o segundo problema é que se amanhã aparecer um novo canal precisaremos modificar o método de Send novamente, se a implementação de envio por e-mail ou qualquer outro canal mudar, teremos que fazer a mesmo coisa, modificar nossa classe Notification.
Violamos dois princípios importantíssimos, o SRP(Principio da responsabilidade única) e o OCP(Principio aberto-fechado).
Utilizaremos o pattern Strategy para resolver isso, já temos nosso contexto [Notification], e agora vamos implementar a abstração da estratégia[void Send()] e suas implementações [EmailNotificationStrategy, SmsNotificationStrategy, PushNotificationStrategy]
// INotificationStrategy.cs
public interface INotificationStrategy
{
void Send(Notification notification);
}
// EmailNotificationStrategy.cs
public class EmailNotificationStrategy : INotificationStrategy
{
public void Send(Notification notification)
{
if (notification.Settings.EnableNotification == false)
{
Console.WriteLine("Cannot send notification because the user has disabled");
}
if (notification.Settings.NotificationChanel != NotificationChannels.Email)
{
throw new ApplicationException("Strategy is wrong");
}
Console.WriteLine($"Send notification: \"{notification.Content}\" by email");
}
}
public class PushNotificationStrategy : INotificationStrategy
{
public void Send(Notification notification)
{
if (notification.Settings.EnableNotification == false)
{
Console.WriteLine("Cannot send notification because the user has disabled");
}
if (notification.Settings.NotificationChanel != NotificationChannels.PushNotification)
{
throw new ApplicationException("Strategy is wrong");
}
Console.WriteLine($"Send notification: \"{notification.Content}\" by push notification");
}
}
// SmsNotificationStrategy.cs
public class SmsNotificationStrategy : INotificationStrategy
{
public void Send(Notification notification)
{
if (notification.Settings.EnableNotification == false)
{
Console.WriteLine("Cannot send notification because the user has disabled");
}
if (notification.Settings.NotificationChanel != NotificationChannels.Sms)
{
throw new ApplicationException("Strategy is wrong");
}
Console.WriteLine($"Send notification: \"{notification.Content}\" by sms");
}
}
Com a abstração e as implementações das estratégias criadas, vamos modificar o contexto "Notification.cs" para utiliza-las.
// Notification.cs
public class Notification
{
public INotificationStrategy? NotificationStrategy { get; set; }
public Notification(string content, UserNotificationSettings settings)
{
Content = content;
Settings = settings;
}
public string Content { get; set; }
public UserNotificationSettings Settings { get; set; }
public void Send()
{
if (NotificationStrategy == null) throw new ArgumentException("NotificationStrategy is required");
NotificationStrategy.Send(this);
}
}
Na utilização precisaremos passar a estratégica que iremos utilizar na hora do envio, decidi passar por propriedade, voce também poderia passar por constructor modificando a classe Notification.cs se assim preferir.
// Program.cs
var notification1 = new Notification("Hello Strategy", new UserNotificationSettings(NotificationChannels.Email));
var notification2 = new Notification("Hello Strategy", new UserNotificationSettings(NotificationChannels.Sms));
var notification3 =
new Notification("Hello Strategy", new UserNotificationSettings(NotificationChannels.PushNotification));
notification1.NotificationStrategy = new EmailNotificationStrategy();
notification2.NotificationStrategy = new SmsNotificationStrategy();
notification3.NotificationStrategy = new PushNotificationStrategy();
notification1.Send();
notification2.Send();
notification3.Send();
Se amanha aparecer uma nova maneira de notificar, só precisaremos implementar uma nova estratégia e utilizar.
Referencia:
https://refactoring.guru/pt-br/design-patterns/strategy
https://app.pluralsight.com/library/courses/c-sharp-design-patterns-strategy
Capa - Photo by Maarten van den Heuvel on Unsplash
Top comments (0)