Esclarecer o Princípio da Responsabilidade Única

62

O Princípio de Responsabilidade Única afirma que uma classe deve fazer uma e apenas uma coisa. Alguns casos são bem claros. Outros, porém, são difíceis porque o que parece ser "uma coisa" quando visto em um dado nível de abstração pode ser múltiplo quando visto em um nível inferior. Eu também temo que se o Princípio da Responsabilidade Única for honrado nos níveis mais baixos, um código de ravioli excessivamente dissociado e detalhado, onde mais linhas são gastas criando minúsculas classes para tudo e informações sobre o encanamento do que realmente resolvendo o problema, pode resultar. p>

Como você descreveria o que "uma coisa" significa? Quais são alguns sinais concretos de que uma classe realmente faz mais do que "uma coisa"?

    
por dsimcha 05.11.2010 / 14:34
fonte

11 respostas

50

Eu realmente gosto da maneira como Robert C. Martin (Tio Bob) reafirma o Princípio da Responsabilidade Única (vinculado ao PDF) :

There should never be more than one reason for a class to change

É sutilmente diferente da definição tradicional "deve fazer apenas uma coisa", e eu gosto disso porque força você a mudar a maneira como pensa sobre sua aula. Em vez de pensar em "isso está fazendo uma coisa?", Você pensa no que pode mudar e como essas mudanças afetam sua classe. Então, por exemplo, se o banco de dados muda, sua classe precisa mudar? E se o dispositivo de saída mudar (por exemplo, uma tela, um dispositivo móvel ou uma impressora)? Se a sua turma precisa mudar por causa de mudanças de muitas outras direções, isso é um sinal de que sua turma tem muitas responsabilidades.

No artigo vinculado, o Tio Bob conclui:

The SRP is one of the simplest of the principles, and one of the hardest to get right. Conjoining responsibilities is something that we do naturally. Finding and separating those responsibilities from one another is much of what software design is really about.

    
por 05.11.2010 / 18:27
fonte
17

Eu continuo me perguntando qual problema o SRP está tentando resolver? Quando o SRP me ajuda? Eis o que eu criei:

Você deve refazer a responsabilidade / funcionalidade de uma turma quando:

1) Você duplicou a funcionalidade (DRY)

2) Você acha que seu código precisa de outro nível de abstração para ajudá-lo a entender (KISS)

3) Você descobre que as partes de funcionalidade são entendidas por especialistas do seu domínio como parte de um componente diferente (Linguagem Ubíqua)

Você NÃO deve refazer a responsabilidade de uma turma quando:

1) Não há nenhuma funcionalidade duplicada.

2) A funcionalidade não faz sentido fora do contexto da sua turma. Em outras palavras, sua classe fornece um contexto no qual é mais fácil entender a funcionalidade.

3) Os especialistas do seu domínio não têm um conceito dessa responsabilidade.

Ocorre-me que se o SRP é aplicado de forma ampla, nós trocamos um tipo de complexidade (tentando fazer cara ou coroa de uma classe com muita coisa acontecendo por dentro) com outro tipo (tentando manter todos os colaboradores / níveis de abstração em linha reta, a fim de descobrir o que essas classes realmente fazem).

Em caso de dúvida, deixe de fora! Você sempre pode refatorar mais tarde, quando há um caso claro para isso.

O que você acha?

    
por 29.12.2010 / 20:26
fonte
4
Não sei se existe uma escala objetiva para isso, mas o que daria isso seria os métodos - não tanto o número deles, mas a variedade de suas funções. Eu concordo que você pode levar a decomposição longe demais, mas eu não seguiria nenhuma regra dura e rápida sobre isso.

    
por 05.11.2010 / 14:39
fonte
4

A resposta está na definição

O que você define a responsabilidade de ser, em última análise, lhe dá o limite.

Exemplo:

Eu tenho um componente que tem a responsabilidade de exibir faturas - > Nesse caso, se eu começar a adicionar mais alguma coisa, estou quebrando o princípio.

Se, por outro lado, eu disser responsabilidade das manuseio das faturas - > A adição de várias funções menores (por exemplo, impressão Fatura , atualização Fatura ) está dentro desse limite.

No entanto, se esse módulo começou a lidar com qualquer funcionalidade fora de faturas , ele estaria fora desse limite.

    
por 30.12.2010 / 02:40
fonte
2

Eu sempre vejo isso em dois níveis:

  • Eu me certifico de que meus métodos façam apenas uma coisa e façam bem
  • vejo uma classe como um agrupamento lógico (OO) desses métodos que representam bem uma coisa

Então, algo como um objeto de domínio chamado Dog:

Dog é minha classe, mas os cães podem fazer muitas coisas! Eu poderia ter métodos como walk() , run() e bite(DotNetDeveloper spawnOfBill) (desculpe não pude resistir; p).

Se Dog tornar-se pesado, eu pensaria em como grupos desses métodos podem ser modelados juntos em outra classe, como uma classe Movement , que poderia conter meus métodos walk() e run() .

Não há regras rígidas e rápidas, seu design OO evoluirá com o tempo. Eu tento ir para uma interface clara / API pública, bem como métodos simples que fazem uma coisa e uma coisa bem.

    
por 05.11.2010 / 15:07
fonte
1

Eu vejo mais nas linhas de uma classe só deve representar uma coisa. Para se apropriar do exemplo da @ Karianna, eu tenho a minha Dog class, que possui métodos para walk() , run() e bark() . Não adicionarei métodos para meaow() , squeak() , slither() ou fly() porque essas não são coisas que os cães fazem. São coisas que outros animais fazem, e esses outros animais teriam suas próprias classes para representá-los.

(BTW, se o seu cão faz voar, então provavelmente você deveria parar de jogá-lo pela janela).

    
por 05.11.2010 / 18:57
fonte
1

Uma classe deve fazer uma coisa quando vista em seu próprio nível de abstração. Sem dúvida, fará muitas coisas em um nível menos abstrato. É assim que as classes trabalham para tornar os programas mais fáceis de manter: eles ocultam os detalhes da implementação se você não precisar examiná-los de perto.

Eu uso nomes de classes como teste para isso. Se eu não puder dar a uma classe um nome descritivo razoavelmente curto, ou se esse nome tiver uma palavra como "E", provavelmente estou violando o Princípio de Responsabilidade Única.

Na minha experiência, é mais fácil manter esse princípio nos níveis mais baixos, onde as coisas são mais concretas.

    
por 29.12.2010 / 21:15
fonte
0

É sobre ter um único papel .

Cada classe deve ser retomada por um nome de função. Um papel é, na verdade, um (conjunto de) verbo (s) associado (s) a um contexto.

Por exemplo:

O arquivo fornece o acesso de um arquivo. FileManager gerencia objetos File.

Dados de retenção de recursos para um recurso de um arquivo. ResourceManager reter e fornecer todos os recursos.

Aqui você pode ver que alguns verbos como "gerenciar" implicam um conjunto de outros verbos. Os verbos sozinhos são melhor pensados como funções do que classes, na maioria das vezes. Se o verbo implica muitas ações que têm seu próprio contexto comum, então deve ser uma classe em si.

Assim, a ideia é apenas deixar que você tenha uma idéia simples do que a classe define ao definir uma função única, que pode ser a agregação de várias sub-funções (desempenhadas por objetos membros ou outros objetos).

Costumo criar classes de gerenciador com várias outras classes diferentes. Como uma fábrica, um registro, etc. Veja uma classe de gerente como algum tipo de chefe de grupo, um chefe de orquestra que orienta outros povos a trabalhar juntos para alcançar uma idéia de alto nível. Ele tem um papel, mas implica trabalhar com outros papéis únicos dentro dele. Você também pode ver como uma empresa é organizada: um CEO não é produtivo no nível de produtividade pura, mas se ele não estiver lá, nada poderá funcionar corretamente em conjunto. Esse é o seu papel.

Ao criar, identifique papéis exclusivos. E para cada papel, veja novamente se não pode ser cortado em vários outros papéis. Dessa forma, se você precisar alterar a forma como o seu Gerente constrói objetos, simplesmente mude a Fábrica e leve em conta a paz.

    
por 05.11.2010 / 16:24
fonte
-1

O SRP não trata apenas da divisão de classes, mas também da delegação de funcionalidade.

No exemplo Dog usado acima, não use o SRP como justificativa para ter 3 classes separadas, como DogBarker, DogWalker, etc (baixa coesão). Em vez disso, observe a implementação dos métodos de uma classe e decida se eles "sabem demais". Você ainda pode ter dog.walk (), mas provavelmente o método walk () deve delegar a outra classe os detalhes de como a caminhada é realizada.

Na verdade, estamos permitindo que a classe Dog tenha um motivo para mudar: porque os cães mudam. É claro que, combinando isso com outros princípios do SOLID, você estenderia o Dog para novas funcionalidades ao invés de mudar o Dog (aberto / fechado). E você iria Injetar suas dependências, como IMove e IEat. E, claro, você faria essas interfaces separadas (segregação de interface). O cão só mudaria se encontrássemos um bug ou se o Dogs mudasse fundamentalmente (Liskov Sub, não extenda e remova o comportamento).

O efeito líquido do SOLID é que conseguimos escrever novos códigos com mais frequência do que modificamos o código existente, e isso é uma grande vitória.

    
por 18.07.2013 / 00:03
fonte
-1

Tudo depende da definição de responsabilidade e de como essa definição impactará a manutenção de seu código. Tudo se resume a uma coisa e é assim que o seu design vai ajudá-lo a manter seu código.

E como alguém disse: "Andar na água e projetar software a partir de especificações dadas é fácil, desde que ambos estejam congelados".

Então, se estamos definindo a responsabilidade de maneira mais concreta, então não precisamos mudá-la.

Às vezes, as responsabilidades são claramente óbvias, mas às vezes podem ser sutis e precisamos decidir com discernimento.

Suponha que adicionamos outra responsabilidade à classe Dog chamada catchThief (). Agora, isso pode estar levando a uma responsabilidade diferente adicional. Amanhã, se a maneira como o Cachorro está pegando ladrão tiver que ser mudada pelo Departamento de Polícia, então a classe Cachorro terá que ser mudada. Seria melhor criar outra subclasse neste caso e nomeá-la como ThiefCathcerDog. Mas, em uma visão diferente, se tivermos certeza de que isso não mudará em nenhuma circunstância, ou a maneira como o catchThief foi implementado depende de algum parâmetro externo, então é perfeitamente correto ter essa responsabilidade. Se as responsabilidades não forem extraordinariamente estranhas, então temos que decidir criteriosamente com base no caso de uso.

    
por 02.08.2013 / 03:34
fonte
-1

"Um motivo para mudar" depende de quem está usando o sistema. Certifique-se de usar casos para cada ator e fazer uma lista das alterações mais prováveis. Para cada possível mudança de caso de uso, certifique-se de que apenas uma classe seja afetada por essa alteração. Se você estiver adicionando um caso de uso totalmente novo, certifique-se de que só seria necessário estender uma classe para isso.

    
por 03.07.2016 / 05:02
fonte