O princípio da responsabilidade única é definido na wikipedia como
O princípio de responsabilidade única é um princípio de programação de computadores que afirma que todo módulo, classe ou função deve ter responsabilidade sobre uma única parte da funcionalidade fornecida pelo software e que a responsabilidade deve ser totalmente encapsulada pela classe
Se uma classe deve ter apenas uma responsabilidade, como pode ter mais de um método? Cada método não teria uma responsabilidade diferente, o que significaria que a classe teria mais de uma responsabilidade.
Todos os exemplos que vi demonstrando o princípio de responsabilidade única usam uma classe de exemplo que possui apenas um método. Pode ser útil ver um exemplo ou ter uma explicação de uma classe com vários métodos que ainda podem ser considerados como tendo uma responsabilidade.
fonte
Respostas:
A responsabilidade única pode não ser algo que uma única função possa cumprir.
Esta classe pode violar o princípio da responsabilidade única. Não porque tem duas funções, mas se o código
getX()
egetY()
precisa satisfazer diferentes partes interessadas que podem exigir mudanças. Se o vice-presidente Sr. X enviar um memorando de que todos os números serão expressos como números de ponto flutuante e a diretora de contabilidade, a Sra. Y, insistir em que todos os números que suas revisões de departamento permanecerão inteiros, independentemente do que o Sr. X pense bem, então é melhor que essa classe tenha uma única idéia de quem é responsável, porque as coisas estão prestes a ficar confusas.Se o SRP tivesse sido seguido, seria claro se a classe Location contribui para as coisas às quais o Sr. X e seu grupo estão expostos. Deixe claro o que a classe é responsável e você sabe qual diretiva afeta essa classe. Se ambos impactam essa classe, ela foi mal projetada para minimizar o impacto da mudança. "Uma turma deve ter apenas um motivo para mudar" não significa que toda a turma possa fazer apenas uma coisinha. Isso significa que eu não deveria poder olhar para a classe e dizer que o Sr. X e a Sra. Y têm interesse nessa classe.
Além de coisas assim. Não, vários métodos estão bem. Apenas dê um nome que deixe claro quais métodos pertencem à classe e quais não.
O SRP do tio Bob é mais sobre a lei de Conway do que sobre a lei de Curly . O tio Bob defende a aplicação da lei de Curly (faça uma coisa) a funções e não a classes. O SRP adverte contra os motivos de mistura para alterar juntos. A lei de Conway diz que o sistema seguirá como as informações de uma organização fluem. Isso leva a seguir o SRP porque você não se importa com o que nunca ouve.
As pessoas continuam querendo que o SRP tenha todos os motivos para limitar o escopo. Há mais motivos para limitar o escopo do que o SRP. Limito ainda mais o escopo, insistindo que a classe seja uma abstração que pode ter um nome que garante que olhar para dentro não o surpreenda .
Você pode aplicar a Lei de Curly às aulas. Você está fora do que o tio Bob fala, mas pode fazê-lo. O erro é quando você começa a pensar que isso significa uma função. É como pensar que uma família deveria ter apenas um filho. Ter mais de um filho não impede que seja uma família.
Se você aplicar a lei de Curly a uma classe, tudo na classe deve ser sobre uma única idéia unificadora. Essa ideia pode ser ampla. A ideia pode ser persistência. Se algumas funções do utilitário de registro estiverem lá, elas estarão claramente fora do lugar. Não importa se o Sr. X é o único que se importa com esse código.
O princípio clássico a ser aplicado aqui é chamado de Separação de Preocupações . Se você separar todas as suas preocupações, pode-se argumentar que o que resta em qualquer lugar é uma preocupação. Foi assim que chamamos essa idéia antes do filme de 1991, City Slickers, que nos apresentou o personagem Curly.
Isto é bom. Só que o que o tio Bob chama de responsabilidade não é uma preocupação. Uma responsabilidade para ele não é algo em que você se concentra. É algo que pode forçá-lo a mudar. Você pode se concentrar em uma preocupação e ainda criar um código responsável por diferentes grupos de pessoas com diferentes agendas.
Talvez você não se importe com isso. Bem. Pensar que segurar para "fazer uma coisa" resolverá todos os problemas de seu design mostra uma falta de imaginação do que "uma coisa" pode acabar sendo. Outro motivo para limitar o escopo é a organização. Você pode aninhar muitas "uma coisa" dentro de outras "uma coisa" até ter uma gaveta de lixo cheia de tudo. Eu já falei sobre isso antes
É claro que o motivo clássico da OOP para limitar o escopo é que a classe possui campos particulares e, em vez de usar getters para compartilhar esses dados, colocamos todos os métodos que precisam desses dados na classe, onde eles podem usar os dados em particular. Muitos acham isso muito restritivo para usar como um limitador de escopo, porque nem todo método que pertence junto usa exatamente os mesmos campos. Eu gosto de garantir que qualquer ideia que reuniu os dados seja a mesma que reuniu os métodos.
A maneira funcional de analisar isso é isso
a.f(x)
ea.g(x)
são simplesmente f a (x) e g a (x). Não são duas funções, mas um continuum de pares de funções que variam juntas. Oa
mesmo não tem de ter dados nela. Pode ser simplesmente como você sabe qualf
eg
implementação você usará. Funções que mudam juntas pertencem juntas. Esse é o bom e velho polimorfismo.O SRP é apenas uma das muitas razões para limitar o escopo. É um bom. Mas não é o único.
fonte
A chave aqui é o escopo ou, se você preferir, a granularidade . Uma parte da funcionalidade representada por uma classe pode ser ainda mais separada em partes da funcionalidade, cada parte sendo um método.
Aqui está um exemplo. Imagine que você precisa criar um CSV a partir de uma sequência. Se você deseja estar em conformidade com a RFC 4180, levaria algum tempo para implementar o algoritmo e lidar com todos os casos extremos.
Fazer isso em um único método resultaria em um código que não será particularmente legível e, especialmente, o método faria várias coisas ao mesmo tempo. Portanto, você o dividirá em vários métodos; por exemplo, um deles pode estar encarregado de gerar o cabeçalho, ou seja, a primeira linha do CSV, enquanto outro método converteria um valor de qualquer tipo em sua representação de string adequada ao formato CSV, enquanto outro determinaria se um o valor precisa ser colocado entre aspas duplas.
Esses métodos têm sua própria responsabilidade. O método que verifica se é necessário adicionar aspas duplas ou não, tem o seu, e o método que gera o cabeçalho, um. Esse é o SRP aplicado aos métodos .
Agora, todos esses métodos têm um objetivo em comum, ou seja, tomar uma sequência e gerar o CSV. Essa é a responsabilidade única da classe .
Pablo H comentou:
De fato. O exemplo de CSV que dei foi idealmente um método público e todos os outros métodos são privados. Um exemplo melhor seria de uma fila, implementada por uma
Queue
classe. Essa classe conteria basicamente dois métodos:push
(também chamadoenqueue
) epop
(também chamadodequeue
).A responsabilidade de
Queue.push
é adicionar um objeto à cauda da fila.A responsabilidade
Queue.pop
é remover um objeto da cabeça da fila e lidar com o caso em que a fila está vazia.A responsabilidade da
Queue
classe é fornecer uma lógica de fila.fonte
Uma função é uma função.
Uma responsabilidade é uma responsabilidade.
Um mecânico tem a responsabilidade de consertar carros, o que envolverá diagnósticos, algumas tarefas simples de manutenção, algum trabalho de reparo real, alguma delegação de tarefas a outros etc.
Uma classe de contêiner (lista, matriz, dicionário, mapa etc.) tem a responsabilidade de armazenar objetos, o que envolve armazená-los, permitir a inserção, fornecer acesso, algum tipo de pedido, etc.
Uma única responsabilidade não significa que há muito pouco código / funcionalidade, significa qualquer funcionalidade que "pertença" sob a mesma responsabilidade.
fonte
Responsabilidade única não significa necessariamente que apenas faz uma coisa.
Tomemos, por exemplo, uma classe de serviço do usuário:
Esta classe possui vários métodos, mas sua responsabilidade é clara. Ele fornece acesso aos registros do usuário no armazenamento de dados. Suas únicas dependências são o modelo de usuário e o armazenamento de dados. É fracamente acoplada e altamente coesa, que é realmente o que o SRP está tentando fazer você pensar.
O SRP não deve ser confundido com o "princípio de segregação da interface" (consulte SOLID ). O princípio de segregação de interface (ISP) diz que interfaces menores e leves são preferíveis a interfaces maiores e mais generalizadas. O Go faz uso pesado do ISP em toda a sua biblioteca padrão:
SRP e ISP certamente estão relacionados, mas um não implica o outro. O ISP está no nível da interface e o SRP está no nível da classe. Se uma classe implementa várias interfaces simples, pode não ter mais apenas uma responsabilidade.
Agradecemos a Luaan por apontar a diferença entre ISP e SRP.
fonte
UserService
eUser
a ser UpperCamelCase, mas os métodosCreate
,Exists
eUpdate
eu teria feito lowerCamelCase.Há um chef em um restaurante. Sua única responsabilidade é cozinhar. No entanto, ele pode cozinhar bifes, batatas, brócolis e centenas de outras coisas. Você contrataria um chef por prato no seu menu? Ou um chef para cada componente de cada prato? Ou um chef que pode cumprir sua única responsabilidade: cozinhar?
Se você pedir que o chef faça a folha de pagamento também, é quando você viola o SRP.
fonte
Contra-exemplo: armazenando estado mutável.
Suponha que você tenha a classe mais simples de todas, cujo único trabalho é armazenar um
int
.Se você estivesse limitado a apenas 1 método, poderia ter um
setState()
, ou agetState()
, a menos que quebre o encapsulamento e tornei
público.Então, claramente, essa responsabilidade única exige ter pelo menos 2 métodos nessa classe. QED.
fonte
Você está interpretando mal o princípio da responsabilidade única.
Responsabilidade única não é igual a um único método. Eles significam coisas diferentes. No desenvolvimento de software, falamos sobre coesão . Funções (métodos) que possuem alta coesão "pertencem" juntas e podem ser contadas como desempenhando uma única responsabilidade.
Cabe ao desenvolvedor projetar o sistema para que o princípio de responsabilidade única seja cumprido. Pode-se ver isso como uma técnica de abstração e, portanto, às vezes é uma questão de opinião. A implementação do princípio de responsabilidade única torna o código mais fácil de testar e mais fácil de entender sua arquitetura e design.
fonte
Muitas vezes, é útil (em qualquer idioma, mas especialmente nos idiomas OO) examinar as coisas e organizá-las do ponto de vista dos dados e não das funções.
Portanto, considere a responsabilidade de uma classe de manter a integridade e fornecer ajuda para usar corretamente os dados que possui. Claramente, isso é mais fácil de fazer se todo o código estiver em uma classe, em vez de se espalhar por várias classes. A adição de dois pontos é mais confiável e o código é mantido com mais facilidade, com um
Point add(Point p)
método noPoint
classe do que em outro lugar.E, em particular, a classe não deve expor nada que possa resultar em dados inconsistentes ou incorretos. Por exemplo, se um
Point
deve estar dentro de um plano (0,0) a (127,127), o construtor e quaisquer métodos que modifiquem ou produzam um novoPoint
terão a responsabilidade de verificar os valores dados e rejeitar quaisquer alterações que violem isso. requerimento. (Geralmente, algo como aPoint
seria imutável, e garantir que não haja maneiras de modificar umPoint
depois que ele foi construído também seria uma responsabilidade da classe)Observe que as camadas aqui são perfeitamente aceitáveis. Você pode ter uma
Point
aula para lidar com pontos individuais e umaPolygon
aula para lidar com um conjunto dePoint
s; elas ainda têm responsabilidades separadas, porquePolygon
delega toda a responsabilidade de lidar com qualquer coisa relacionada exclusivamente aPoint
(como garantir que um ponto tenhax
umy
valor e um ) para aPoint
classe.fonte