DI impact on OO

Printer-friendly versionPrinter-friendly version
When dependency injection impairs object-orientation


[...] objects are composed of cohesive data and the behavior that uses these data [...]


Flashback: What is object-oriented and what for it has been introduced?
In brief: We have to distinguish between object-oriented programming and object-based programming. While objects in the latter are simply units which may have an identity and attributes / properties, as known from records in procedural programming, the object-oriented concept of an object includes additionally polymorphism, inheritance and information hiding / encapsulation.
Therefore, a unit without polymorphism, inheritance or information hiding / encapsulation can be an object technically (actually a record in object-based programming), but cannot be an object conceptually (in object-oriented programming), which is normally meant to represent a real world object with a state and behavior. Hence, in object-oriented programming objects are composed of cohesive data and the behavior that uses these data as well.
Please note: The use of a programming language that supports object-oriented programming does not ensure that a programming is actually object-oriented.

The conceptual object with the integration of state and behavior in the same unit, has been introduced to allow developers to change the implementation of a unit without having to change other units (clients), as long as the units interface is not altered. Instead of a direct access to the units data, a client communicates with the unit by messages (operation calls, passing parameters and returning values). This may sound like a slight advantage, but it is an absolutely crucial advantage with a huge impact on an applications architecture. In a project with a limited budget (that means all projects), it makes a difference if they have to adapt just one unit or maybe thousends... A consequently applied information hiding / encapsulation gives the developer a powerful lever to trigger behavior and to alter the state of objects indirectly. Without that lever the developer will have to take care of every detail each time again. More reasons for information hiding / encapsulation could be enumerated, but this article is not about the advantages or disadvantages of information hiding / encapsulation. This article simply reminds of the fact that object-oriented programming requires information hiding / encapsulation by definition.


We do not inject domain data.


What has that to do with dependency injection?
Usually we inject services or the like, more generally spoken we inject stateless objects. Stateless means here the objects have no domain state (such as domain values), but may have a purely technical state (such as dependencies to other stateless objects). These stateless objects are a set of procedures or functions. To work on domain data, the stateless objects must receive the data as actual parameters for their procedures or functions. Therefore these stateless objects cannot represent real world objects, which have their own internal state, and hence cannot be a part of the domain model. The stateless objects aka services, managers, controllers, DAO or the like are procedural / functional code working on separate data structures. This is what we inject. We do not inject domain data.

To extend dependency injection to stateful objects (objects having a domain state) will not work well, since the dependency injection is performed at startup time where we cannot predict which objects will be needed, how many objects will be needed and with which state these objects will be needed by operations which use them later on. Even if the dependency injection would happen just in time when the dependency is needed, we still have to predict the number and the state of these objects beforehand (e.g. in the configuration of the dependency injection framework in use), since the injection necessarily happens before the receiving object can use the injected object. Due to this obviously necessary order, the dependency injection is based on static information about relationships between classes at compile time and is not based on dynamically determined information about relationships between objects at runtime. This could be fixed partially with some uggly extra efforts based on lazy loading proxies with individual factories etc., just to allow to pass some actual parameters to the container / injector, but still leaves us in the dark when it comes to the allocation of more instances dynamically inside a clients operation. In this context see also the approach of the "AssistedInject" in Google's Guice. Moreover, the object receiving a dependency injection has to keep the dependency as an instance variable in the heap instead of pushing it onto the stack just when it is really needed.
This is independent from the concrete dependency injection framework (with partial exceptions in Google's Guice) and a consequence of the dependency injection pattern itself.
To set the initial state into an already injected object is not an option at all. The injected object will be at least temporarily in an invalid state. And the next developer who works on that code might not know about that initial state passing...
Somebody might think, why not querying the dependency injection container just in time and same time passing the actual parameters? Indeed, dependency injection containers usually provide to pull an dependency from the container by the client itself. This approach could make sense possibly, but it is not dependency injection - dependency injection is injection! It means using a dependency injection container for something that is anything else, but not dependency injection. If we have to pull all dependencies, then what is the point in using that dependency injection container anyway? Moreover, to refer to a dependency, dependency injection frameworks typically force us to use plain Strings as parameters. Maybe not the best idea considering refactorings...