I am currently facing a problem that I'd like to find an elegant solution for, and I'm looking for a bit of help.
I have a dependency on a class, and from time to time I have to update the code to depend on a new version of that class. However, although the new class is structurally similar to the old class with just some value changes, the two classes are unrelated. The problem I have is that I'd like to be able to choose at runtime to use one version or the other. An example will help.
Let's use this as an example: This FixedOffsetMessageGenerator has the responsibility to generate a message String with each field as a fixed length, where the size of each field is defined by the MessageTemplate class. The MessageTemplate class is autogenerated and lives in an external dependency so there is no opportunity to make any changes to it.
Under normal circumstances when a new version of MessageTemplate comes along it requires a code change. The only change in the client code is to change the package name in the import statement.
We run multiple instances of this application and the requirement is to build in a feature switch that will enable us to configure whether it uses v1 or v2 of the MessageTemplate. If the MessageTemplate classes were part of a class hierarchy then with Polymorphism it'd be easy, but they are unrelated which makes it much more difficult.
Any and all ideas very welcome.
Uses the service-provider loading facilities, defined by the ServiceLoader class, to attempt to locate and load an implementation of the service...
I dunno, that might not be flexible enough in your case but maybe it's worth having a look at.
... which isn't the case here.
load an implementation of the service
The best idea I've had so far is to use the Bridge pattern but haven't tried an implementation of it yet. The straight forward way to implement it isn't great for performance.
There's two things bad about this solution:
1) I now have to create a new instance of the Delegate class everywhere I want to use the Template.
2) The performance hit of having to check the state of 'v2enabled' every time is undesirable.
I haven't come up with anything better yet.
Since this is done only once at class loading, then the performance hit is taken once. You can read the fully-qualified class name from a system property that you can change for each app instance that you run.
EDIT: Just noticed Liutauras' code - yeah, that's almost what I had in mind for the static initialization block above, except just use System.getProperty() to get the class name.
Tim Cooke wrote:the requirement is to (build in a feature switch that will) enable us to configure whether it uses v1 or v2 of the MessageTemplate.
...The straight forward way to implement it isn't great for performance.
I italicized the implementation detail that got built-in with your requirement statement. If you take out the baked-in solution from that sentence, the requirement is still the same except now you have more freedom to explore options because you're not prematurely locked into one particular solution.
Because the properties don't live in a properties file. These MessageTemplate classes are a lot like a properties file, but just in the form of a class. I don't know why this is so but these are the cards I've been dealt so have to roll with it.
Carey Brown wrote:... why not just get the values directly from the properties file?
The use of Reflection in Liutauras's proposal is absolutely the missing piece in my thinking, and the refinement proposed by Junilu is exactly the form that intend to use in the final solution. The best feature of Junilu's proposal is that the client usage of the MessageTemplate class remains essentially unchanged, which cuts down the amount of rework required to implement the Bridge. I'm still performing Shotgun Surgery but it's not as invasive as it could be with another solution. Another important advantage to this solution is that the Shotgun Surgery will not need to be repeated when the next new Template version is released because the dependency on that class exists in just one place now. It's tidy, and I like it a lot.
Thank you all for your help.
It's interesting but I just realized that I've used a similar solution at work to solve a slightly different problem. We used to deploy our apps to an IT-managed environment and they had this scheme where they defined a specific environment variable for apps to refer to in determining which set of configuration files to apply. I used the same technique, a static initialization block that read
A third instance would make it more conclusive but it seems we may have stumbled upon a Java solution pattern of sorts here, too.
All of the heavy lifting with the Reflection library and variable assignment is done up front in the static block, which is fine, leaving the performance of the client usage unaffected. This is a big deal for my requirements.
And the only change required on the client side is to change the import statement for MessageTemplate to use the new bridge class.
I think that'll do just nicely.
properties file you'd have configured to refer to a one template at any given point in time:
In which way you wouldn't need to make decision in source file which MessageTemplate version to use, you just need to know that you are dealing with messageTemplate.class from properties file.
So you get needed version from properties file without a need to recompile your Java source file in case of changes in templates naming convention or packaging.
Tim Cooke wrote:The only compromise I had to make was that I could no longer declare the static fields as final because the values are assigned later in the static block.
I'm pretty sure you can still declared those fields as final. The requirement is that they are definitely assigned at the end of the static initialization block.
You might be confusing this with calls to super, which do need to be made before anything else in a constructor.
- X 2
A little restructuring of the logic will allow you to keep the fields as static final:
Or you could assign the default values in the catch block. You'd still need to declare the local vars outside the try-catch block though.
The idea of assigning a default value to those fields should the Reflection bit fail makes me feel really uneasy. To have the application continue running with incorrect values being served up by the MessageTemplate would be a very very bad thing. If for some reason it can't get the correct value through the Reflection call then I need to know about it immediately and loudly because in that case the show must not go on. Therefore, Rob's suggestion of throwing a RuntimeException in the catch block is the desired behaviour. Thanks Rob.
- X 2
would have been compiled as if it were written
At least that was the case in early versions of Java. So just dropping in your New And Improved™ version of MessageTemplate wouldn't affect those classes. But I'm assuming you're going to recompile them too.
It also has a link to the section on binary compatibility, which is also relevant to Paul's point.
This leads me back to having to keep a reference to both classes in the Bridge and having an if/else statement to choose. This in itself is sub optimal as I've degraded the performance with the if/else statement. It sounds picky but remember these methods are in the critical path and are called a lot. Performance is everything here.
This now gets me to thinking if I could use Reflection again to generate the method on class construction so that it's body contains the call to the desired class. Something like:
If V2 required, create this method.
If V1 required, create this method.
Or perhaps use reflection to generate the field instead so the method can be just a normal method.
If V2 required, create this field.
If V1 required, create this field.
More experimentation required.
In the meantime, I found this article by Bruce Eckel: http://bruceeckel.github.io/2015/10/17/are-java-8-lambdas-closures/
I'm assuming, of course, that you're using Java 8 or later.
where template.doStuff() is also version-specific and not necessarily dependent on the static constant values?
Again, this is all just off the top of my head, so you'll have to explore these ideas further with some experimentation. Interesting problem.