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
-
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.
2. 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.
- 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.
- 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 init method and the init methods of all base classes should be
- It should not be possible to concurrently call the constructor.
- It is theoretically possible and I wasted time thinking about it.
6. 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).
- With the final keyword and a guaranteed constructor call, you can
- The problems above make it more difficult to provide high software
quality (e.g. in OS frameworks, tools, libraries).
- The constructor contains the code that sets up the instance. It
-
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. - Constructors do not have a return value.
- 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.
-
Constructors can not be defined in an included .cfm.
- As constructors are a compile time feature, I guess dynamic
includes would cause problems.
- As constructors are a compile time feature, I guess dynamic
- 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.
- 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.
- 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.
- Execution order
- Not sure about this one. The questions are:
- When are properties instantiated (default values).
- When is the constructor and pseudo constructor called?
- Are the other methods available in the constructor?
- In Java the compiler gives a warning, if the constructor uses
a method that can be overwritten in subclasses. So I guess in Java it is
possible to use a method in the constructor that is overridden at runtime. - With the final keyword in Lucee 5, every developer can decide
to only use methods in the constructors, that can not be overwritten
(declared as final).
- In Java the compiler gives a warning, if the constructor uses
- My try:
- …
2. make base methods available and override base+1 methods
3. make methods available and override base methods
4. …
5. make base properties available and override…
6. make properties available and override…
7. …
8. call base pseudo constructor (see 13.2)
9. call pseudo constructor (see 13.2)
10. call constructor (which calls super() as specified)
17. Object creation- new Foo() - creates the instance and calls the constructor as
defined in 16. - createObject(“component”, “Foo”) - creates the instance and
calls the constructor as defined in 16. - - creates the instance and calls the constructor as
defined in 16. - - creates the instance and calls the constructor as
defined in 16 and calls the method specified.
- new Foo() - creates the instance and calls the constructor as
-
Backward compatibility
- Without a new constructor present, everything behaves as before.
- With the new constructor present in the source code.
- If a method with the name init is present, it is treated as a
normal function. - new, createObject, cfobject and cfinvoke honor the spec.
- It is not possible to instantiate a CFC without honoring the spec.
- If a method with the name init is present, it is treated as a
- Inheritance between CFCs with old and new constructor should work well.
- Most important is, that CFCs with the new constructor can extend
CFC with the old init constructor.
- Most important is, that CFCs with the new constructor can extend
What do you think?