Aqui está um problema com que me deparo com frequência: que haja um projeto de loja virtual que tenha uma classe de produto. Desejo adicionar um recurso que permita aos usuários publicar comentários em um produto. Então, eu tenho uma classe Review que faz referência a um produto. Agora eu preciso de um método que lista todas as revisões de um produto. Existem duas possibilidades:
(UMA)
public class Product {
...
public Collection<Review> getReviews() {...}
}
(B)
public class Review {
...
static public Collection<Review> forProduct( Product product ) {...}
}
Olhando para o código, eu escolheria (A): não é estático e não precisa de um parâmetro. No entanto, sinto que (A) viola o Princípio da Responsabilidade Única (SRP) e o Princípio Aberto-Fechado (OCP), enquanto (B) não:
(SRP) Quando quero alterar a maneira como as revisões são coletadas para um produto, preciso alterar a classe do produto. Mas deve haver apenas um motivo para alterar a classe do produto. E isso certamente não são os comentários. Se eu compilar todos os recursos que têm algo a ver com produtos no Produto, em breve eles estarão espalhados.
(OCP) Preciso alterar a classe do produto para estendê-la com esse recurso. Penso que isto viola a parte do princípio "Fechado para mudança". Antes de receber a solicitação do cliente para implementar as revisões, considerei o Produto concluído e o "fechei".
O que é mais importante: seguir os princípios do SOLID ou ter uma interface mais simples?
Ou estou fazendo algo errado aqui?
Resultado
Uau, obrigado por todas as suas ótimas respostas! É difícil escolher uma como resposta oficial.
Deixe-me resumir os principais argumentos das respostas:
- pro (A): OCP não é uma lei e a legibilidade do código também é importante.
- pro (A): o relacionamento da entidade deve ser navegável. Ambas as classes podem conhecer esse relacionamento.
- pro (A) + (B): faça as duas coisas e delegue em (A) a (B) para que o produto tenha menos probabilidade de ser alterado novamente.
- pro (C): coloque métodos finder na terceira classe (serviço) onde não é estático.
- contra (B): impede zombaria nos testes.
Algumas coisas adicionais que minhas faculdades no trabalho contribuíram:
- pro (B): nossa estrutura ORM pode gerar automaticamente o código para (B).
- pro (A): por razões técnicas da nossa estrutura ORM, será necessário alterar a entidade "fechada" em alguns casos, independentemente de onde o localizador for. Portanto, nem sempre eu posso continuar com o SOLID.
- contra (C): muito barulho ;-)
Conclusão
Estou usando os dois (A) + (B) com delegação para o meu projeto atual. Em um ambiente orientado a serviços, no entanto, irei com (C).
Assert(5 = Math.Abs(-5));
Abs()
não é o problema, testar algo que depende disso. Você não tem uma costura para isolar o CUT (Code-Under-Test) dependente para usar uma simulação. Isso significa que você não pode testá-lo como uma unidade atômica e todos os seus testes se tornam testes de integração que testam a lógica da unidade. Uma falha em um teste pode estar na CUT ou no Abs()
(ou seu código dependente) e remove os benefícios de diagnóstico dos testes de unidade.