Reducing dependencies is a fundamental refactoring technique. It addresses the problem of too many dependencies by introducing additional abstractions, where each abstraction in turn is smaller and simpler.

Introduction

The house class from the the previous article depends on several other contributors to do its chillax job: the tea and cup, the knobs and hinge, the display and remote.

The refactoring below will increase the number of classes. New classes are introduced to capture abstractions like cuppa, door and tv. Each of these classes has fewer dependencies.

Code

house.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "cuppa.h"
#include "door.h"
#include "tv.h"

class house
{
public:
  void chillax();

private:
  cuppa cuppa_;
  door door_;
  tv tv_;
};

house.cpp

1
2
3
4
5
6
7
8
9
10
#include "house.h"

void house::chillax() {
  cuppa_.finish();

  door_.open();
  door_.close();

  tv_.switch_on();
}

cuppa.h

1
2
3
4
5
6
7
8
9
10
11
12
#include "cup.h"
#include "tea.h"

class cuppa
{
public:
  void finish();

private:
  tea tea_;
  cup cup_;
};

cuppa.cpp

1
2
3
4
5
6
#include "cuppa.h"

void cuppa::finish() {
  tea_.drink();
  cup_.rinse();
}

door.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "hinge.h"
#include "knob.h"

class door
{
public:
  void open();
  void close();

private:
  hinge hinge_;
  knob knob1_;
  knob knob2_;
};

door.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "door.h"

void door::open() {
  knob1_.turn();
  knob1_.push();
  hinge_.screech();
  knob1_.release();
}

void door::close() {
  knob2_.turn();
  knob2_.push();
  hinge_.screech();
  knob2_.release();
}

tv.h

1
2
3
4
5
6
7
8
9
10
11
12
#include "display.h"
#include "remote.h"

class tv
{
public:
  void switch_on();

private:
  display display_;
  remote remote_;
};

tv.cpp

1
2
3
4
5
6
#include "tv.h"

void tv::switch_on() {
  display_.plug_in();
  remote_.press_button();
}

The refactoring took us from 52 lines of code in 2 files to 89 lines of code in 8 files. The increase in number of files is expected, but the almost doubling in the number of lines of the code is the result of having started with an already simple class with not much code. Even then, it does say a better story, mapping itself better to the original problem description: a person finishes the cup of tea and goes to another room where it switches on the TV.

Obstacles

Here are some obstacles on reducing the dependencies.

Knowledge. First of all is the knowledge that it can be done and how it can be done. This article tries to help with that.

Inertia. People just don’t want to change habits. The two responses you get are on the lines of:

  • “That’s how we’ve written code since forever.”
  • “I like to see all my code together, to be able to see what it’s doing.”. In all fairness there some point to the last comment, it’s all an act of balancing and taste.

The third obstacle is legacy code where concerns are mixed so it’s not easy to identify islands of functionality that could be separated.

The last obstacle is naming. In the initial example I used objects from the physical world. It was easy to identify that the knob and hinge belong to the door. We have words for this. For some problem domains, e.g. networking, again we have words, we know what sockets, addresses, buffers etc. mean.

But in practice it’s difficult to find good names for some new problem domain. The risk is that there are too many classes named likeSomeClassThatHoldsThoseOtherTwoThingsTogether that are not going to make instant sense to another developer. I find that pairing and iteration helps here. If you can find a name that at least the pair of you agree then there is chance is a OK name.

Conclusion

Learning the technique of reducing dependencies is one of the tools that, if applied correctly, can result in a system that’s easier to test and maintain.