Só porque um sistema é complexo, não significa que você precise complicá-lo . Se você tem uma classe que possui muitas dependências (ou Colaboradores) como esta:
public class MyAwesomeClass {
public class MyAwesomeClass(IDependency1 _d1, IDependency2 _d2, ... , IDependency20 _d20) {
// Assign it all
}
}
... então ficou muito complicado e você realmente não está seguindo o SRP , está? Aposto que, se você anotasse o que MyAwesomeClass
faz em um cartão CRC, ele não caberia em um cartão de índice ou você teria que escrever em letras minúsculas ilegíveis.
O que você tem aqui é que vocês seguiram apenas o Princípio de Segregação de Interface e podem ter levado ao extremo, mas essa é outra história. Você pode argumentar que as dependências são objetos de domínio (o que acontece), no entanto, ter uma classe que lida com 20 objetos de domínio ao mesmo tempo está estendendo um pouco demais.
O TDD fornecerá um bom indicador de quanto uma classe faz. Sem rodeios; se um método de teste possui um código de configuração que leva uma eternidade para ser gravado (mesmo se você refatorar os testes), MyAwesomeClass
provavelmente você tem muitas coisas a fazer.
Então, como você resolve esse enigma? Você move as responsabilidades para outras classes. Existem algumas etapas que você pode executar em uma classe com esse problema:
- Identifique todas as ações (ou responsabilidades) que sua classe realiza com suas dependências.
- Agrupe as ações de acordo com dependências estreitamente relacionadas.
- Redelegate! Ou seja, refatorar cada uma das ações identificadas para novas ou (mais importante) outras classes.
Um exemplo abstrato sobre responsabilidades de refatoração
Vamos C
ser uma classe que tem várias dependências D1
, D2
, D3
, D4
que você precisa para refatorar para usar menos. Quando identificamos quais métodos C
chamam as dependências, podemos fazer uma lista simples:
D1
- performA(D2)
,performB()
D2
- performD(D1)
D3
- performE()
D4
- performF(D3)
Olhando para a lista, podemos ver isso D1
e D2
estamos relacionados entre si, pois a classe precisa deles de alguma forma. Também podemos ver essas D4
necessidades D3
. Portanto, temos dois agrupamentos:
Group 1
- D1
<->D2
Group 2
- D4
->D3
Os agrupamentos são um indicador de que a classe agora tem duas responsabilidades.
Group 1
- Um para manipular a chamada de dois objetos que precisam um do outro. Talvez você possa deixar sua classe C
eliminar a necessidade de lidar com ambas as dependências e deixar um deles cuidar dessas chamadas. Nesse agrupamento, é óbvio que D1
poderia ter uma referência a D2
.
Group 2
- A outra responsabilidade precisa de um objeto para chamar outro. Não consegue D4
lidar D3
com a classe? Provavelmente, podemos eliminar D3
da classe C
deixando D4
fazer as chamadas.
Não tome minha resposta como definida, pois o exemplo é muito abstrato e faz muitas suposições. Tenho certeza de que existem mais maneiras de refatorar isso, mas pelo menos as etapas podem ajudá-lo a obter algum tipo de processo para mover responsabilidades, em vez de dividir as classes.
Editar:
Entre os comentários, @Emmad Karem diz:
"Se sua classe possui 20 parâmetros no construtor, não parece que sua equipe sabe o que é SRP. Se você tem uma classe que faz apenas uma coisa, como tem 20 dependências?" - Eu acho que se você tem uma classe Customer, não é estranho ter 20 parâmetros no construtor.
É verdade que os objetos DAO tendem a ter muitos parâmetros, que você precisa definir em seu construtor, e os parâmetros geralmente são tipos simples, como string. No entanto, no exemplo de uma Customer
classe, você ainda pode agrupar suas propriedades dentro de outras classes para simplificar as coisas. Como ter uma Address
turma com ruas e uma Zipcode
turma que contenha o CEP e também lide com a lógica de negócios, como a validação de dados:
public class Address {
private String street1;
//...
private Zipcode zipcode;
// easy to extend
public bool isValid() {
return zipcode.isValid();
}
}
public class Zipcode {
private string zipcode;
public bool isValid() {
// return regex match that zipcode contains numbers
}
}
Isso é discutido mais adiante na postagem do blog "Nunca, nunca, nunca use String em Java (ou pelo menos com frequência)" . Como alternativa ao uso de construtores ou métodos estáticos para facilitar a criação dos subobjetos, você pode usar um padrão de construtor de fluidos .