What are immutable objects?
Simply stated, once an immutable object is created you can’t change it. There are typically no setter methods or public instance members, and the constructors are strictly controlled. The results are immutable objects. A factory method can also be used to generate or build immutable objects.Why?
If this concept seems strange, consider these ideas:
- If an object’s state doesn’t change, it makes testing much easier.
- You test the state of the object only after it’s been created. You’re not as focused on testing methods that alter the state of the object, simply because they don’t exist.
- Immutable objects are inherently thread safe. [BLOCH]
- Since no thread can change it’s state, all threads read the exact same data.
- They can be shared freely.
- Your state-based unit tests may be fewer and more reliable.
- I’m speculating here, but your state-based tests may need only test those methods that create immutable objects. Perhaps, one set of tests that work on one set of creational methods could also be used to test other creational methods. (This would be a good area to look at further)
java.lang.String
, a classic immutable object. Once a String is created, it cannot be changed. Period. With software that does a lot of String manipulation, you can end up with a lot of string objects in need of garbage collecting. To answer this problem, StringBuilder
was implemented (StringBuffer is the predecessor). So, you may need to create a mutable version of your object that should be used with specific intent.What does this look like?
In its simplest form, you (1) control how objects are created, and (2) make sure the object’s state can’t be changed. Though there are a number of ways to do this, I’ll present one method using ColdFusion 9.
This example represents a mathematical expression abstraction. An expression will look something like this:
(2*4) + (5^2) * 10
. The main thing we do is control the default constructor, init
. By requiring a string expression as a parameter, we’ve controlled how instances are created.component name="ImmutableExpression" { rawExpression=''; evaluatedExpression=''; public ImmutableExpression function init(required String expression) { rawExpression = expression; try{ evaluatedExpression = evaluate(expression); } catch(Expression e){ throw( type="InvalidExpressionException", message="This doesn't look like a valid expression: " & expression); } return this; } ... }
The above controls ColdFusion’s default constructor
init
by requiring a single argument. In our Expression object, we want to maintain the original string representation and the result of evaluating the expression. Once we create an expression, we’re done. Equally as important, any state is maintained by private instance variables. And there are no setters and no direct way to change this object’s state.
A unit test for the constructor could look something like:
component extends=“mxunit.framework.TestCase” { function twoExpressionsShouldNotBeTheSame(){ exp1 = new ImmutableExpression( “3^3” ); exp2 = new ImmutableExpression( “4*2” ); assertNotSame( exp1, exp2 ); } /** * @expectedException InvalidExpressionException */ function badExpressionShouldBeKickedOut(){ exp1 = new ImmutableExpression( “1*b” ); } }
Let’s make this a little more interesting ...
With these expressions, which are somewhat related to polynomials, perhaps we’d like to add them:exp1 = new ImmutableExpression( “3^3” ); exp2 = new ImmutableExpression( “4*2” ); exp3 = exp1.add(exp2) ;
The take-home here is in the 3rd line. The intent is to effectively add exp2 to exp1, returning a new ImmutableExpression.
exp3.getExpression()
will return (3^3) + (4*2)
and exp3.value()
will return 35
The implementation is straight forward:
... public ImmutableExpression function add (ImmutableExpression exp){ return new ImmutableExpression( “(” & rawExpression & “) + (” & exp.getExpression() & “)” ); } ...
Again, the point here is that we choose not to alter the state of an expression once created and so, we create new ImmutableExpression objects when we need to actually have the expression interact with another object, such as
add(...)
. This is in the style if functional programmingCaveat
- ColdFusion doesn’t offer the ability to annotate a component as
final
, so you have to check the type being instantiated at runtime to prevent it from being extended. Why? If a component can be extended its methods can be overridden and possibly break your immutable intention.
... myComponentType = “sandbox.mutability.ImmutableExpression”; private void function ensureInstanceType(){ if ( getMetaData(this).fullName != myComponentType ) throw ( type=“InheritanceViolationException”, message=“This component is not intended to be extended.” ); } ...
Wrap up
Creating immutable objects is fairly straightforward and makes writing unit tests more effective, and immutable objects are inherently thread safe. However, care must be taken when creating software that could generate lots of immutable objects, and a mutable version of the object could be added to the API.Functional Programming
In mathematics, a function f(n) always returns a value and never alters the function arguments. So, if you have anadd(1,1)
function, it will guarantee to return something, hopefully 2
, and it will guarantee not to change either of the parameters (1 or 1). With respect to programming, we’re usually passing in variables that are evaluated at runtime to some value. In a functional style, you do not change the state of the variables being passed in. In some functional languages, this constraint is a feature, but in many languages it is not. So, you need to do this yourself by cloning.ColdFusion Functional Programming Example: https://github.com/virtix/balisong
Full source code: https://gist.github.com/804634
References:
Josh Bloch, Effective Java
Barbara Liskov, Program Development in Java
♫ Inspiration
Korn
Bill Evans
bill shelton
6 comments:
Mark - interesting post! Two small comments that are not really on topic. In your initial CFC you have:
public ImmutableExpression function init(required String expression) {
if( !structKeyExists(arguments,"expression") ) {
Why do you check for expression? Your method argument signature explicitly sets expression as required. Afaik, your IF there will never run.
Second: Why the toString here:
expression.toString()
Since the argument is required to be a string, what purpose does the toString provide?
Sorry Bill - thought this was Marc.
Thanks for the comments, Ray.
You're absolutely correct on both points. I added "required" more as an afterthought. The original code was simply "init(expression)". And the toString() is redundant for the same reason. Good eye!
/bill
I had the same thoughts as Mr. Camden. That aside, what you want to say can be summarised as:
1. Strings are immutable (they will be discarded when reference changes but never overwritten)
2. Send in valid expressions as Strings as argument parameters,
3. do not allow manipulation (i.e. no Setters)
I think you made a mountain of a mole hill. I cannot see any great application of this. And I wasted time trying to understand the point you were making (besides the 3 points above).
You could have just blogged those 3 points!
Anonymous "coldfusion",
Your comments attempt to dismiss an important concept in software (see Liskov and Bloch references for starters). Perhaps, you should dig a little deeper ...
bill
@coldfusion: Frankly I don't see the point of your comment. You are complaining that they explained something well? Did you really waste _that_ much time? Printed out this is probably a two page blog entry which is approx. 4 minutes of reading. While it may be an easy concept for you, others will appreciate the detail. I did.
Post a Comment