Comparison between old and new OO features
This article proposes a new class system and extended OO features. We are looking forward for any of your feedback and contribution!
Class definition
| qooxdoo 0.6.x | qooxdoo 0.7 |
qx.OO.defineClass("my.cool.Class"); | qx.Class.define("my.cool.Class"); |
Inheritance
| qooxdoo 0.6.x | qooxdoo 0.7 |
Only implicit: Reference to super class is given as second argument of the class definition: qx.OO.defineClass("my.cool.Class",
my.great.SuperClass); | Explicit: Reference to super class is given in key extend: qx.Class.define("my.cool.Class", {
extend : my.great.SuperClass
}); |
Constructor
| qooxdoo 0.6.x | qooxdoo 0.7 |
Only implicit: Constructor is given as third argument of the class definition: qx.OO.defineClass("my.cool.Class",
my.great.SuperClass,
function() {
...
}); | Explicit: Constructor is given in key construct: qx.Class.define("my.cool.Class",
{
extend : my.great.SuperClass,
construct : function() {
...
}
}); |
Class members
| qooxdoo 0.6.x | qooxdoo 0.7 |
Class members are given somewhere after class definition (no closed form): qx.OO.defineClass("my.cool.Class");
qx.Class.foo = VALUE;
qx.Class.bar = function() {}; | Class members are part of the class definition (closed form), given in the map statics: qx.Class.define("my.cool.Class",
{
statics :
{
foo : VALUE,
bar : function() {}
}
}); |
Instance Members
| qooxdoo 0.6.x | qooxdoo 0.7 |
Instance members are given somewhere after class definition (no closed form):qx.OO.defineClass("my.cool.Class");
qx.Proto.foo = VALUE;
qx.Proto.bar = function() {}; | Instance members are part of the class definition (closed form), given in the map members: qx.Class.define("my.cool.Class",
{
members:
{
foo : VALUE,
bar : function() {}
}
}); |
Accessing Static Members
| qooxdoo 0.6.x | qooxdoo 0.7 |
Need to use the fully-qualified class name. Requires manual updates if class name changes. qx.OO.defineClass("my.cool.Class", ...);
qx.Class.PI = 3.141;
qx.Proto.circumference = function(radius) {
return 2 * my.cool.Class.PI * radius;
}; | Generic form. Requires no updates if class name changes. This code can optionally be optimized for performance in build versions.qx.Class.define("my.cool.Class",
{
statics : {
PI : 3.141
}
members : {
circumference : function(radius) {
return 2 * this.self(arguments).PI * radius;
}
}
}); |
Calling the Superclass Constructor
| qooxdoo 0.6.x | qooxdoo 0.7 |
Need to repeat the super class name. Requires manual updates if super class (name) changes. qx.OO.defineClass("my.cool.Class",
my.great.SuperClass,
function(x) {
my.great.SuperClass.call(this, x);
}); | Generic form. Requires no updates if super class (name) changes. This code can optionally be optimized for performance in build versions.qx.Class.define("my.cool.Class",
{
extend : my.great.SuperClass,
construct : function(x) {
this.base(arguments, x);
}
}); |
Calling the Overridden Superclass Method
| qooxdoo 0.6.x | qooxdoo 0.7 |
Need to repeat the super class name. Requires manual updates if super class (name) changes. qx.OO.defineClass("my.cool.Class",
my.great.SuperClass,
...
});
qx.Proto.foo = function(x) {
my.great.SuperClass.prototype.foo.call(this, x);
}; | Generic form without using prototype. Requires no updates if super class (name) changes. This code can optionally be optimized for performance in build versions.qx.Class.define("my.cool.Class", {
extend : my.great.SuperClass,
...
members : {
foo : function(x) {
this.base(arguments, x);
}
}
}); |
Calling the Overridden Superclass Method (passing all arguments)
This construct has this advantage: if the signature of a method is changed later by e.g. adding more parameters, all overriden methods don’t have to be updated, provided the base method is called with unmodified parameters.
| qooxdoo 0.6.x | qooxdoo 0.7 |
Need to repeat the super class name. Requires manual updates if super class (name) changes. qx.OO.defineClass("my.cool.Class",
my.great.SuperClass,
...
});
qx.Proto.foo = function() {
my.great.SuperClass.prototype.foo.apply(this, arguments);
}; | Generic form without using prototype. Requires no updates if super class (name) changes. This code can optionally be optimized for performance in build versions.qx.Class.define("my.cool.Class", {
extend : my.great.SuperClass,
...
members : {
foo : function(x) {
arguments.callee.base.apply(this, arguments);
}
}
}); |
Destructor
| qooxdoo 0.6.x | qooxdoo 0.7 |
Developers needed to make sure a dispose() method was supplied that dealt with memory management of complex data structures: qx.OO.defineClass("my.cool.Class",
my.great.SuperClass,
function() {
...
});
qx.Proto.dispose = function()
{
...
}); | As a logical match to any existing constructor given by the key construct, a destructor is explicitely given by the destruct key: qx.Class.define("my.cool.Class",
{
extend : my.great.SuperClass,
construct : function() {
...
}
destruct : function() {
...
}
}); |
Interfaces
| qooxdoo 0.6.x | qooxdoo 0.7 |
| Not available. | Leading uppercase I as a naming convention.qx.Interface.define("my.cool.IInterface"); |
Attaching interfaces to a class
| qooxdoo 0.6.x | qooxdoo 0.7 (planned) |
| Not available. | The implement key contains either a reference to an single interface, or an array of multiple interfaces : qx.Class.define("my.cool.Class",
{
implement : [my.cool.IInterface, my.other.cool.IInterface],
...
}); |
Mixins
| qooxdoo 0.6.x | qooxdoo 0.7 |
| Not available. | Leading uppercase M as a naming convention. A mixin can have all the things a class can have: properties, destructor, constructor and members. qx.Mixin.define("my.cool.MMixin"); |
Attaching mixins to a class
| qooxdoo 0.6.x | qooxdoo 0.7 |
| Not available. | The include key contains either a reference to an single mixin, or an array of multiple mixins: qx.Class.define("my.cool.Class",
{
include : [my.cool.MMixin, my.other.cool.MMixin]
...
}); |
Access
| qooxdoo 0.6.x | qooxdoo 0.7 |
| Vague naming convention, rather inconsistent use. | By the following naming convention. Goal is to be as consistent as possible. During the build process private members can optionally be renamed to random names in order to ensure that they cannot be called from outside the class.publicMember
_protectedMember
__privateMember |
Static classes
| qooxdoo 0.6.x | qooxdoo 0.7 |
| Not available as explicit declaration. | Explicit declaration allows for useful checks during development. For example. construct or members are not allowed for such a purely static class. qx.Class.define("my.cool.Class", {
type : "static"
}); |
Abstract classes
| qooxdoo 0.6.x | qooxdoo 0.7 |
| Not available as explicit declaration. Required explicit coding in the constructor and adding class variables to ensure runtime checking. | Declaration allows for useful checks during development and does not require explicit code. qx.Class.define("my.cool.Class", {
type : "abstract"
}); |
Singletons
| qooxdoo 0.6.x | qooxdoo 0.7 |
| Not available as explicit declaration. Required explicit coding. | Declaration allows for useful checks during development and does not require explicit code. A method getInstance() is added to such a singleton class. qx.Class.define("my.cool.Class",
{
type : "singleton",
extend : my.great.SuperClass
}); |
| qooxdoo 0.6.x | qooxdoo 0.7 |
Immediate access to members of the same class during declaration of new members was only inconsistently possible in 0.6.x. While it was possible, for instance, to set-up a new class variable by using a previously defined class variable, the order of declaration was crucial. On the other hand, it was not possible to easily code access to class variables from within mehtods using qx.Class:qx.OO.defineClass("my.cool.Class", ...);
qx.Class.driveLetter = "C";
// the following works
qx.Class.drive = qx.Class.driveLetter + ":\\";
qx.Proto.whatsTheDrive = function() {
// the following does _not_ work!
// return "Drive is " + qx.Class.drive;
// it has to be coded using the fully-qualified class name
return "Drive is " + my.cool.Class.drive;
}; | The closed form of the class definition for qooxdoo 0.7 does not allow immediate access to other members, as they are part of the configuration data structure themselves. While it is typically not a feature used very often, it nonetheless needs to be supported by the new class declaration. Instead of some trailing code outside the closed form of the class declaration, an optional defer method is called after the other parts of the class definition have been finished. It allows access to all previously declared statics, members and dynamic properties. If the feature of accessing previously defined members is not absolutely neccessary, the defer should *not* be used in the class definition. It is missing some important capabilities compared to the regular members definition and it cannot take advantage of many crucial features of the build process (documentation, optimization, etc.).qx.Class.define("my.cool.Class",
{
statics:
{
driveLetter : "C"
},
defer: function(statics, members, properties)
{
statics.drive = statics.driveLetter + ":\\";
members.whatsTheDrive = function() {
return "Drive is " + statics.drive;
};
}
}); |
Browser specific methods
| qooxdoo 0.6.x | qooxdoo 0.7 |
Methods which are browser-specific often have top-level switches to decide already at load time which implementation to use. This avoids browser checks at runtime inside the method, but is rather ugly looking and hard to document using the API documentation strings.if (qx.core.Client.isMshtml() || qx.core.Client.isOpera())
{
qx.Proto.foo = function() {
// Internet Explorer or Opera
}
}
else
{
qx.Proto.foo = function() {
// All other browsers
}
} | To maintain the closed form, browser switches on method level is done using variants. Since the generator knows about variants it is (optionally) possible to only keep the code for each specific browser and remove the implementation for all other browsers from the code and thus generate highly-optimized browser-specific builds. It is possible to use an logical “or” directly inside a variant key. If none of the keys matches the variant, the “default” key is used: members:
{
foo: qx.Variant.select("qx.client",
{
"mshtml|opera": function() {
// Internet Explorer or Opera
},
"default": function() {
// All other browsers
}
});
} |
Events
| qooxdoo 0.6.x | qooxdoo 0.7 |
In qooxdoo 0.6 it is not possible to determine from the code, which events an object fires. Special API documentation strings in the class header are used to indicate which types of events are used in the class but there is no way to check the correctness of these declarations./**
* @event click {qx.event.type.MouseEvent} Fired if the widget is clicked.
*/
qx.OO.defineClass("qx.some.Class", qx.core.Target, function()
{
...
});
... | In qooxdoo 0.7 the class definition has a special events key. The value of the key is a map, which maps each distinct event name to the name of the event class whose instances are passed to the event listeners. The event system can now (optionally) check whether an event type is supported by the class and issue a warning if an event type is unknown. This ensures that each supported event must me listed in the event map.qx.Class.define("qx.come.Class",
{
extend: qx.core.Target,
events :
{
/** Fired when the widget is clicked. */
"click": "qx.event.type.MouseEvent"
}
...
}) |