Hoje eu tive uma discussão com alguém.
Eu estava explicando os benefícios de ter um modelo de domínio rico em oposição a um modelo de domínio anêmico. E eu demonstrei meu argumento com uma classe simples assim:
public class Employee
{
public Employee(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastname;
}
public string FirstName { get private set; }
public string LastName { get; private set;}
public int CountPaidDaysOffGranted { get; private set;}
public void AddPaidDaysOffGranted(int numberOfdays)
{
// Do stuff
}
}
Como ele defendeu sua abordagem de modelo anêmico, um de seus argumentos foi: "Eu acredito no SOLID . Você está violando o princípio da responsabilidade única (SRP), pois representa os dados e executa a lógica na mesma classe".
Achei essa afirmação realmente surpreendente, pois, seguindo esse raciocínio, qualquer classe com uma propriedade e um método viola o SRP e, portanto, OOP em geral não é SÓLIDO, e a programação funcional é o único caminho para o céu.
Decidi não responder aos seus muitos argumentos, mas estou curioso para saber o que a comunidade pensa sobre essa questão.
Se eu tivesse respondido, começaria apontando o paradoxo mencionado acima e depois indicaria que o SRP é altamente dependente do nível de granularidade que você deseja considerar e que, se você for suficientemente longe, qualquer classe que contenha mais de um propriedade ou um método a viola.
O que você teria dito?
Atualização: O exemplo foi generosamente atualizado por guntbert para tornar o método mais realista e nos ajudar a focar na discussão subjacente.
fonte
Respostas:
A responsabilidade única deve ser entendida como uma abstração de tarefas lógicas no seu sistema. Uma classe deve ter a única responsabilidade de (fazer todo o necessário para) executar uma única tarefa específica. Isso pode realmente trazer muito para uma classe bem projetada, dependendo da responsabilidade. A classe que executa seu mecanismo de script, por exemplo, pode ter muitos métodos e dados envolvidos no processamento de scripts.
Seu colega de trabalho está se concentrando na coisa errada. A questão não é "que membros essa classe tem?" mas "que operações úteis essa classe executa dentro do programa?" Uma vez que isso seja entendido, seu modelo de domínio ficará bem.
fonte
O Princípio de Responsabilidade Única se preocupa apenas se um pedaço de código (no OOP, normalmente estamos falando de classes) tem responsabilidade sobre um pedaço de funcionalidade . Acho que seu amigo dizendo que funções e dados não podem se misturar realmente não entendeu essa ideia. Se
Employee
também contivéssemos informações sobre o local de trabalho, a velocidade do carro e que tipo de comida o cachorro come, teríamos um problema.Como essa classe lida apenas com uma
Employee
, acho justo dizer que não viola o SRP descaradamente, mas as pessoas sempre terão suas próprias opiniões.Um lugar em que podemos melhorar é separar as informações do funcionário (como nome, número de telefone, email) das férias dele. Na minha opinião, isso não significa que métodos e dados não possam ser combinados , apenas significa que talvez a funcionalidade de férias possa estar em um local separado.
fonte
Para minha mente esta classe poderia violar SRP se continuasse a representar tanto um
Employee
eEmployeeHolidays
.Do jeito que está, e se fosse para a Revisão por pares, eu provavelmente deixaria passar. Se mais propriedades e métodos específicos de Funcionários forem adicionados, e mais propriedades específicas de feriados forem adicionadas, eu provavelmente recomendaria uma divisão, citando o SRP e o ISP.
fonte
Já existem ótimas respostas apontando que o SRP é um conceito abstrato sobre funcionalidade lógica, mas há pontos mais sutis que acho que vale a pena acrescentar.
As duas primeiras letras no SOLID, SRP e OCP são sobre como o seu código muda em resposta a uma alteração nos requisitos. Minha definição favorita do SRP é: "um módulo / classe / função deve ter apenas um motivo para mudar". Argumentar sobre os motivos prováveis para a alteração do seu código é muito mais produtivo do que discutir se o seu código é SOLID ou não.
Quantas razões a sua classe Employee precisa mudar? Não sei, porque não conheço o contexto em que você o está usando e também não consigo ver o futuro. O que posso fazer é debater possíveis alterações com base no que vi no passado, e você pode avaliar subjetivamente a probabilidade delas. Se mais de uma pontuação entre "razoavelmente provável" e "meu código já tiver sido alterado exatamente por esse motivo", você estará violando o SRP contra esse tipo de alteração. Aqui está um: alguém com mais de dois nomes entra na sua empresa (ou um arquiteto lê este excelente artigo do W3C ). Aqui está outra: sua empresa muda a maneira como aloca dias de férias.
Observe que esses motivos são igualmente válidos, mesmo se você remover o método AddHolidays. Muitos modelos de domínio anêmico violam o SRP. Muitos deles são apenas tabelas de banco de dados em código, e é muito comum que as tabelas de banco de dados tenham mais de 20 motivos para mudar.
Aqui está algo para se pensar: sua classe Employee mudaria se o seu sistema precisasse acompanhar os salários dos funcionários? Endereços? Informações de contato de emergência? Se você disser "sim" (e "provavelmente acontecerá") a dois deles, sua classe estará violando o SRP, mesmo que ainda não tenha um código! O SOLID é sobre processos e arquitetura, tanto quanto sobre código.
fonte
O fato de a classe representar dados não é de responsabilidade da classe, é um detalhe de implementação particular.
A classe tem uma responsabilidade, representar um funcionário. Nesse contexto, isso significa que ela apresenta alguma API pública que fornece a funcionalidade necessária para você lidar com funcionários (se AddHolidays é um bom exemplo, é discutível).
A implementação é interna; acontece que isso precisa de algumas variáveis privadas e alguma lógica. Isso não significa que a classe agora tenha múltiplas responsabilidades.
fonte
A idéia de que misturar lógica e dados de qualquer maneira está sempre errada é tão ridícula que nem merece discussão. No entanto, há de fato uma violação clara de responsabilidade única no exemplo, mas não é porque haja uma propriedade
DaysOfHolidays
e uma funçãoAddHolidays(int)
.É porque a identidade do funcionário está misturada com o gerenciamento de férias, o que é ruim. A identidade do funcionário é algo complexo que é necessário para rastrear férias, salário, horas extras, para representar quem está em qual equipe, para vincular a relatórios de desempenho etc. Um funcionário também pode alterar o nome, o sobrenome ou ambos, e permanecer o mesmo empregado. Os funcionários podem até ter várias grafias de nome, como ASCII e unicode. As pessoas podem ter 0 a n primeiro e / ou sobrenome. Eles podem ter nomes diferentes em jurisdições diferentes. O rastreamento da identidade de um funcionário é uma responsabilidade suficiente para que o gerenciamento de férias ou de férias não possa ser acrescentado sem chamá-lo de uma segunda responsabilidade.
fonte
Como os outros, eu discordo disso.
Eu diria que o SRP é violado se você estiver executando mais de uma peça de lógica na classe. A quantidade de dados que precisa ser armazenada na classe para alcançar essa única peça de lógica é irrelevante.
fonte
Atualmente, não acho útil debater sobre o que constitui e o que não constitui uma única responsabilidade ou uma única razão para mudar. Eu proporia um Princípio Mínimo de Luto em seu lugar:
Como é isso? Não é necessário que um cientista de foguetes descubra por que isso pode ajudar a reduzir os custos de manutenção e, esperançosamente, não deve ser um ponto de debate sem fim, mas, como no SOLID em geral, não é algo para aplicar às cegas em todos os lugares. É algo a considerar ao equilibrar trade-offs.
Quanto à probabilidade de exigir alterações, isso diminui com:
Quanto à dificuldade de fazer alterações, ela aumenta com os acoplamentos eferentes. O teste introduz acoplamentos eferentes, mas melhora a confiabilidade. Feito bem, geralmente faz mais bem do que mal e é totalmente aceitável e promovido pelo Princípio Mínimo de Luto.
E o princípio Make Badass está vinculado ao princípio de luto mínimo, uma vez que as coisas mais duras encontrarão uma menor probabilidade de exigir mudanças do que as que são péssimas no que fazem.
Do ponto de vista do SRP, uma classe que quase nada faz certamente teria apenas um (às vezes zero) motivos para mudar:
Isso praticamente não tem motivos para mudar! É melhor que o SRP. É o ZRP!
Exceto que eu sugeriria uma violação flagrante do princípio Make Badass. Também é absolutamente inútil. Algo que faz tão pouco não pode esperar ser durão. Tem muito pouca informação (TLI). E, naturalmente, quando você tem algo que é TLI, ele não pode fazer nada realmente significativo, nem mesmo com as informações que encapsula; portanto, deve vazá-las para o mundo exterior, na esperança de que outra pessoa faça algo significativo e durão. E esse vazamento é bom para algo que apenas agrega dados e nada mais, mas esse limite é a diferença que eu vejo entre "dados" e "objetos".
Claro que algo que é TMI também é ruim. Podemos colocar todo o software em uma classe. Pode até ter apenas um
run
método. E alguém pode até argumentar que agora há um motivo muito amplo para mudar: "Essa classe só precisará ser alterada se o software precisar de melhorias". Estou sendo bobo, mas é claro que podemos imaginar todos os problemas de manutenção com isso.Portanto, há um ato de equilíbrio quanto à granularidade ou grosseria dos objetos que você cria. Costumo julgá-lo pela quantidade de informações que você deve vazar para o mundo exterior e pela funcionalidade significativa que ela pode executar. Costumo achar o princípio Make Badass útil para encontrar o equilíbrio ao combiná-lo com o princípio do luto mínimo.
fonte
Pelo contrário, para mim, o modelo de domínio anêmico quebra alguns dos principais conceitos de POO (que unem atributos e comportamento), mas pode ser inevitável com base em escolhas arquitetônicas. Os domínios anêmicos são mais fáceis de pensar, menos orgânicos e mais seqüenciais.
Muitos sistemas tendem a fazer isso quando várias camadas precisam jogar com os mesmos dados (camada de serviço, camada da web, camada do cliente, agentes ...).
É mais fácil definir a estrutura de dados em um local e o comportamento em outras classes de serviço. Se a mesma classe foi usada em várias camadas, essa pode aumentar e faz a pergunta de qual camada é responsável por definir o comportamento de que precisa e quem é capaz de chamar os métodos.
Por exemplo, pode não ser uma boa ideia que um processo do Agente que calcule estatísticas de todos os seus funcionários possa chamar uma computação por dias pagos. E a GUI da lista de funcionários certamente não precisa de todo o novo cálculo de ID agregado usado nesse agente estatístico (e os dados técnicos que o acompanham). Quando você separa os métodos dessa maneira, geralmente termina com uma classe com apenas estruturas de dados.
Você pode facilmente serializar / desserializar os dados do "objeto", ou apenas alguns deles, ou para outro formato (json) ... sem se preocupar com qualquer conceito / responsabilidade do objeto. É apenas a passagem de dados embora. Você sempre pode fazer o mapeamento de dados entre duas classes (Employee, EmployeeVO, EmployeeStatistic ...), mas o que Employee realmente significa aqui?
Portanto, sim, ele separa completamente os dados nas classes de domínio e o tratamento de dados nas classes de serviço, mas é necessário aqui. Esse sistema é ao mesmo tempo funcional para gerar valor comercial e também técnico para propagar os dados em todos os lugares necessários, mantendo um escopo de responsabilidade adequado (e o objeto distribuído também não resolve isso).
Se você não precisar separar os escopos de comportamento, estará mais livre para colocar os métodos nas classes de serviço ou nas classes de domínio, dependendo de como vê seu objeto. Tendo a ver um objeto como o conceito "real", isso naturalmente ajuda a manter o SRP. Assim, no seu exemplo, é mais realista do que o salário do chefe do funcionário acrescente o dia de pagamento concedido à sua PayDayAccount. Um funcionário é contratado pela empresa, Works, pode ficar doente ou pedir um conselho, e ele tem uma conta no Payday (o chefe pode recuperá-la diretamente dele ou de um registro do PayDayAccount ...) Mas você pode criar um atalho agregado aqui por simplicidade, se você não quiser muita complexidade para um software simples.
fonte
Parece muito razoável para mim. O modelo pode não ter propriedades públicas se expor ações. É basicamente uma ideia de Separação de Consulta de Comando. Observe que o Command terá um estado privado, com certeza.
fonte
Você não pode violar o Princípio da Responsabilidade Única, porque é apenas um critério estético, não uma regra da Natureza. Não se deixe enganar pelo nome que soa cientificamente e pelas letras maiúsculas.
fonte