Princípios SOLID: Princípio da Responsabilidade Única (SRP)

SOLID Principles

Neste post darei início a uma série que falará sobre os princípios SOLID, os quais são boas práticas vindas de décadas de experiência em engenharia de software.

Estes 5 princípios passaram a ser chamados de SOLID após a popularização dos mesmos por meio do Uncle Bob (os 5 princípios fazem parte do famoso livro “Agile Principles, Patterns and Practices”).

Mas afinal, por que CINCO princípios? Porque SOLID é um acrônimo – americanos adoram acrônimos – onde cada letra corresponde a um princípio:

[S]ingle Responsability Principle
[O]pen/Closed Principle
[L]iskov Substitution Principle
[I]nterface Segregation Principle
[D]ependency Inversion Principle

Estes princípios, quando bem aplicados, ajudam a eliminar os design-smells do nosso código, permitindo maior facilidade de manutenção e extensão.

Neste primeiro post, falarei sobre o SRP, ou Princípio da Responsabilidade Única.

DEFINIÇÃO

Este princípio nada mais é do que uma perspectiva diferente para um dos mais fundamentais princípios da orientação a objetos: a coesão.

Sua definição diz que: 

“Uma classe deve ter somente uma razão para mudar”.

Vamos tentar entender o que isso significa e eventuais problemas causados pela violação deste princípio.

ALGUNS PROBLEMAS

Uma classe com mais de um motivo para mudar possui mais de uma responsabilidade, ou seja, não é coesa. Temos vários problemas com isso:

– Dificuldade de compreensão e, portanto, dificuldade de manutenção.

– Dificuldade de reuso.

– Com responsabilidades entrelaçadas em uma mesma classe, pode ficar difícil alterar uma dessas responsabilidades sem comprometer as outras (rigidez) e pode acabar quebrando outras partes do software (fragilidade).

– Acoplamento alto, ou seja, a classe tem um número excessivo de dependências, e portanto fica mais sujeita a mudanças em decorrência de alterações em outras classes (novamente a fragilidade).

EXEMPLOS COMUNS DE VIOLAÇÃO

Imaginem uma classe de negócio Pedido:

public class Pedido
{
    public void AdicionarProduto(Produto produto, int quantidade) { }
    public decimal CalcularTotal() { }
    public void GerarPlanilhaExcel() { }
}

No exemplo acima, temos uma quebra do SRP de uma forma bem explícita, uma vez que temos responsabilidades que deveriam ser de componentes distintos do software. Enquanto os dois primeiros métodos fazem sentido para o domínio do qual Pedido faz parte, o último está relacionado à exibição de dados em um formato específico, o que faz mais sentido em camadas superiores, como de Aplicação ou de UI.

Em um projeto com várias classes seguindo esse “padrão”, fica difícil – ou impossível – manter a coesão em um nível mais alto: em nível de componentes. Em outras palavras, o software acaba sendo um emaranhado de classes sem um divisão clara de camadas.

De forma mais prática: chega um momento onde fica impossível separar determinadas classes em uma class library devido à referência circular. Também fica complicado fazer o deploy de componentes isolados por haver dependências demais entre eles.

Outros exemplos comuns são: (1) classes que misturam negócio e persistência (Pedido, por exemplo, contém métodos que sabem incluir, alterar e excluir pedidos, fazendo com o que mesmo seja acoplado com classes como SqlConnection ou ainda algum ORM); (2) view models que apresentam regras de negócio (mais sobre view model [aqui]).

Obs.: não vale a pena falar muito das classes “God”, que possuem milhares de linhas de código, métodos gigantescos e um número enorme de dependências, pois isso vocês não fazem, certo? 🙂

UM EXEMPLO MENOS ÓBVIO

Nem sempre é fácil identificar várias responsabilidades em uma mesma classe. Eu diria que na maioria das vezes não é. Aliás, atribuir responsabilidades é uma das principais tarefas de um programador OO.

Mesmo que uma classe de negócio esteja fazendo apenas tarefas relacionadas ao seu domínio, ela pode estar fazendo coisas demais.

Vejamos o seguinte exemplo:


public class Cliente
{
    // dados do cliente, como Nome, CPF, etc.
    // outros métodos

    public decimal CalcularDescontoPara(Venda venda)
    {
        if (venda.FormaDePagamento == FormaDePagamento.AVista)
        {
            if (venda.Total > 2000m)
                return venda.Total * 0.2;
            return venda.Total * 0.1;
        }
        return 0m;
    }
}

Observem acima que o método “CalcularDescontoPara” não manipula nenhum dado da classe Cliente, ou seja, nenhuma informação do cliente é necessária para se determinar o valor do desconto.

Sendo assim, esta classe possui pelo menos duas razões para mudar: uma quando houver alteração na lógica de negócio referente a um Cliente e outra quando houver alguma alteração na lógica de uma Venda.

Certamente, faz mais sentido que este método seja da classe Venda!

CONCLUSÃO

O SRP é um dos princípios mais importantes que existe na orientação a objetos. Quando falamos de responsabilidades e coesão estamos tocando em dois pontos-chave da OO, que nos ajudam a criar classes menores, de mais fácil entendimento, manutenção e reuso.
—————————–
Toda a série:
Princípio da Responsabilidade Única (SRP)
Princípio do Aberto/Fechado (OCP)
Princípio da Substituição de Liskov (LSP)
Princípio da Segregação de Interface (ISP)
Princípio da Inversão de Dependência (DIP)

30 comentários em “Princípios SOLID: Princípio da Responsabilidade Única (SRP)

Adicione o seu

  1. Parabéns, simplesmente fantástico, nunca tinha visto este assunto tão bem explicado!
    Aguardando ansiosamente os próximos POSTS.

    Curtir

  2. Uma pergunta: Quando tenho 2 classes e ambas dependem uma da outra, o que é melhor? Criar uma 3a classe que manipula essas 2?
    Por exemplo: Suponha que a classe Venda tivesse que saber se o cliente é um cliente diferenciado. Assim, o método continuaria no Cliente? Na Venda? Ou seria melhor criar uma classe Desconto onde receberia o Cliente e a Venda?

    Curtir

    1. Olá, Maurício

      Não gosto de associações bidirecionais.. Dão dor de cabeça, deixam o design confuso…Eu tento evitar ao máximo e mesmo quando acontece, eu me esforço para, em algum momento, eliminá-las.

      Supondo que o lado restante seja o do Cliente (Cliente -> Venda), se a venda precisasse de alguma informação do cliente para executar alguma operação, eu passaria essa informação como parâmetro do método (ou todo o Cliente: venda.CalcularDescontoPara(cliente))

      Quanto a uma terceira classe só para calcular o desconto? Sim, seria plausível, principalmente se você tivesse várias formas de cálculo e seu método começasse a ficar cheio de condicionais. Nesse caso, caberia até pensar no padrão Strategy.

      São opções mas, enfim, tudo depende do problema você precisa resolver.

      []s

      Curtir

      1. Minha sugestão seria: Se obviamente toda Venda pertence a um Cliente, a venda deve ter uma referência ao cliente. Para saber se o cliente é diferenciado, a classe Cliente deve possuir uma propriedade nos dê essa informação. Para calcular o desconto, como o Robson disse, provavelmente haveria uma terceira classe que implemente o cálculo, onde passamos a venda como parâmetro. Assim, as classes Cliente e Venda ficam limpas, sem regras de negócio, e os valores seriam manipulados por extensões fora dessas classes, ou aplicando o padrão strategy, caso haja várias formas de cálculo. Fiz um exemplo de cabeça no GitHub, se quiserem dar uma olhada, vejam aqui: https://gist.github.com/jpolvora/4974724 . O que acham ?

        Curtir

      2. Olá Jone!

        Quanto ao seu código, o método AplicaDescontoSeNecessario está olhando para propriedades da Venda o tempo todo e ainda alterando alguns deles. Sendo assim, faz mais sentido que ele fosse um método da Venda. Por que um método de extensão para Venda se o comportamento podia estar nela mesmo?

        Normalmente utilizo extension methods para classes que não posso manipular ou para fazer algum tipo de helper, que não afeta o comportamento da classe estendida. O propósito deles não é de arrancar comportamento das classes. Seguindo o paradigma OO, Cliente e Venda deveriam sim possuir comportamento!

        No mais, ficaria com 2 classes ou criaria um “CalculadorDeDesconto” (usando Strategy) se o cálculo for algo bem possível de mudança. É claro, estamos falando de um exemplo bem “bobo” e incompleto…Depende muito do domínio a ser atendido. Poderia haver mto mais complexidade..

        []s

        Curtir

  3. Legal teu blog, ja ta adicionado no feed do celular!
    Essa frase “..não manipula nenhum dado da classe Cliente” fez eu entender na hora a moral da história, fica facil assim identificar os metodos que estão sobrando.
    Aguardando os próximos post, vou começar a ler os anteriores, nos vemos por ai nos comentários do seu blog ahahha

    Curtir

  4. Caracas Tiozão, quando você fala em classes “God”, só uma coisa vem a minha mente, “ManterLancamento”, hahaha. Gostei do post, aliás tenho recomendado a leitura dos teus posts para a galera aqui. Aquele abraço mané!

    Curtir

  5. Recentemente Robson Castilho assumiu seu affair com o Uncle Bob. (risos)

    Brincadeira Robson, sucesso para você e seu blog!

    Curtir

  6. Parabéns Robson. Já havia lido alguns posts sobre o tema e com certeza seu post veio para somar bastante nesse assunto. Os próximos posts sobre os outros princípios prometem. Vamos ver se conseguimos discutir esses e outros assuntos também no grupo do linkedin!

    Curtir

  7. Ótimo post Robson. Eu sempre fui a favor de regras estarem nos modelos (até pq satisfaz os princípio de OO) ao contrário dos modelos anêmicos. Mas ultimamente estou naquela da “camada de negocio” e nos modelos somente regras simples de tratamento/transformação de alguns dados/enum (quase um DTO mais parrudo). O que vc pensa sobre isso?

    E quando vc diz sobre “classes que misturam negócio e persistência […] acoplado com classes como SqlConnection ou ainda algum ORM”, regras por si só não precisariam de acesso aos dados, mas isso acontece de forma muito corriqueira. No caso vc pensa na resolução deste problema com o IoC ? Ou vc separa e restringe os acessos aos dados em “outro local”?

    Curtir

    1. Olá, JP.
      Obrigado!

      Não sei se entendi muito bem. Seria um modelo anêmico?
      Os perigos disso é a tendência ruim de espalhar regras de negócio por outros cantos da aplicação, inclusive duplicando regras.
      Nos últimos projetos, temos efetivamente trabalhado com domain model e tenho visto grandes benefícios em legibilidade e facilidade de manutenção. Hoje não vejo razões para voltar pro modelo anêmico.

      Qto ao acesso as dados, grande parte da mágica é feito por um ORM. Fazer algo orientado a objetos sem um ORM (e sem misturar persistência nas classes de negócio) é algo inviável. ORM é o grande facilitador da jogada.
      E sim, utilizamos IoC e o padrão Repository.

      De uma forma simplificada:
      DOMAIN MODEL -> objetos de domínio e repositórios (abstração)
      ACESSO A DADOS -> implementação dos repositórios
      Na camada mais alta, normalmente a camada de apresentação, o DI container resolve a dependência dos repositórios, injetando a implementação. Sendo assim a camada de domínio NÃO DEPENDE da camada de acesso a dados.

      Dê uma olhada neste post do Elemar Jr, que ele ilustra exatamente esse inversão de dependência entre dominio e acesso a dados: http://elemarjr.net/2013/02/04/acoplamento-evidente-em-aplicacoes-com-tres-camadas/

      []s e valeu por comentar!

      Curtir

      1. Então, imagine o seguinte cenário básico hipotético: uma classe Processo, com número e ano. Você teria os CRUD´s disso, e regras mais específicas. Vamos especificar somente uma, chamada “exportar”, que faz algumas regras internas de validações, consultas em alguns locais, tratamento de dados e retorna um novo processo filho. Não detalhando nada de regras, no meu caso, teria:

        1) O modelo persistível chamado Processo com as propriedades e talvez algumas regras simples de transformação de dados. Não acessa nenhuma base.

        2) Um classe que represente os negócios desse modelo, vamos chamá-lo de ProcessoManager. Aí dentro teríamos o método exportar com parâmetro o modelo Processo. Essas classes dependem do ORM, que é injetado pelo container DI.

        3) O controlador teria o acesso a ProcessoManager (tbm injetado), repassando os dados de Processo que vieram da tela para a exportação. Ou seja, o controlador repassa o modelo Processo para as regras no ProcessoManager, mas não tem dependência com a camada de dados.

        Ou seja, dessa forma as regras não estão dentro do modelo em sí. Você acha que aparenta bem a primeira imagem do post que Elemar Jr ? Caso sim, há o acoplamento clássico do MVC…

        No seu caso, então vc teria:

        1) O modelo persistível Processo, com as propriedades e o método exportar. Ele depende/herda de uma abstração do repositório, para indicar o acesso aos dados dentro das regras.

        2) Cria a implementação do repositório com o acesso em si (ou altera existente).

        3) O controlador então acessa o modelo Processo (injetado, onde já indica as implementações dos repositórios), chamando o exportar dentro do modelo. Ou seja, o controlador acessa direto as regras dentro do modelo. Não tem dependência com a camada de dados.

        Seria mais ou menos isso?
        Tá confuso?

        Curtir

      2. Eu funciono mais no visual mas vamos ver….

        Só irá parecer com a PRIMEIRA imagem do post (falando apenas de Dominio/Dados) se e somente se algum objeto do domínio depender de algum(ns) objetos da DAL.
        Exemplos: Processo (Dominio) e ProcessoDao (Dados) => public void processo.Salvar() { _processoDao.Salvar(numero, ano); }

        Pra mim não ficou claro o item (2) do seu cenário.

        No meu caso, teria várias coisas diferentes:

        1) Objeto de domínio NENHUM herda de Repositorio…Repositorio é um objeto para abstrair uma coleção de objetos. Não faz sentido um “Cliente” herdar, ou seja, ser um tipo de “ColeçãoDeClientes” ou “CarteiraDeClientes”. Quanto a depender, normalmente também não dependem. A menos que seja um serviço de domínio que precise fazer alguma validação no banco ou obter um objeto diferente no banco..aí ele usa um repositório (abstração).

        Um repositório é que depende do objeto de domínio:
        Cliente cliente = repositorioDeClientes.ObterPor(id);
        IEnumerable clientesAtivos = repositorioDeClientes.ObterAtivos();

        2) e 3)
        Os repositórios são usados pela camada de apresentação. Um controller depende do repositório abstrato (recebendo-o em seu construtor) e o DI Container injeta a implementação adequada nele. A implementação do repositório é que depende diretamente da Session do NHibernate.
        No meu caso, como o “composition root” está no próprio projeto Web ele acaba sendo o componente mais instável, porque há dependência para todas as outras camadas.

        Não ficou claro o “Exportar” para mim, para eu poder falar melhor sobre a existência dele…
        Sei que ficou “capenga” mas conseguiu entender 0,005% ?? 🙂

        (Prometo escrever próximos posts sobre isso, ilustrando com imagens. Assim fica mais fácil de visualizar as dependências.

        []s

        Curtir

  8. Ficou, mas para chegar a um veredito mesmo precisaríamos sentar e desenhar as relações, hehe….

    A ideia do “exportar” era só para ter alguma regra mais complexa na discussão, sem pensar nos CRUD’s porque hoje em dia são resolvidos de forma simples…

    Aguardamos os próximos posts!

    Curtir

  9. Parabéns Robson. Artigo muito bem escrito e explicado. Não consigo imaginar escrever tão bem. Você me autoriza a replicar este artigo? Claro… concedendo os créditos e autoria a você é claro.

    Curtir

Participe! Vamos trocar uma ideia sobre desenvolvimento de software!

Blog no WordPress.com.

Acima ↑