Graham Rocher suggests that closures cannot be added to Java because Collection cannot compatibly grow in number of methods (methods like forEach, any, all, etc.), and apparently adding static methods to the Collections class is not a solution in these halcyon days of dependency injection. He does acknowledge that static methods are sometimes useful, but so far he hasn't said where or why. He says that Math.abs is a poor example, because you never need an alternative implementation. However, StrictMath.abs is an alternative implementation (though not used often). Suppose that you wanted to be able to choose whether to use Math or StrictMath's abs implementation. You could make or use an interface that contains the 'abs' method, and make two implementations of it. I know that many blog readers find generics difficult to understand, and some even find anonymous classes tricky, so I'll be accommodating..
interface Abs
{
double abs(double value);
}
final class LaxAbs implements Abs
{
public double abs(double value)
{
return Math.abs(value);
}
}
final class StrictAbs implements Abs
{
public double abs(double value)
{
return StrictMath.abs(value);
}
}
Graeme's argument is that, because substitutability is required, 'forEach' and friends should not be static, so that their implementation can be provided through dependency injection.
It can be seen from the above example that, given static methods, it is possible to make versions that are DI-compatible, so the static implementation can then be ignored (as it is wrapped).
Then, why can't the implementation of forEach be static? For the users who just want to use the implementation given in the SDK, Collections.forEach is fine. For those who want to be able to substitute it for others, wrapping it, or using a wrapped version provided by the SDK, is trivial.
I'd suggest that you can safely implement any algorithm as static, and, if there is a need, you can provide a non-static way of accessing it.
2 comments:
Hi Ricky,
I'm still not sure you're understanding the difference between a core API and a user defined one.
Take for example java.util.List. This object is used all over the place as say as a JavaBean property and so on. I can have something like this in my bean:
private List myList;
public void setMyList(List list) {
this.myList = list;
}
What this means is that for someone else to come along and replace the internal implementation used here it is simply a matter of setting a property that implements List.
Hibernate for example uses this to perform lazy loading. It will assign a PersistentList to an otherwise normal JavaBean. In this sense Hibernate is unobtrusive because it does'nt require you to bind your code to a particular framework.
This is just one example, there are many other frameworks like Grails, XFire, insert x framework here that use techniques like this to unobtrusively add functionality to a JavaBean without the User's domain model knowing any better.
Now what you're saying is that you can "wrap" the static method. The problem with this is you're defining a none-core API. So if a framework developer wants to hook into your Pojo he has to implement *your* special interface.
There is a fundamental difference between *user* defined APIs and core APIs like those in the JDK. You *cannot* have core APIs that are not interface based because it means users will *wrap* the API which them means you as a framework developer have a million wrapper classes to implement or even worse have to force your users to implement said interface.
Imagine if the Hibernate guys came along and, yeh because the List stuff in Java is static you have to implement the lazy loading stuff yourself for your wrapper class? No one would use it! And things like Hibernate and Spring would not exist if everything was static.
In terms of IoC and Dependency Injection. Their goals are roughly the same, but they differ in usage. In IoC as it was originally defined you use a factory class so this would be equivalent to doing this in Spring:
Something s = (Something)ctx.getBean("mySomething")
This method however is flawed as it binds you to the factory class and the name of the bean. Dependency injection on the other hand will "inject" s into your instance at runtime without your bean being aware of it.
It is the contrast between JNDI and the new @Resource annotation in JEE
Hi Graeme (I assume),
"Imagine if the Hibernate guys came along and, yeh because the List stuff in Java is static you have to implement the lazy loading stuff yourself for your wrapper class?"
I'm not sure how List could be static. It could have a static factory method to construct it, like the Collections.synchronizedList stuff. The actual object can't be static, or it wouldn't be an object, it'd be a method.
So the only meaningful way I can look at List and make parts of it static to get anything from that part of your comment is by taking some functionality and removing it from the interface, and making that appear as a static method somewhere.
Let's take toArray() then. Making the implementation of that static does not make it harder to write a lazy-loading implementation of List. It makes it easier.
If you take too much out of the interface, i.e., take necessary things out of the interface, such that the static method has to do something daft like cast the reference to some subinterface to be able to operate, etc., then yes, your point would make sense, but that's pretty warped.
Your point about Hibernate needing mutability is good, that's a limitation of Hibernate, and probably most other persistence frameworks, and is a good reason to use DAO, so that your 'real' objects can be immutable.
I'm not sure what my preferred approach would be - I have a natural dislike for introspection, yet a stronger disdain for boilerplate code. I'll think about it more when I actually have to, probably. ;)
"You *cannot* have core APIs that are not interface based because it means users will *wrap* the API"
That really depends on what the API does. If what it does is a fundamentally simple algorithm that doesn't need substituting for another, ever, then the user won't wrap the API.
To use the tired example, Math.abs really doesn't need wrapping, because it doesn't need to be part of an object in the first place.
Thanks for the clarification about IoC and dependency injection.
Post a Comment