No, don’t leave yet! This isn’t another article about non-deterministic finalization, RAII etc. That’s what we almost always think of when someone mentions the object life-cycle, but I’m actually interested in the other end of the cycle – the “near birth” end.
We often take it as read that when an object’s constructor has completed successfully, the object should be ready to use. However, frameworks and technologies like Spring and XAML often make it easier to create an object and then populate it with dependencies, configuration etc. Yes, in some cases it’s more appropriate to have a separate configuration class which is used for nothing but a bunch of properties, and then the configuration can be passed into the “real” constructor in one go, with none of the readability problems of constructors taking loads of parameters. It’s all a bit unsatisfactory though.
What we most naturally want is to say, “Create me an empty X. Now configure it. Now use it.” (Okay, and as an obligatory mention, potentially “Now make it clean up after itself.”)
While configuring the object, we don’t want to call any of the “real” methods which are likely to want to do things. We may want to be able to fetch some of the configuration back again, e.g. so that some values can be relative to others easily, but we don’t want the main business to take place. Likewise, when we’ve finished configuring the object, we generally want to validate the configuration, and after that we don’t want anyone to be able to change the configuration. Sometimes there’s even a third phase, where we’ve cleaned up and want to still be able to get some calculated results (the byte array backing a MemoryStream
, for instance) but not call any of the “main” methods any more.
I’d really like some platform support for this. None of it’s actually that hard to do – just a case of keeping track of which phase you’re in, and then adding a check to the start of each method. Wouldn’t it be nicer to have it available as attributes though? Specify the “default phase” for any undecorated members, and specify which phases are valid for other members – so configuration setters would only be valid in the configuration phase, for instance. Another attribute could dictate the phase transition – so the ValidateAndInitialize
method (or whatever you’d call it) would have an attribute stating that on successful completion (no exceptions thrown) the phase would move from “configure” to “use”.
Here’s a short code sample. The names and uses of the attributes could no doubt be improved, and if there were only a few phases which were actually useful, they could be named in an enum instead, which would be neat.
[Phased(defaultRequirement=2, initial=1)] class Sample { IAuthenticator authenticator; public IAuthenticator Authenticator { [Phase(1)] [Phase(2)] get { return authenticator; } [Phase(1)] set { authenticator = value; } } [Phase(1)] [PhaseTransition(2)] public void ValidateAndInitialize() { if (authenticator==null) { throw new InvalidConfigurationException("I need an authenticator"); } } public void DoSomething() { // Use authenticator, assuming it's valid } public void DoSomethingElse() { // Use authenticator, assuming it's valid } } |
Hopefully it’s obvious what you could and couldn’t do at what point.
This looks to me like a clear example of where AOP should get involved. I believe that Anders isn’t particularly keen on it, and when abused it’s clearly nightmarish – but for certain comment things, it just makes life easier. The declarative nature of the above is simpler to read (IMO – particularly if names were used instead of numbers) than manually checking the state at the start of each method. I don’t know if any AOP support is on the slate for Java 7 – I believe things have been made easier for AOP frameworks by Java 6, although I doubt that any target just Java 6 yet. We shall have to see.
One interesting question is whether you’d unit test that all the attributes were there appropriately. I guess it depends on the nature of the project, and just how thoroughly you want to unit test. It wouldn’t add any coverage, and would be hard to exhaustively test in real life, but the tests would be proving something…