Aproveite o mês das
carreiras na Alura

Até 44% OFF

Falta pouco!

00

DIAS

00

HORAS

00

MIN

00

SEG

Entendendo o pattern Strategy em PHP

Alura
renan.saggio
renan.saggio

Compartilhe

Embora o PHP inicialmente não ser uma linguagem orientada a objetos, em sua versão 5 foi adicionado suporte para esse popular paradigma. Desde então a linguagem e suas bibliotecas tem adotado cada vez mais suas soluções, mas com isso, precisamos sempre nos atentar às boas práticas. É aqui que os padrões de projeto nos ajudam. Neste post veremos o problema e solução de uma situação bastante comum do mundo OO.

Vamos imaginar que temos um sistema de e-commerce em que o usuário pode pagar o pedido de várias formas e dependendo da forma recebe um desconto.

Forma de pagamento

Banner da Imersão de IA da Alura com Google Gemini. Participe de aulas gratuitas online com certificado. Domine as inovações mais recentes da IA.

Desconto

Boleto

10%

Debito Online

7%

PayPal

5%

Cartão de crédito

0%

Para implementar essa funcionalidade, nós vamos mexer na classe Carrinho que contém um atributo $itens que armazena a lista de itens, um método que calcula o total da compra somando o valor de todos os itens e um método que de acordo com a forma de pagamento calcula o total da compra com um possível desconto.


<?php class Carrinho {
// adiciona item, remove item e outros métodos do carrinho
public function getTotal() {
$total = 0; foreach($this->itens as $item) { $total += $item->getValor(); }
return $total; }
public function getTotalComDesconto() { // precisamos implementar este método }
} ?> 

Então podemos começar a escrever o nosso método getTotalDesconto. Para poder calcular o desconto, precisamos saber qual é a forma de pagamento e por isso vamos pedi-la por parâmetro. Já que temos várias possibilidades, vamos verificar todas.


public function getTotalComDesconto($formaPagamento) { $total = $this->getTotal(); if($formaPagamento == "Boleto") { $total = $total \* 0.9; }else if($formaPagamento == "Debito") { $total = $total \* 0.93; }else if($formaPagamento == "PayPal") { $total = $total \* 0.95; }
return $total;
} 

Mas o que acontece se for necessário implementar uma nova forma de pagamento? Bom, teríamos que adicionar mais um if a esse método e isso não é bom! Veja que o nosso método não vai parar de crescer nunca.

A nossa classe Carrinho sabe muito sobre os meios de pagamento, quem deveria saber como calcular o valor com desconto é o meio de pagamento, temos basicamente um código procedural disfarçado dentro de uma classe onde é necessário verificar todas as possibilidades com ifs para tomar uma decisão.

Já que não é responsabilidade da classe carrinho fazer este cálculo, vamos extrair esse comportamento para classes mais específicas, Por exemplo o primeiro if que verifica se a forma de pagamento Boleto no lugar de fazer:

 if($formaPagamento == "Boleto") { $total = $total \* 0.9; } 

podemos extrair para essa nova classe:


class Boleto {
public function calcula($total) { return $total \* 0.9; }
}

seguindo esse raciocínio cada forma de pagamento que estamos verificando nos ifs vai virar uma classe como Boleto,DebitoOnline,Paypal.


class DebitoOnline { public function calcula($total) { return $total \* 0.93; } }
class PayPal { public function calcula($total) { return $total \* 0.95; } }
class CartaoCredito { public function calcula($total) { return $total; } }

Agora que temos classes especificas para o pagamento vamos utilizá-las  no nosso método.


public function getTotalComDesconto($formaPagamento) {
$total = $this->getTotal();
if($formaPagamento == "Boleto") { $boleto = new Boleto(); $total = $boleto->calcula($total); }else if($formaPagamento == "Debito") { $debito = new DebitoOnline(); $total = $debito->calcula($total); }else if($formaPagamento == "PayPal") { $paypal= new PayPal(); $total = $paypal->calcula($total); }
return $total; } 

Veja que isso não resolveu o nosso problema dos ifs e esse método ainda cresce toda vez que é preciso adicionar uma forma de pagamento. Todas as formas de pagamento tem algo em comum,  elas possuem um método chamado calcula que recebe o valor bruto e retorna o valor com desconto. Se conseguirmos garantir isso, não importa qual é a forma de pagamento, só precisamos chamar o método calcula.

Em orientação a objetos, como podemos forçar que uma classe implemente um método ? Criando uma interface! Então vamos criar a interface FormaDePagamento que força todo mundo a implementar um método chamado calcula.


interface FormaDePagamento { public function calcula($total); }

Agora todas as formas de pagamento devem implementar esta interface:


/\* agora todas as classes que representam uma forma de pagamento implementam a interface FormaDePagamento \*/
class DebitoOnline implements FormaDePagamento { public function calcula($total) { return $total \* 0.93; } }

O nosso getTotalComDesconto pede alguém com um método calcula, que recebe o valor bruto por parâmetro e invoca esse método:


public function getTotalComDesconto(FormaDePagamento $formaPagamento) { $total = $this->getTotal(); $total = $formaPagamento->calcula($total); return $total; } 

Veja que agora toda vez que for necessário adicionar uma forma de pagamento basta criar uma classe que implemente a interface FormaDePagamento portanto, o nosso método getTotalComDesconto não cresce conforme criamos uma forma de pagamento pois agora injetamos a estratégia do cálculo. A principal vantagem dessa solução é que a tarefa de adicionar uma forma de pagamento se torna muito mais simples o que facilita a manutenção do código, alem disso temos classes mais coesas e por mais que a classe carrinho esteja acoplada a outras classes veja que o parâmetro pede uma interface ou seja estamos nos acoplando a uma abstração que são naturalmente mais estáveis.

Esta abordagem que utilizamos é um design pattern que chamamos de Strategy.

Se interessa por design patterns? Veja este pattern e outros no nosso curso do alura!

Veja outros artigos sobre Programação