Setter Injection and JavaBeans
Setter injection will always get you where you want to be. It also has the advantage of being in line with the standard JavaBean approach of using a default nullary constructor and then setting what is needed. To start, in the context of more modern languages the concept of the standard JavaBeans is laughable. The idea that code like:
Person me = new Person();
me.setFirstName("Matt");
me.setMood("Laughing");
is some kind of win for convention is absurd. It is verbose, and a visual correspondence to an underlying data structure is lost. It is preferable to telescopic constructors with ambiguous arguments, but it doesn't seem to offer enough benefit to justify potential problems (Groovy does offer some nice sugar that addresses this, but this is about Java).
What's the problem?
Nullary constructors lead to objects that are an inconsistent state if they have have required members. Continuing the example above, suppose every person must have a first name. When that object is first created, it does not have a first name until that setter is called. This is almost certainly not an issue in this code since everything is wrapped up in the same stack frame and so nothing should be able to accidentally sneak in between those 2 statements. But it does limit the use of that class into only being operated on in thread safe contexts such as above. The class itself is not inherently thread safe.
The other possible issue is mutability. First names can change, but suppose this Person is being stored in some database where it has a numeric primary key. Mucking around with that id after the object exists could be a recipe for disaster, locking the id would make everything a lot safer.
Spring Cleaning
The simplest example for this problem in a DI container is looking at a typical Spring managed beans. In general most layered applications with persistence will consist of a service layer which interacts with the repository layer. The code with setter injection (with annotations, and javax.inject since I like standards) could be something like (there may be typos since I'm IDE dependent but am not using one, and I'm omitting the @Transactional annotation(s)).
@Service
public class SomeServiceImpl implements SomeService() {
private SomeRepository someRepository;
@Inject //Or on the variable itself
public void setSomeRepository(SomeRepository someRepository) {
this.someRepository = someRepository;
}
@Override
public void updateSomeData(DataStruct struct) {
//Do some magic
someRepository.storeData(struct);
}
}
The possibility of "updateSomeData" being called when the object is in an inconsistent state is more apparent in this type of class where you'd expect calls from multiple clients. But...Spring takes care of that for you by wiring all of your beans together on start-up. But this does become an issue when the bean is used outside of the context of Spring. One of the pursuits of frameworks like Spring is to allow your code to remain decoupled from the underlying framework, but the code above is operating under a potentially fatal presumption.
Constructor Version
@Service
@Transactional
public class SomeServiceImpl implements SomeService() {
private final SomeRepository someRepository;
@Inject //Or on the variable itself
public SomeServiceImpl(SomeRepository someRepository) {
if (someRepository == null) throw new NullPointerException();
this.someRepository = someRepository;
}
@Override
public void updateSomeData(DataStruct struct) {
//Do some magic
someRepository.storeData(struct);
}
}
As you can see there is no substantial difference in code length, but this class is far stronger. Spring operates in virtually the same way, but the annotation is moved to the new constructor (the xml version does add a slight amount of extra knowledge but still less than 10 minutes to process). Objects of this class cannot be in an inconsistent state, its dependencies are checked and locked before the object is made available (throwing the NPE in the style recommended in Effective Java by Joshua Bloch, but other approaches could work).
An additional benefit is that the code has a clearer intent. By defining the key dependencies of a class as constructor dependencies (and as final where relevant), the class becomes more self-describing: "These are the pieces that I am useless without and will blow up if I don't have them". This can then be augmented by setter injection for those other managed beans which are looser, less essential dependencies (such as Strategies that may be used in 1 or 2 methods, or a service which will by bypassed if not available).
Conclusion
Constructors provide a powerful means to ensure that your objects are in a consistent state throughout their life and can help minimize needless mutability and their exposed API. A possible downside of Spring (and other DI containers) is that the easy and consistent setter usage can discourage the use of constructors. Proper use of constructors in place of setters leads to more resilient and more intentional code.
No comments :
Post a Comment