My impression is that projects using a DI framework like Spring or Guice tend to lose their object orientation and degenerate to a purely procedural design:

DI not only centralizes the question of which implementation of an object to use but also manages the life-cycle of these objects. Many OO design patterns, however, rely on the ability of business logic to wire objects. I wouldn't know, for example, how to implement a Strategy Pattern in Spring because the decision which of the concrete strategies to use, is statically determined by the configuration of the application instead of a piece of code. Same goes for decorator, composite, observer, ...

The reason above leads to a design where functionality and data are separated. Because you don't want to let the DI container decide when data is created, you split any code off the data in order to have only this part managed by the DI container. This is contrary to the idea of OO where code and data should reside together in one unit. This breaks the encapsulation of data because all fields of the data "beans" are exposed by public getters and setters.

You can't use polymorphism anymore because code is not linked to its data anymore and the "virtual functions" can't work. This leads to those instanceof cascades that we all know we shouldn't use. Also, we lose all those designs that rely on polymorphism.

The DI container will inject managed beans only in objects which are also managed by it. So you can't mix managed beans and normal objects because there can't be any "non-managed gaps" in the reference chain of managed beans. So once you started with DI, you need to put all other code under the control of the DI container and you can't use normal objects anymore. I suppose that this is the reason why the separation of code and data is done so rigorously in DI projects.

I see that there are reasons why to use DI but is it really worth giving up so much? Don't people care about OO? Apart from this article, I can't find any discussions on this topic.

Or is it just me who doesn't understand how to do this properly? Any ideas how the four points above can be tackled with?

Appendix 1: Explanation of strategy pattern

I refer to the example here. Let's assume there's a DI version of it where all implementations of Strategy and the Context are injected to StrategyExample. Then StrategyExample doesn't decide anymore which Strategy to inject into Context but the configuration would have already decided which implementation to inject into Context. So yes, DI heavily applies the Strategy Pattern. But it always does so statically.

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

You are correct that you've found a duplicate question, because you (and the author of the article you linked to) are both confusing OO with Rich Domain. They're not the same thing. An anti-OO design pattern is Transaction Script, not Anaemic Domain.
–
pdrJan 19 '13 at 19:11

"Rich Domain" appears to me as a name for normal classes invented by people who are obsessed with EJB or Spring and don't want to admit that they drifted into the procedural world and are not programming OO anymore.
–
WolfgangJan 22 '13 at 15:54

2 Answers
2

DI not only centralizes the question of which implementation of an
object to use but also manages the life-cycle of these objects. Many
OO design patterns, however, rely on the ability of business logic to
wire objects. I wouldn't know, for example, how to implement a
Strategy Pattern in Spring because the decision which of the concrete
strategies to use, is statically determined by the configuration of
the application instead of a piece of code. Same goes for decorator,
composite, observer, ...

That's simply not true. I think strategy is a prime example for one pattern that actually benefits from DI and is by itself (with or without container) the prototype of DI. You can export as many strategies as you like and pick the one that you want in your business logic. Also, no one, I repeat nothing and no one stops you from instantiating a known strategy by yourself to override the strategy that was injected.

The reason above leads to a design where functionality and data are
separated. Because you don't want to let the DI container decide when
data is created, you split any code off the data in order to have only
this part managed by the DI container. This is contrary to the idea of
OO where code and data should reside together in one unit. This breaks
the encapsulation of data because all fields of the data "beans" are
exposed by public getters and setters.

I see your point but you can easily resolve this by using a factory pattern. In the simplest way, you can instantiate your object yourself and then have it built up and wired by the factory (calling and hiding the container's implementation).

You can't use polymorphism anymore because code is not linked to its
data anymore and the "virtual functions" can't work. This leads to
those instanceof cascades that we all know we shouldn't use. Also, we
lose all those designs that rely on polymorphism.

I totally don't see your point here. Dependency Injection done right is a really good example for the power of polymorphism by itself.

The DI container will inject managed beans only in objects which are
also managed by it. So you can't mix managed beans and normal objects
because there can't be any "non-managed gaps" in the reference chain
of managed beans. So once you started with DI, you need to put all
other code under the control of the DI container and you can't use
normal objects anymore. I suppose that this is the reason why the
separation of code and data is done so rigorously in DI projects.

Yes, but this can easily be resolved with a factory approach. You totally can build software that runs with or without a DI container by using a simple factory.

Re. 1: What you describe is not the strategy pattern. Re. 2: My observation is that DI projects don't use such factories but use code-only and data-only classes. It's not seen as a bad thing by people who advocate DI. Re 3: I was interested in how this "done right" actually looks like :). Re 4: If such factories are able to inject managed objects to the new object they create, they cause the problem I described. If they don't inject managed objects, they are off-topic.
–
WolfgangJan 19 '13 at 17:23

@Wolfang: Why don't I describe the strategy pattern? See also this thread: programmers.stackexchange.com/questions/135914/… Also, I think such factories can hide the DI and create the objects in another way and do not create the problem you described!
–
FalconJan 19 '13 at 17:28

I've added an explanation to the question. Was too long for the comments here.
–
WolfgangJan 19 '13 at 17:46

@Wolfang: I still honestly cannot see what keeps you from changing the strategy. You could also inject a list of available strategies and choose at runtime, and if you'd like to, you could maintain state (not that I like state, but... hey!)
–
FalconJan 19 '13 at 18:29

I don't think DI throws OO principles out the window; in may ways, DI is dependent on them.

I'll keep this answer generic, since most DI frameworks borrow very heavily from one another. The concepts are similar, though some frameworks have features that others don't. I will assume, however, that the framework accepts external configuration that is read at runtime (like XML or properties file(s)), possibly in addition to compile-time configuration (like attributes).

In a DI world, you become more heavily dependent on interfaces and composition. For example, say your application needs to get a price of a product, and that price might come from a CSV file, or a database, or a REST service. You can define the interface as something like

A decent DI system would be able to introspect the property types of this object, and call the setters with the appropriate objects. When an object is first referenced, the framework looks up all of the depended-upon objects, and calls the object's init() method if it has one. For example, a property file based configuration (just to make the example simpler) may look like:

So at runtime, the configuration can be changed without having to recompile anything. The framework knows that the FilePricingService is expecting priceFile t be a File, so it invokes its own converters to automagically create a File from the filename that was provided. It knows that Parser is not a simple type, so it assumes this is a reference to another DI-managed component, and looks up that component. This is especially handy if you don't have the source to the components you want to use. Extending the example:

etc. The idea is that wherever something needs a PricingService, it doesn't have to care what the implementation class is. Say you had a requirement drop on you that says "pick the lowest price from two different CSV files and the REST service". The only additional code you'd have to write is a composition class that uses these other existing pricing services:

and tell the application to use that FilesAndRESTPricingService in its configuration.

This system is dependent on many OO priciples. Polymorphism/inheritance, encapsulation, etc., are all in use here. Java makes some of this a lot more verbose, with boilerplate getters and setters everywhere, but that's a deficiency of the language, not of the DI pattern. (Imagine a Java with macros that allow you to say { property File theFile; } which would create the getter/setter code).

The benefit of using a DI system like this is the framework handles a lot of the configuration for you. A good DI system would also provide a way for you to inspect the runtime configuration of the objects via some UI. At runtime, if you needed to change the list of PricingServices, for example, the framework UI should be able to show you all of the registered implementations, and let you change their dependencies on the fly.

Systems like this exist, and have existed for over a decade, in real-world production settings.

You're describing how a DI framework works, not how the problems I describe can be solved. I don't challenge that a DI framework internally uses OOP. I challenge that a program that uses a DI framework can be OO. But you make a good point that Java is not a suitable language for DI-based systems. Which supports my point that it's not intended for DI, but for OO.
–
WolfgangJan 19 '13 at 17:29