4 Managing dependencies

 

This chapter covers

  • Reducing the effect of coupling in the class design
  • Depending on high-level, more stable code
  • Avoiding tightly coupled classes
  • Increasing flexibility and testability with dependency injection

In any software system, classes get together to deliver more extensive behavior. For example, a service class may depend on several repositories and entities to do its job. This means the service is coupled to these other classes.

We’ve discussed the problems of large classes and the advantages of smaller classes. On the one hand, having a class depend on other classes instead of doing everything alone is good. On the other hand, once a class delegates part of its task to another class, it has to “trust” the other class to do its job right. If a developer introduces a bug in an entity, this bug may propagate to the service class and make it break without even touching its code.

That’s why you shouldn’t randomly add more dependencies to a class. Dependency management or, in simpler words, which classes depend on which classes and whether this is good or bad, is critical when maintaining large software systems.

4.1 Separate high-level and low-level code

4.1.1 Design stable code

4.1.2 Interface discovery

4.1.3 When not to separate the higher level from the lower level

4.1.4 Example: The messaging job

4.2 Avoid coupling to details or things you don’t need

4.2.1 Only require or return classes that you own

4.2.2 Example: Replacing the HTTP bot with the chat SDK

4.2.3 Don’t give clients more than they need

4.2.4 Example: The offering list

4.3 Break down classes that depend on too many other classes

4.3.1 Example: Breaking down the MessageSender service

4.4 Inject dependencies, aka dependency injection

4.4.1 Avoid static methods for operations that change the state