1

We strictly adhere to inversion of control in our codebase, but that creates hellish constructors (yes, I know that means our classes aren't cohesive enough, this is a work in progress). The thing is, that sometimes those dependencies are quite silly like a DateTime wrapper to enable date mocking, or the logger, or a TaskFactory wrapper which are not directly related to the business of the class and I do not think they require explicit introduction.

Could we ease the load on our ctors by using a service locator for those items which are passed all over the system? What would be the disadvantages?

5
  • Do you make use of repositories to wrap up sets of services which are commonly used together into one interface? I've found that a useful way to deal with IoC constructor parameter hell in the past. Nov 29, 2015 at 12:49
  • I often use service locators for logging, but wouldn't recommend it for other services. Nov 29, 2015 at 13:07
  • @PhilipKendall I think bunching up services to a single repository would be... Inelegant. It's also very much not SRP.
    – Ziv
    Nov 29, 2015 at 14:56
  • If a technique leads to a hellish experience perhaps the technique isn't as good as people say. Personally I think the "inversion of control everywhere" gives "inversion of control where it makes sense" a bad name Nov 29, 2015 at 15:56
  • 2
    Thought experiment: When you need an ordinary type from the Standard Library of your favorite programming language, do you always use a Service Locator to new it up, or do you merely reference the library and new up the type yourself? Dec 1, 2015 at 23:36

4 Answers 4

3

The question is, do you really need the dependencies at that level?

Things like a datetime wrapper can be used in two ways: either you need to get a date to assign to something (the order was placed 'now'), or you need some notion of time to perform an action (get me all the outstanding orders that were placed seven days before 'now'). In the first case, your dependency on the wrapper is absolute because the class needs it. In the second case, you can rework your method so that it takes any datetime and the logic of 'seven days before now' is pushed up, reducing the dependencies in your class.

As for logging, it again depends on your application. If you are logging exceptions you should probably only have the dependency on your logging classes in some top-level code. If you are doing logging as part of an auditing feature, you could have an implementation where events are raised from objects but only logged in one place, again reducing the dependency on the logging.

The main reason to avoid service locators is because they hide away the dependencies of your class. This makes it harder for you to design good classes (SOLID), makes testing them harder and turns what are compile-time errors for constructor-based dependencies into runtime errors for service locator-based classes.

3

I presume that when you say a "DateTime wrapper" what you really mean is a "Clock" interface which can be queried for the DateTime value which humans occasionally perceive as "now".

I too have encountered the problem of having to pass dependencies around a lot, and I have solved it with a universal lightweight adaptation of Domain Driven Design which I call Subject Oriented Programming.

  • I don't use service locators because:
    • A service locator is a mandatory global dependency. That's a bad thing to have. Trust me, you will sooner or later regret having it.
    • A service locator may, as JDT pointed out, defer a compile-time error to a run-time error. And since these errors occur when a system is being wired together, while tests are usually wired differently, these errors cannot be detected with unit testing or integration testing, you have to do end-to-end testing in order to discover them.
  • I also don't use dependency injection frameworks because:
    • They work by magic, and I don't like magic.
    • They embrace silent failure, while I mandate hard failure.
    • They don't have an API that you can call, so you cannot use code completion, you have to know stuff by heart.

The idea behind Subject Oriented Programming is this:

  • Every object ideally belongs to a domain. In this case, it is called a subject of that domain.

  • A domain is the exclusive factory of its subjects. Nobody else may instantiate a subject. This in turn means that all subject constructors are hidden to the outside world. (Package-private in java.)

  • Ideally, subjects are visible to outside code (code outside the namespace or package of their domain) via interfaces, not as concrete classes. (But this is irrelevant to this discussion.)

  • Every subject receives a reference to the domain to which it belongs as its first constructor parameter. (This is analogous to how every method of an object receives the this pointer as its first (hidden) parameter. In other words, the domain is to a subject what the object is to a method.)

  • Every subject that needs to use some service obtains it from its own domain via a property of the domain. (Regular getXYZ(), no map lookup.)

  • The domain may offer a service directly, as in myDomain.getGradientSaturator(), or indirectly, as in myDomain.getSplineDomain().getReticulator().

  • Domains receive their dependencies as constructor parameters.

So:

Nobody needs to query any repository for services, no huge lists of dependencies are passed either to constructors or to factory methods, (except perhaps to constructors of domains, which are rare,) and the availability of all services is guaranteed (so to speak*) by the compiler.

Still, at various places where domain hierarchies are constructed, all necessary services are supplied, so any one of them can be replaced with a mock.

In lack of any better term, I am calling this Subject-Oriented Programming for the time being.

(*) so to speak, unless you do something silly, like pass null to a constructor which expected a GradientSaturator.

1

Dependency injection solves a particular problem, however as you're finding it adds complexity. It's not something you should always do for everything because its 'always good', as it can actually add overhead for certain kinds of maintenance. For instance a change where you add a requirement to use a dependency in a method requires your code to change to both add an injection field to the constructor and the change where you use the dependency itself.

Dependency injection allows you to change this later. For something such as a date, if you do not envisage in any situation that your date-handling system will change (which is pretty much the case for any mature programming language which provides date handling), adding dependency injection for this function has a cost and gives you nothing. So don't do it. If you do you are violating the principle of You Ain't Gonna Need It.

So if you don't need the flexibility DI gives you for some of the services you're DIing, just take them out, use a service locator or even (horror) a direct dependency. Its a judgement: if you later find you actually do need that flexibility, modern code dev platforms will let you add it in without too much pain.

0

What would happen if you decide that Class A needs a different logger (for example) than Class B? Or even harder, if instance 1 of Class A needs a different logger than instance 2 of Class A?

You might not have this requirement now, but in the feature you might have it. Maybe you want specific classes or instances to log into different files or to use completely different logging libraries.

These concerns might also be valid for the other dependencies that you want to obtain from the service locator.

You cannot solve this issue if you are using the Service Locator because the Service Locator limits composability. On the other hand, if you inject your dependencies, you have full power to compose your objects in anyway that you want.

Not the answer you're looking for? Browse other questions tagged or ask your own question.