DEV Community

Cover image for Design Patterns: Strategy
Paulo Walraven
Paulo Walraven

Posted on

Design Patterns: Strategy

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,
}
Enter fullscreen mode Exit fullscreen mode
// UserNotificationSettings.cs
public class UserNotificationSettings
{
   public UserNotificationSettings(NotificationChannels notificationChanel)
   {
       NotificationChanel = notificationChanel;
       EnableNotification = true;
   }

   public NotificationChannels NotificationChanel { get; set; }
   public bool EnableNotification { get; set; }
}
Enter fullscreen mode Exit fullscreen mode
// 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");
   }
}
Enter fullscreen mode Exit fullscreen mode
// 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();
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode
// 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");
   }
}
Enter fullscreen mode Exit fullscreen mode
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");
   }
}
Enter fullscreen mode Exit fullscreen mode
// 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");
   }
}
Enter fullscreen mode Exit fullscreen mode

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);
   }
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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)