Design Pattern: Introdução e características
Tags: programming, productivity, algorithms
Published: 2021-10-14 17:23:41

## Introdução

Design Patterns (padrões de projeto) são soluções típicas para problemas comuns, são como plantas baixa de um prédio por exemplo. Um padrão de projeto não é algo que pode ser copiado para dentro do seu contexto, não funciona como exemplos de funções de documentações, libs ou aquele código maroto do stackoverflow 😝. Pois o padrão é um conceito geral a ser usado para resolver um problema particular.

Temos 3 tipos de padrões:

1. Criacionais: Fornecem criação de objetos que aumentam a flexibilidade e a reutilização de código.
2. Estruturais: Mostram como é montado objetos e classes em estruturas mais robustas, mantendo-a flexível e eficiente.
3. Comportamentais: Cuidam da comunicação eficiente e da assinalação entre objetos.

## Por que aprender sobre isso?

É possível trabalhar "tranquilamente" como desenvolvedor por anos sem saber nada sobre padrões, muita gente segue assim inclusive, masssss de um jeito ou de outro, no dia a dia vc acabará implementando um dos padrões existentes sem saber.

> Sendo assim, pq não aprender sobre eles?

Os padrões de projeto resolvem diversos problemas comuns em projetos de software, pense neles como se fosse uma caixa de ferramentas, vc olha o problema, abre a caixa, analisa qual ferramenta vai precisar no momento, pega e usa.

> Simples, não?

Além disso, eles propõem uma definição para tais implementações, vc se depara com um código, vê que se parece com algum padrão e diz, é possível resolver isso usando Strategy, caso o colega conheça o padrão, logo ele entenderá a sua sugestão.

> Se não entender, é uma ótima oportunidade para ensinar e passar o conhecimento.

## Características de um bom projeto

> Antes de seguir vamos comentar sobre algumas características que são almejadas nos nossos projetos e algumas coisas que devemos evitar.

- Reutilização de código

    É um dos modos mais comuns para se reduzir custos no desenvolvimento. O objetivo é simples: Ao invés de copiar e colar o mesmo código dentro do mesmo projeto ou desenvolver algo do zero sempre, por que não reutilizar códigos já existentes nos seus novos projetos?

    O objetivo parece muito simples, mas na prática, fazer um código que já existe e que funciona sozinho, funcionar em outro contexto, exige um esforço adicional.

    Para conseguir reutilizar com êxito seus códigos, deve-se evitar dependências de classes concretas ao invés de interfaces e operações hardcoded. Essas coisas reduzem a flexibilidade do seu código e deixa ele cada vez mais difícil a sua reutilização.

- Extensibilidade

    Seu código está pronto para novos componentes? Novas regras? 

    Tendo um ecommerce, o cliente percebe a necessidade de adicionar um novo método de pagamento, seu código deve estar preparado e componentizado para suportar tais mudanças, caso contrário ele quebrará. Uma coisa temos que ter em mente, a única constante de um desenvolvedor é a mudança, esteja preparado para ela, implemente algo hoje para que não quebre se for mudado amanhã.

    Existe um lado bom nisso: Se alguém pede novos recursos na sua aplicação, significa que ela ainda é importante para a empresa.

Estamos falando sobre um bom projeto, mas o que é um bom projeto? Como podemos deixar nossa arquitetura flexível, estável, escalável e de fácil entendimento?

Essas perguntas são boas, mas as respostas dependem de cada contexto. A boa notícia é que existem vários princípios universais de projeto de software que podem ajudar nessas respostas.

## Encapsulamento

> Identifique tudo que é variável e separe dos que permanecem os mesmos.

O objetivo desse princípio é minimizar o efeito causado por mudanças.

Imagine o seguinte, temos um supermercado, ao efetuar um pagamento, ele é recusado, oq vai acontecer com os produtos no caixa? Oq acontece com o cliente? Seu código deve estar encapsulado para poder tratar essas excessões. Você pode isolar cada parte que varia em módulos independentes, protegendo o restante da aplicação contra os efeitos indesejados, dessa forma é possível tratar os erros lançados e evitando complicações, como por exemplo um estoque que não volta ao seu estado original quando o pagamento não é efetuado.

> Quanto menos tempo vc gasta fazendo mudanças, mais tempo ganha para implementar novas funcionalidades.

## Encapsulamento nível método

Bom, suponha que vc tenha um ecommerce com o método `checkout` nele contém toda a regra de calcular os valores totais de acordo com o meio de pagamento, calcula frete, verifica se tem promoção e fecha o pedido tudo no mesmo código, como por exemplo:

```php
<?php
class Cart {
    use Promotion;

    public function checkout(){

        $products = [1, 2, 3, 4];
        foreach ($arr as &$value) {
            $totalValue += $value;
        }

        // cálculo de promoção
        // cálculo de frete
        // fechar pedido
    }
}
```

Caso alguma regra dessas mude a função ficará cada vez mais extensa, mais complexa para manutenção e refatoração, vai precisar de muitos testes para garantir que nada quebre, a melhor estratégia é extrair certos trechos para contextos menores, deixando dessa forma:

```php
<?php
class Cart {
    public function checkout(){

        $products = [1, 2, 3, 4];
        foreach ($arr as &$value) {
            $totalValue += $value;
        }

        $this->calculatePromotion();
        $this->calculateShipping();

        // fechar pedido
    }

    function calculatePromotion(){
        // cálculo de promoção
    } 

    function calculateShipping(){
        // cálculo de frete
    }
}
```

Agora usando dessa forma, cada função ficou isolada da principal, caso alguma se torne mais complexa, vc pode facilmente criar uma classe, a refatoração se torna menos onerosa.

## Encapsulamento nível de classe

Seu método que antes era simples e tinha apenas uma responsabilidade, vem ganhando mais e mais recursos, se tornando mais complexo, e assim desfocando a responsabilidade primária da classe a qual ele pertence. Extrair tudo para uma nova classe pode deixar as coisas mais claras e simples.

- Nosso exemplo se encontra dessa forma


![1.jpeg](https://cdn.hashnode.com/res/hashnode/image/upload/v1631726691765/TNsL8VKN8x.jpeg)

- Agora faremos um objeto especial receber todo o trabalho relacionado a promoções.


![2.jpeg](https://cdn.hashnode.com/res/hashnode/image/upload/v1631726680036/J3wmtKnTD.jpeg)

- O código ficará algo assim:

    ```php
    <?php 
    class Cart {
        use Promotion;

        public function checkout(){

            //calcula sub total
            $totalValue = $subTotal + Promotion::calculatePromotion(products);

            $this->calculateShipping();
            //fecha pedido
        }
    }
    ```

O calculo das promoções ficará abstraído dentro da classe `Promotion`.

## Desenvolva interfaces

> Nunca dependa de classes concretas e sim abstrações.

Seu código precisa ser o mais flexível possível, para isso é preciso que seja possível extendê-lo facilmente sem que nada quebre no código existente. Para garantir que essa afirmação é correta vamos para mais um exemplo, saindo do contexto de ecommerce, usaremos `gatos` para simplificar. 

> Quem ai não ama gatinhos não é mesmo?

> Se não ama, pode se retirar por favor, BRINKS

Um gato que come qualquer comida é mais flexível e fácil de satisfazer do que um que come apenas salmão, gato chique esse ein. Você ainda pode alimentar o primeiro gato com salmão para agrada-lo vez ou outra, mas não poderá alimentar o segundo com atum por exemplo quando chegar no fim do mês quando a grana esta curta, o cardápio do primeiro pode ser estendido para qualquer outra comida, sendo mais fácil para alimenta-lo.

- Diagrama demonstrando os dois gatos


![3.jpeg](https://cdn.hashnode.com/res/hashnode/image/upload/v1631726661415/ZS4B5XA7O.jpeg)

> Mas como eu deixo meu código mais flexível??

Existem alguns meios de configurar uma colaboração entre objetos:

1. Determine o que exatamente um objeto precisa do outro, quais métodos ele irá executar.
2. Descreva esses métodos em uma nova interface ou classe abstrata.
3. Na classe que é um dependência, implemente essa interface.
4. Agora faça a segunda classe ser dependente dá interface ao invés da classe concreta. Ainda será possível fazer ela funcionar com objetos da classe original, só que a conexão é muito mais flexível.
* Gato 1

    ```php
    <?php
    class Salmon {

        public $nutrition;

        public function __construct()
        {
    			$this->nutrition = 10;
        }
    }

    class Cat {

        public function eat( Salmon $salmon ) : string
    		{
            return 'The cat is was fed with nutrition: '.$salmon->nutrition;
        }
     
    }

    $cat = new Cat;
    echo $cat->eat(new Salmon);

    // Output
    // The cat was fed with nutrition: 10

    ```
* Gato 2

    ```php
    <?php
    abstract class Food {

        public $nutricion;

    }

    class Salmon extends Food {

        public function __construct()
        {
    			$this->nutricion = 10;
        }

    }

    class Cat {

        public function eat( Food $foodName ) : string
    		{
            return 'The cat is was with nutricion: '.$foodName->nutricion;
        }
     
    }

    $cat = new Cat;
    echo $cat->eat(new Salmon);

    // Output
    // The cat was fed with nutricion: 10
    ```

Antes e depois de extrair a interface, o segundo código é mais flexível que o primeiro, pois quaisquer novas comidas serão "comidas" pelo gato e não somente uma.

> Ufa, agora sim poderei economizar um pouco com meu gato.

## Prefira composição sobre herança

A herança é provavelmente a maneira mais comum de se reutilizar código, é a mais óbvia né, temos duas classes com códigos em comum, a primeira coisa que vem na mente, vamos criar uma classe base, pegamos o código comum, joga nela e pronto, extende nas outras duas, sucesso.

Porém, a herança vem com um lado ruim que só é aparente quando seu projeto está com mais classes e mudar tudo fica muito difícil, aqui temos uma lista desses problemas:

- Uma subclasse tem que implementar TODOS os métodos que possuem na interface da superclasse mesmo que não seja usado.
- Quando sobrescrever métodos, vc precisa se certificar que o novo comportamento é compatível com o base, pois a subclasse pode ser passados para qualquer código do tipo da superclasse e vc não quer nada quebre.
- A herança quebra o encapsulamento da superclasse, detalhes internos da classe mãe ficam disponíveis para a subclasse.
- As subclasses estão firmemente acopladas as superclasses, caso tenha alguma mudança em uma superclasse pode gerar um efeito colateral e quebrar o código das subclasses.
- Tentar reutilizar código através da herança pode levar a criação de uma cadeia de heranças paralelas, a herança acontece em uma dimensão.

Existe uma alternativa para a herança que tem o nome de composição. A herança representa a relação de "pipoca é um alimento", "cachorro é um animal" a composição representa a relação "cachorro tem 2 olhos", "pipoca tem temperos". 

> Sempre pense no seu código o mais desacoplado possível.

É isso galera, nos vemos no próximo artigo.