Single Responsibility Principle

O princípio de responsabilidade única, como o nome já diz, tem como objetivo assegurar que uma classe tenha apenas uma responsabilidade, ou seja, que ela só execute coisas do seu domínio.

Segundo Robert C. Martin, “Uma classe deve ter apenas um motivo para ser modificada”, então, para uma classe se modificada por apenas um motivo ela deve se tratar de apenas um assunto, ou seja, ter uma única responsabilidade.

Para exemplificar um mal uso desse princípio segue abaixo um simples código que itera uma lista contas para calcular o valor médio das contas. O código funciona normalmente, porém, há várias responsabilidades nessa funcionalidade, como: calcular valor total de uma conta, somar os valores das contas e calcular a média do valor.

BigDecimal valorTotal = BigDecimal.ZERO;

for(Conta conta : contas) {
    for(ItemConta itemConta : conta.getItens()) {
        if(itemConta.getValor() != null) {
             valorTotal = valorTotal.add(itemConta.getValor());
         }
    }
}

BigDecimal quantidadeDeContas = BigDecimal.valueOf(contas.size());
BigDecimal media = valorTotal.divide(quantidadeDeContas);

e como melhorar esse código? Apenas extraindo cada responsabilidade em métodos! Um método para calcular o valor total, onde a conta já sabe qual seu valor e não precisa iterar seus itens, um método que retorna a quantidade e um método que calcula o valor total, dessa forma o método que retorna a média das contas apenas requisita esses valores para os métodos específicos (calcularValorTotal, getQuantidadeDeContas, CalculoUtil.media), pois o método calcularMedia não precisa saber como exercer essas responsabilidade, ele apenas precisa receber essas informações e repassar o valor calculado.

public BigDecimal calcularMedia(List<Conta> contas) {
    final BigDecimal valorTotal = calcularValorTotal(contas);
    final BigDecimal quantidadeDeContas = getQuantidadeDeContas(contas);
    final BigDecimal media = CalculoUtil.media(valorTotal, quantidadeDeContas);

    return media;
}

private BigDecimal calcularValorTotal(List<Conta> contas) {
	 return contas.stream()
                .map(Conta::getValorTotal)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
}

private BigDecimal getQuantidadeDeContas(List<Conta> contas) {
	return BigDecimal.valueOf(contas.size());
}

Com isso, além da melhora da legibilidade as funcionalidades ficam com um menor acoplamento entre elas, pois só exercem uma responsabilidade e consequentemente são mais fácil de serem testadas, pois, só abordam um contexto.

Outro exemplo comum de violação desse princípio são os tradicionais “services”, que originalmente nascem a idéia de ser o ponto central de regras de um domínio, mas com o passar do tempo acaba atuando em diferentes domínios perdendo sua essência de única responsabilidade.

No exemplo abaixo é criado uma classe ContaService, que como o nome sugere, deveria apenas fazer ações sobre o domínio do Conta, porem, há outros métodos nessa classe, por mais que são assuntos muitos relacionados, infringe o único motivo para mudar.

public class ContaService {

    public void salvarContas(List<Conta> contas) {

    }

    public BigDecimal calcularValorTotalConta(Conta conta) {

    }

    public void salvarItemContas(List<ItemConta> itemsConta) {

    }

    public File exportarContasParaExcel(List<Conta> contas) {

    }
}

A implementação para atender esse princípio seria criar uma classe para cada um dos métodos citados anteriormente, onde, cada classe terá apenas a responsabilidade do contexto do método.

O método calcularValorTotal pode ser deslocado para dentro da própria classe conta, onde ele estará dentro do seu domínio, assim também reforçando os conceitos de orientação a objetos, onde um objeto tem que ser responsável pelo seu próprio domínio.

public class Conta {

    public BigDecimal calcularValorTotal() {

    }
}

Por mais que muito relacionado um contexto de ItemConta com Conta, a responsabilidade de uma alteração nos item são dos próprios itens, com isso, surge uma nova classe ItemService.

public class ItemService {

    public void salvar(List<ItemConta> itemsConta) {

    }
}

Similar ao item anterior, o contexto de exportação de uma conta também é muito relacionado, porém por uma separação ainda melhor das responsabilidade a exportação é realidade em uma classe isolada, onde tem uma única responsabilidade de formatar os dados de contas para um arquivo.

public class ContaExportacaoExcelService {

    public File exportar(List<Conta> contas) {

    }
}

E por fim, a classe contaService realiza apenas ações no contexto de Conta, assim possuindo apenas uma responsabilidade.

public class ContaService {

    public void salvar(List<Conta> contas) {

    }
}

Com isso, podemos ver os benefícios de aplicar o princípio de responsabilidade única, onde há sempre uma busca por classes mais coesas, visando uma melhor manutenção por serem compreendidas com mais facilidade, com maior possibilidade de reuso e menor acoplamento.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.