JavaRush /Blogue Java /Random-PT /Cinco princípios básicos de design de classe (SOLID) em J...
Ve4niY
Nível 14

Cinco princípios básicos de design de classe (SOLID) em Java

Publicado no grupo Random-PT
Classes são os blocos a partir dos quais um aplicativo é construído. Assim como os tijolos de um prédio. Aulas mal escritas podem causar problemas um dia. Cinco princípios básicos de design de classes (SOLID) em Java - 1Para saber se uma aula está escrita corretamente, você pode verificar os “padrões de qualidade”. Em Java, esses são os chamados princípios SOLID. Vamos falar sobre eles.

Princípios SOLID em Java

SOLID é um acrônimo formado pelas letras maiúsculas dos primeiros cinco princípios de OOP e design. Os princípios foram inventados por Robert Martin no início dos anos 2000, e a sigla foi posteriormente cunhada por Michael Feathers. Aqui está o que os princípios SOLID incluem:
  1. Princípio da Responsabilidade Única.
  2. Princípio Aberto Fechado.
  3. Princípio da Substituição de Liskov.
  4. Princípio de segregação de interface.
  5. Princípio de Inversão de Dependência.

Princípio de Responsabilidade Única (SRP)

Este princípio afirma que nunca deve haver mais de um motivo para mudar de classe. Cada objeto tem uma responsabilidade, completamente encapsulada em uma classe. Todos os serviços de classe visam garantir essa responsabilidade. Essas classes sempre serão fáceis de alterar se necessário, porque fica claro pelo que a classe é responsável e pelo que não é. Ou seja, será possível fazer mudanças e não ter medo das consequências - o impacto em outros objetos. E esse código é muito mais fácil de testar, porque você cobre uma funcionalidade com testes isolados de todas as outras. Imagine um módulo que processa pedidos. Se o pedido for gerado corretamente, ele o salva no banco de dados e envia um e-mail para confirmação do pedido:
public class OrderProcessor {

    public void process(Order order){
        if (order.isValid() && save(order)) {
            sendConfirmationEmail(order);
        }
    }

    private boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }

    private void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}
Tal módulo pode mudar por três razões. Em primeiro lugar, a lógica de processamento do pedido pode ser diferente, em segundo lugar, o método de salvá-lo (tipo de banco de dados), em terceiro lugar, o método de envio de uma carta de confirmação (por exemplo, em vez de um e-mail você precisa enviar SMS). O Princípio da Responsabilidade Única implica que os três aspectos deste problema são, na verdade, três responsabilidades diferentes. Isso significa que eles devem estar em classes ou módulos diferentes. Combinar múltiplas entidades que podem mudar em momentos diferentes e por motivos diferentes é considerada uma má decisão de projeto. É muito melhor dividir o módulo em três módulos separados, cada um dos quais executará uma única função:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}

public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}

Princípio Aberto/Fechado (OCP)

Este princípio é descrito sucintamente da seguinte forma: as entidades de software (classes, módulos, funções, etc.) devem estar abertas para extensão, mas fechadas para mudança . Isso significa que deveria ser possível alterar o comportamento externo de uma classe sem fazer alterações físicas na própria classe. Seguindo este princípio, as classes são desenvolvidas de forma que para adequá-la às condições específicas da aplicação, basta estendê-la e redefinir algumas funções. Portanto, o sistema deve ser flexível, capaz de funcionar sob condições variáveis ​​sem alterar o código-fonte. Continuando com nosso exemplo de pedido, digamos que precisamos realizar algumas ações antes do pedido ser processado e após o envio do e-mail de confirmação. Em vez de alterar a classe em si OrderProcessor, iremos estendê-la e alcançar uma solução para o problema em questão sem violar o princípio do OCP:
public class OrderProcessorWithPreAndPostProcessing extends OrderProcessor {

    @Override
    public void process(Order order) {
        beforeProcessing();
        super.process(order);
        afterProcessing();
    }

    private void beforeProcessing() {
        // Perform some actions before processing the order
    }

    private void afterProcessing() {
        // Perform some actions after order processing
    }
}

Princípio de Substituição de Barbara Liskov (LSP)

Esta é uma variação do princípio aberto/fechado discutido anteriormente. Pode ser descrito da seguinte forma: os objetos de um programa podem ser substituídos por seus herdeiros sem alterar as propriedades do programa. Isso significa que uma classe desenvolvida estendendo uma classe base deve substituir seus métodos de uma forma que não interrompa a funcionalidade do ponto de vista do cliente. Ou seja, se um desenvolvedor estende sua classe e a utiliza em uma aplicação, ele não deve alterar o comportamento esperado dos métodos sobrescritos. As subclasses devem substituir os métodos da classe base de uma forma que não interrompa a funcionalidade do ponto de vista do cliente. Isso pode ser examinado em detalhes usando o exemplo a seguir. Vamos supor que temos uma classe responsável pela validação do pedido e verifica se todos os itens do pedido estão em estoque. Esta classe possui um método isValidque retorna true ou false :
public class OrderStockValidator {

    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if (! item.isInStock()) {
                return false;
            }
        }

        return true;
    }
}
Suponhamos também que alguns pedidos precisam ser validados de forma diferente: verifique se todas as mercadorias do pedido estão em estoque e se todas as mercadorias estão embaladas. Para fazer isso, estendemos a classe OrderStockValidatorcom a classe OrderStockAndPackValidator:
public class OrderStockAndPackValidator extends OrderStockValidator {

    @Override
    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if ( !item.isInStock() || !item.isPacked() ){
                throw new IllegalStateException(
                     String.format("Order %d is not valid!", order.getId())
                );
            }
        }

        return true;
    }
}
Porém, nesta classe violamos o princípio LSP, pois em vez de retornar false se o pedido não passar na validação, nosso método lança uma exceção IllegalStateException. Os clientes deste código não esperam isso: eles esperam que true ou false sejam retornados . Isso pode levar a erros no programa.

Princípio de divisão de interface (ISP)

Caracterizado pela seguinte afirmação: Os clientes não devem ser forçados a implementar métodos que não utilizarão . O princípio da separação de interfaces sugere que interfaces muito “grossas” precisam ser divididas em outras menores e mais específicas, para que os clientes de interfaces pequenas conheçam apenas os métodos necessários para seu trabalho. Como resultado, ao alterar um método de interface, os clientes que não utilizam este método não devem mudar. Vejamos um exemplo. O desenvolvedor Alex criou a interface "relatório" e adicionou dois métodos: generateExcel()e generatedPdf(). Agora o Cliente A deseja utilizar esta interface, mas pretende utilizar apenas relatórios em PDF e não em Excel. Ele ficará satisfeito com esta funcionalidade? Não. Ele terá que implementar dois métodos, um dos quais é amplamente desnecessário e só existe graças a Alex, o designer do software. O cliente usará uma interface diferente ou deixará o campo do Excel em branco. Então, qual é a solução? Consiste em dividir a interface existente em duas menores. Um é um relatório em formato PDF, o segundo é um relatório em formato Excel. Isso dará ao usuário a oportunidade de usar apenas as funcionalidades de que necessita.

Princípio de Inversão de Dependência (DIP)

Este princípio SOLID em Java é descrito da seguinte forma: as dependências dentro do sistema são construídas com base em abstrações . Os módulos de nível superior são independentes dos módulos de nível inferior. As abstrações não devem depender de detalhes. Os detalhes devem depender de abstrações. O software precisa ser projetado de forma que os vários módulos sejam autônomos e se conectem entre si por meio de abstração. Uma aplicação clássica deste princípio é o framework Spring. Na estrutura Spring, todos os módulos são implementados como componentes separados que podem funcionar juntos. Eles são tão independentes que podem ser usados ​​facilmente em outros módulos de software além do framework Spring. Isto é conseguido através da dependência de princípios fechados e abertos. Todos os módulos fornecem acesso apenas a uma abstração que pode ser usada em outro módulo. Vamos tentar demonstrar isso com um exemplo. Falando sobre o princípio da responsabilidade exclusiva, consideramos alguns OrderProcessor. Vamos dar uma outra olhada no código desta classe:
public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}
Neste exemplo, o nosso OrderProcessordepende de duas classes específicas MySQLOrderRepositorye ConfirmationEmailSender. Apresentamos também o código para estas classes:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}
Essas classes estão longe de serem chamadas de abstrações. E do ponto de vista do princípio DIP, seria mais correto começar criando algumas abstrações que nos permitirão operar com elas no futuro, ao invés de implementações específicas. Vamos criar duas interfaces MailSendere OrderRepository, que se tornarão nossas abstrações:
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
Agora vamos implementar essas interfaces em classes que já estão prontas para isso:
public class ConfirmationEmailSender implements MailSender {

    @Override
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }

}

public class MySQLOrderRepository implements OrderRepository {

    @Override
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}
Fizemos o trabalho preparatório para que nossa aula OrderProcessornão dependa de detalhes concretos, mas de abstrações. Vamos fazer alterações introduzindo nossas dependências no construtor da classe:
public class OrderProcessor {

    private MailSender mailSender;
    private OrderRepository repository;

    public OrderProcessor(MailSender mailSender, OrderRepository repository) {
        this.mailSender = mailSender;
        this.repository = repository;
    }

    public void process(Order order){
        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }
}
Nossa classe agora depende de abstrações em vez de implementações concretas. Você pode facilmente alterar seu comportamento injetando a dependência desejada no momento em que a instância é criada OrderProcessor. Vimos SOLID - princípios de design em Java. Mais sobre OOP em geral, o básico dessa linguagem de programação - nada chata e com centenas de horas de prática - no curso JavaRush. É hora de resolver alguns problemas :) Cinco princípios básicos de design de classe (SOLID) em Java - 2
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION