A ways back, the idea of a garbage collected language was tantamount to heresy. "It'll be slow." "It isn't necessary." "How hard is malloc()
?" "Reference counting is all you need." etc.
Nowadays, garbage collection is the norm and there's no going back. By freeing us of concerns about allocating and freeing memory, new development techniques became possible. For me, personally, it was very liberating ... I've always coded in terms of small modules working together. Fifteen years ago I was writing almost-object-oriented code in a heavy duty procedural language (PL/1) ... it was just much, much harder.
Garbage collection liberates me to solve problems in my way ... using lots of small, well understood objects working together. When you have to be concerned about every object creation and destruction, you get miserly about them and before you know it, you have these big, monolithic objects that break all the now accepted rules about separation of concerns. You saw this inside the otherwise stellar NeXTSTEP libraries ... there were quite a few kitchen-sink objects and your main extension point was to subclass an existing class.
I used to be dismayed that the UML sequence diagrams did a really poor job of
illustrating inheritance. It was really hard to properly diagram an object invoking
a method in itself that's implemented in a super-class. Nowadays, I realize that the Amigos were ahead of the curve: aggregation trumps inheritance. And don't think I haven't been thinking about this in terms of Tapestry, which mandates inheritance (you must subclass AbstractComponent, BaseComponent or BasePage). I have been, and some future release of Tapestry will break that requirement.
But the story doesn't end with garbage collection (even if the individual objects do). Garbage collection is the last stage of an object's life cycle, but there's just as much going on at the start of the object's life cycle. That's why component frameworks and dependency injection containers (such as HiveMind, Spring, Picocontainer and Avalon) are so important.
"Why do we need an external descriptor?" "It just isn't necessary." "How hard is it to new Foo()?" Sound familiar?
Once you start thinking in terms of large numbers of objects, and a whole lot of just-in-time object creation and configuration, the question of how to create a new object doesn't change (that's what new
is for) ... but the questions when and who become difficult to tackle. Especially when the when is very dynamic, due to just-in-time instantiation, and the who is unknown, because there are so many places a particular object may be used.
HiveMind didn't spring up out of thin air, it was based on me seeing an awful lot of code, production code, that was either a) inefficient, b) unnecessary or c) incorrect (often due to errors related to class loaders and multi threading). Too often it has been option d) all of the above.
Dependency injection acknowledges an initial state for objects ... the construction state, where objects are created and configured before being put into production. With HiveMind, you get the benefits of simplicity: your services are fundamentally POJOs. But you also get all the life cycle benefits: just-in-time, thread safe construction. Any reasonably sized system is going to need those benefits ... they are a cornerstone of providing the efficiency and robustness your clients and customers require. If you don't get them from a container such as HiveMind, you'll be writing that code yourself. Again and again, introducing bugs all the way.
You're willing to let something else manage the death of your objects because it, the Garbage Collector, can do a better job than you can. Likewise, you should accept that a container, whose only responsibility is to construct and configure your objects, will do a better job of it than your own code. Embrace that fact ... and get back to interesting work!