Great post! Please see my comments between the lines.
Micha
Am Freitag, 27. März 2015 schrieb Walter Seethaler :
Hello everyone,
do you like the init methods (constructors) in CF? I use CFCs a lot and ran into some issues with the way CF instantiates its objects. Especially on a framework level, where you provide CFCs for instantiation or subclassing and have no guarantee, how the people are using your classes. When I looked into the Java constructor specification, I realized that it is a very robust definition that avoid every little issue I ran into. So I tried to translated the spec to CF, solve the backward compatibility issues and provide a robust long-term solution. I hope this is a good time and place to share it.
In OO languages it is almost a standard to have a special method, dedicated to the object creation - the constructor. In CF the closest thing we have is the pseudo constructor, whereas the init method is mainly treated as a normal method. Only the new operator partially treats init as a constructor (but new does not call super.init() automatically). I would love to have more robust (better) constructors in Lucee.
I will discuss 3 topics:
- Motivation
- Specification
- Backward compatibility
1. Motivation
- The constructor contains the code that sets up the instance. It should be guaranteed that the constructor is called, even when the class is part of inheritance.
- In CF you can either hope that everybody calls your init method, or try to assure it programmatically.
The problem here is historical, coldfusion 6 indroduced components but without the new operator and no init method.
Later (version 7,8,9?) added support for the new operator and init. But for backward compatibility the old way to instantiate components make no use of this new pseudo constructor.
In Lucee 5 we deprecate the function createObject,cfinvoke and cfobject, so change this functionality makes no sense anymore, best avoid them.
- The instantiation of a CFC should not depend on how the class is created at runtime. The instantiation should be the same, no matter how the class is created.
- The init method and the init methods of all base classes should be called under all circumstances, not matter if you use new, createObject, cfobject or cfinvoke.
Make sense, we need to think about this with the new dialect.
- It should not be possible to call a constructor multiple times, this just inflicts unnecessary problems.
- When I program CFCs that are used by others, I tend to avoid that programatically. It should not be necessary.
Problem here is that if you load the component via createObject you call init after construction
- The init method should always return the instance.
- As init can return something else, I tend to specify the return type (for clarity), which adds an unnecessary type check at runtime.
- The return this; should not be necessary (as in ACF).
The return type is not necessary! Lucee acts exact the same as acf here
- It should not be possible to concurrently call the constructor.
- It is theoretically possible and I wasted time thinking about it.
- You should be able to enforce the behavior off your CFCs.
- With the final keyword and a guaranteed constructor call, you can control the behavior of your CFC pretty well, even if the class is extended.
- A programming language that is easy to use an to learn, should: be intuitive, provide good error messages and have a good documentation. Those 3 points are not true for constructors in CF (in my opinion).
In my opinion the best way is to deprecate createObject, cfinvoke ,cfobject and remove the. For the doc .
- The problems above make it more difficult to provide high software quality (e.g. in OS frameworks, tools, libraries).
2. Specification
- Constructors can not be overridden.
- super() calls the constructor of the base class.
- If super() is not defined in the constructor source, the compiler adds it on top of the constructor source.
- If the super() call (without arguments) is not possible - because the base constructor has at least 1 required argument (without default value) - the compiler throws an error.
Problem is the compiler cannot decide this, Lucee is not a static language like for example Java, things like this need to be decided at runtime.
- Constructors do not have a return value.
It was that way in earlier version, we changed that for comaptibility to acf, but this should be the case for the new dialect
- If no constructor is defined, the compiler adds the standard constructor: function init(){ super(); }
- Constructors can only be called once.
- (It is guaranteed that) constructors are called on instantiation.
Init is called after construction, changing this could break existing code, the body of the component it the only real constructor.
- Constructors can not be defined in an included .cfm.
- As constructors are a compile time feature, I guess dynamic includes would cause problems.
You cannot turn Lucee in a static language, I strongly disagree on this
- getComponentMetaData() and other reflection methods do not call the constructor.
- afaik getComponentMetaData() calls the pseudo-constructor in Lucee (which is false) - while ACF does not.
No this methods are not producing a instance of the component
- Access modifiers can be: remote, public, package or private.
- Not sure about remote and private.
- As private methods can be called from subclasses in CF, private would make a class abstract.
Lucee 5 indroduces the keyword static, with that the modifier private makes a lot of sense, for example to do a singleton
Component {
private function init(){}
Public static getInstance(){if(isnull(static.instance))static.instance=new Test(); return static.instance;}
- Access modifier public is default and can be omitted.
- Pseudo constructor (the source code outside of the functions).
- Either pseudo code is not allowed anymore (clean approach),
- or it is called prior to the constructor.
- Lucee.cfc does not call super().
- Syntax (the compiler must be able to detect the new syntax to provide backward compatibility for the old syntax: function init()).
- If a new constructor is present in a CFC the compiler and the CFC instance must honor this spec.
- If it is not present, everything should behave backward compatible.
component{
public this( required numeric foo ){
super( foo+1 );
}
}
- I personally do not like that Java uses the classname as the constructor name, because the classname in CF is the filename and thus I can easily rename my CFC without the need to change the source of the CFC. So I came up with this syntax.