Just because a system is complex doesn't mean that you have to make it complicated. If you have a class that has too many dependencies (or Collaborators) like this:
public class MyAwesomeClass {
public class MyAwesomeClass(IDependency1 _d1, IDependency2 _d2, ... , IDependency20 _d20) {
// Assign it all
}
}
...then it got way too complicated and you're not really following SRP, are you? I'd bet if you wrote down what MyAwesomeClass
does on a CRC card it wouldn't fit on an index card or you have to write in really tiny illegible letters.
What you have here is that your guys only followed the Interface Segregation Principle instead and may have taken it to an extreme but that is a whole other story. You could argue that the dependencies are domain objects (which happens) however having a class that handles 20 domain objects at the same time is stretching it a bit too far.
TDD will provide you a good indicator of how much a class does. Bluntly put; if a test method has setup code that takes forever to write (even if you refactor the tests) then your MyAwesomeClass
probably has too much things to do.
So how do you solve this conundrum? You move the responsibilities to other classes. There are some steps you can take on a class that has this problem:
- Identify all the actions (or responsibilities) that your class does with it's dependencies.
- Group the actions according to closely related dependencies.
- Redelegate! I.e. refactor each of the identified actions to either new or (more importantly) other classes.
An abstract example on refactoring responsibilities
Let C
be a class that has several dependencies D1
, D2
, D3
, D4
that you need to refactor to use less. When we identify what methods that C
calls on the dependencies we can make a simple list of it:
D1
- performA(D2)
, performB()
D2
- performD(D1)
D3
- performE()
D4
- performF(D3)
Looking at the list we can see that D1
and D2
are related to each other as the class need them together somehow. We can also see that D4
needs D3
. So we have two groupings:
Group 1
- D1
<-> D2
Group 2
- D4
-> D3
The groupings are an indicator that the class now has two responsibilities.
Group 1
- One for handling the calling two objects that need each other. Maybe you can let your class C
eliminate the need of handling both dependencies and leave one of them handling those calls instead. In this grouping, it is obvious that D1
could have a reference to D2
.
Group 2
- The other responsibility needs one object to call another. Can't D4
handle D3
instead of your class? Then we probably can eliminate D3
from the class C
by letting D4
do the calls instead.
Don't take my answer as set in stone as the example is very abstract and makes a lot of assumptions. I'm pretty sure there are more ways to refactor this, but at least the steps might help you get some kind of a process to move responsibilities around instead of splitting classes up.
Edit:
Among the comments @Emmad Karem says:
"If your class has 20 parameters in the constructor, it doesn't sound like your team quite knows what SRP is. If you have a class that does only one thing, how does it have 20 dependencies? " - I think that If you have a Customer class, it is not strange to have 20 parameters in the constructor.
It is true that DAO objects tend to have a lot of parameters, which you have to set in your constructor, and the parameters are usually are simple types such as string. However in the example of a Customer
class, you can still group it's properties inside other classes to make things simpler. Such as having an Address
class with streets and a Zipcode
class that contains the zipcode and will handle business logic such as data validation as well:
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
}
}
This thing is discussed further in the blog post "Never, never, never use String in Java (or atleast often)". As an alternative of using constructors or static methods to make the sub objects easier to create you could use a fluid builder pattern.
ISomething
). IMHO, these approches are much easier to handle than dependency injection and result in more readable code.