GUIDES Classes And Inheritance
- Contents
- 1. Introduction
- 2. Creating Classes
- 3. Creating Instances
-
4. Declaring Features
- 4.1. Declaring Features During Class Creation
- 4.2. Using the declare Static Method
- 4.3. Using the Feature Declaration Methods
- 4.4. Modifying the Class Manually
-
4.5. Features Declaration Object
- 4.5.1. Declaring Features by Type When Creating a Class
- 4.5.2. Declaring Features by Type for an Already Created Class
- 4.5.3. How the Features Declaration Object is Implemented
- 4.5.4. Standard Properties of the Features Declaration Object
- 4.5.5. More Feature Types for Specific Base Classes
- 4.5.6. Less Conventional Usages
- 4.6. Feature Declaration Methods
- 4.7. Declare Private or Public Features
- 4.8. Feature Declarations are Cumulative
- 4.9. Dual Context Features
- 5. Feature Types
- 6. Class-specific Feature Types
- 7. Constructor Mechanism
- 8. Inheritance
-
9. Advanced Topics
- 9.1. Private vs. Public
- 9.2. Non-inheritable Statics
- 9.3. Singletons
- 9.4. The "no new" Mechanism
1. Introduction
The UIZE JavaScript Framework implements a system for class inheritance that makes subclassing and object-oriented programming possible in JavaScript.
2. Creating Classes
UIZE's system for classes and inheritance makes it dead easy to create a class.
Class features are provided in the Uize.Class module. You can create your own class very easily, simply by calling the subclass method on the Uize.Class base class, as follows...
var Rectangle = Uize.Class.subclass ({
stateProperties:{
width:{value:10},
height:{value:10}
},
instanceMethods:{
displayArea:function () {
alert (this.get ('width') * this.get ('height'));
}
}
});
var rectangle = Rectangle ();
rectangle.displayArea (); // alerts "100"
var anotherRectangle = Rectangle ({width:5,height:15});
anotherRectangle.displayArea (); // alerts "75"
2.1. Creating Further Subclasses
All subclasses that inherit from Uize.Class get a subclass static method, so you can make further subclasses just as easily...
var Rectangle = Uize.Class.subclass ({
stateProperties:{
width:{value:10},
height:{value:10}
},
instanceMethods:{
displayArea:function () {
alert (this.get ('width') * this.get ('height'));
}
}
});
var VerboseRectangle = Uize.Class.subclass ({
instanceMethods:{
displayArea:function () {
var
width = this.get ('width'),
height = this.get ('height')
;
alert ('The areas is: ' + width * height + ' (' + width + ' x ' + height + ' )');
}
}
});
var rectangle = Rectangle ();
rectangle.displayArea (); // alerts "100"
var verboseRectangle = VerboseRectangle ({width:5,height:15});
verboseRectangle.displayArea (); // alerts "The area is: 75 (5 x 15)"
3. Creating Instances
When you create a class that calling the Uize.Class.subclass method, either on the Uize.Class base class or a subclass of it, creating instances is more convenient than regular classes.
3.1. The "new" Keyword is Optional
The Uize.Class base class implements a novel mechanism for constructors that makes the "new" keyword optional when creating instances.
Because the Uize.Class base class utilizes the "no new" mechanism, one can create instances of any Uize.Class subclass either using the new operator or not. This means that you can use the "new" keyword or not with UIZE classes (as well as your own classes), and the end result will be the same.
THIS...
var myInstance = MyClass ();
IS EQUIVALENT TO...
var myInstance = new MyClass ();
For a detailed discussion of this mechanism and how it works, consult the section The "no new" Mechanism.
3.2. State Replaces Constructor Arguments
The Uize.Class base class standardizes the constructor signature for all classes to an optional object that specifies initial state for a newly created instance.
EXAMPLE
var rectangle = Rectangle ({width:5,height:15});
This design choice focuses the design of classes on exposing anything that might affect construction through the state properties system, leading to better designed class interfaces. Being an object, the initial state object also allows state properties to be specified in any order, as well as allowing initial instance state for any and all properties to be optionally specified with sensible defaulting defined for state properties in the class' implementation. In contrast, the traditional approach to constructor arguments often suffers from increasingly complicated overloading of the constructor function as a class evolves to become richer.
4. Declaring Features
For convenience, UIZE provides a few different ways of declaring a class' features to suit different situations.
4.1. Declaring Features During Class Creation
The most concise form of declaring class features allows the features to be optionally declared in the call to the subclass method.
var MyClass = Uize.Class.subclass ({
instanceMethods:{
someInstanceMethod1:function () {
// do some stuff
},
someInstanceMethod2:function () {
// do some stuff
}
},
staticMethods:{
someStaticMethod1:function () {
// do some stuff
},
someStaticMethod2:function () {
// do some stuff
}
}
});
The Uize.Class.subclass method can optionally accept a featuresByTypeOBJ parameter, whose value should be a features declaration object, where the name of each property should be the name of a feature type, and where the value of each property should be an object containing any number of features of that type.
4.2. Using the declare Static Method
If you already have a reference to a created class and you would like to declare multiple features of different types in a single method call, you can call the MyClass.declare static on the class.
MyClass.declare ({
instanceMethods:{
someInstanceMethod1:function () {
// do some stuff
},
someInstanceMethod2:function () {
// do some stuff
}
},
staticMethods:{
someStaticMethod1:function () {
// do some stuff
},
someStaticMethod2:function () {
// do some stuff
}
}
});
4.3. Using the Feature Declaration Methods
As an alternative to using the MyClass.declare static method, you can declare features on a class using the various feature declaration static methods of the class that are inherited from the Uize.Class base class.
call the MyClass.instanceMethods method to declare instance methods |
|
call the MyClass.staticMethods method to declare static methods |
|
call the MyClass.stateProperties method to declare state properties |
|
more feature declaration methods: MyClass.instanceProperties, MyClass.staticProperties, etc. |
EXAMPLE
MyClass.instanceMethods ({
someInstanceMethod1:function () {
// do some stuff
},
someInstanceMethod2:function () {
// do some stuff
}
});
MyClass.staticMethods ({
someStaticMethod1:function () {
// do some stuff
},
someStaticMethod2:function () {
// do some stuff
}
});
4.4. Modifying the Class Manually
In addition to the various convenient ways that are provided for declaring class features, you can also declare features by modifying a class manually.
assign properties on the class' prototype object to define instance methods and/or properties |
|
| assign properties on the class to define static methods and/or properties |
EXAMPLE
MyClass.prototype.someInstanceMethod = function () {
// do some stuff
};
MyClass.someStaticMethod = function () {
// do some stuff
};
While this technique is not recommended, you may encounter older UIZE code that uses this technique for declaring class features, since the code may not yet have been updated since the newer approach to feature declaration was introduced.
4.5. Features Declaration Object
Both the MyClass.subclass and MyClass.declare static methods accept a featuresByTypeOBJ parameter, which allows multiple features of multiple different feature types to be specified in a single feature declaration object and, therefore, declared in a single method call.
4.5.1. Declaring Features by Type When Creating a Class
The Uize.Class.subclass method supports a variation that lets you create a subclass, declaring multiple features by type at the time of creating a class, by supplying just a single featuresByTypeOBJ parameter.
EXAMPLE
var MySubclass = MyClass.subclass ({
alphastructor:function () {
// implementation here
},
omegastructor:function () {
// implementation here
},
staticMethods:{
staticMethod1:function () {
// implementation here
},
staticMethod2:function () {
// implementation here
}
},
instanceMethods:{
instanceMethod1:function () {
// implementation here
},
instanceMethod2:function () {
// implementation here
}
},
stateProperties:{
stateProperty1:{
// property profile
},
stateProperty2:{
// property profile
}
}
});
4.5.2. Declaring Features by Type for an Already Created Class
One or more features of one or more different feature types can be declared for a class after the class has already been created, by calling the Uize.Class.declare method on the class and supplying a featuresByTypeOBJ parameter.
EXAMPLE
var MyClass = Uize.Class.subclass ();
MyClass.declare ({
alphastructor:function () {
// implementation here
},
omegastructor:function () {
// implementation here
},
staticMethods:{
staticMethod1:function () {
// implementation here
},
staticMethod2:function () {
// implementation here
}
},
instanceMethods:{
instanceMethod1:function () {
// implementation here
},
instanceMethod2:function () {
// implementation here
}
},
stateProperties:{
stateProperty1:{
// property profile
},
stateProperty2:{
// property profile
}
}
});
4.5.3. How the Features Declaration Object is Implemented
The properties of the featuresByTypeOBJ object should correspond to the names of the various feature declaration methods supported by the class being subclassed.
When features are specified, categorized by type, in the featuresByTypeOBJ parameter, the Uize.Class.declare and Uize.Class.subclass methods will iterate over the properties of the object, attempting to call a static method of the name of each property encountered, on the class being subclassed, and passing the value of the property as the first parameter of the feature declaration method.
So, for example, if the Uize.Service.subclass method is called to create a service class, and if a featuresByTypeOBJ parameter is specified, and if a serviceMethods property exists within the featuresByTypeOBJ object, then the Uize.Service.serviceMethods static method will be called and the value of the serviceMethods property from the featuresByTypeOBJ object will be passed as the single parameter to the Uize.Service.serviceMethods method.
To illustrate this by example...
THIS...
var FileSystem = Uize.Service.subclass ({
serviceMethods:{
readFile:{
async:false
},
writeFile:{
async:false
},
getFiles:{
async:false
},
getFolder:{
async:false
},
// etc.
// etc.
}
});
...IS EQUIVALENT TO...
var FileSystem = Uize.Service.subclass ();
FileSystem.serviceMethods ({
readFile:{
async:false
},
writeFile:{
async:false
},
getFiles:{
async:false
},
getFolder:{
async:false
},
// etc.
// etc.
});
4.5.4. Standard Properties of the Features Declaration Object
Support for the following standard properties of the features declaration object is built into the Uize.Class base class...
alphastructor - lets you declare the alphastructor for the class |
|
omegastructor - lets you declare the omegastructor for the class |
|
instanceMethods - lets you declare one or more instance methods for the class |
|
instanceProperties - lets you declare one or more instance properties for the class |
|
staticMethods - lets you declare one or more static methods for the class |
|
staticProperties - lets you declare one or more static properties for the class |
|
stateProperties - lets you declare one or more state properties for instances of the class |
|
dualContextMethods - lets you declare one or more dual context methods for the class |
|
dualContextProperties - lets you declare one or more dual context properties for the class |
4.5.5. More Feature Types for Specific Base Classes
Because of how the features declaration object is implemented, declaring multiple features, categorized by type, inherently supports new feature types introduced in subclasses.
In addition to the standard properties of the features declaration object, certain classes may introduce class-specific feature types for which new feature declaration static methods will be introduced. These new feature types will, by design, introduce additional properties that can be specified in the features declaration object when declaring features for subclasses of those classes. For more details, see the section Class-specific Feature Types.
4.5.6. Less Conventional Usages
Because of how the features declaration object is implemented, one can also do less conventional things along with declaring features in the Uize.Class.subclass and Uize.Class.declare methods.
For example, one can effectively call the Uize.Class.set static method to override the initial values of state properties that are inherited from the base class, as shown in the following example...
INSTEAD OF...
var MySliderWidgetSubclass = Uize.Widget.Bar.Slider.subclass ({
instanceMethods:{
// instance methods declared here
},
stateProperties:{
// state properties declared here
}
});
MySliderWidgetSubclass.set ({
minValue:-50,
maxValue:50
});
USE...
var MySliderWidgetSubclass = Uize.Widget.Bar.Slider.subclass ({
instanceMethods:{
// instance methods declared here
},
stateProperties:{
// state properties declared here
},
set:{
minValue:-50,
maxValue:50
}
});
4.6. Feature Declaration Methods
The Uize.Class module provides a number of methods that let you declare instance and/or static features of a class.
Uize.Class.declare - lets you declare one or more features of one or more different feature types for the class |
|
Uize.Class.alphastructor - lets you declare the alphastructor for the class |
|
Uize.Class.omegastructor - lets you declare the omegastructor for the class |
|
Uize.Class.instanceMethods - lets you declare one or more instance methods for the class |
|
Uize.Class.instanceProperties - lets you declare one or more instance properties for the class |
|
Uize.Class.staticMethods - lets you declare one or more static methods for the class |
|
Uize.Class.staticProperties - lets you declare one or more static properties for the class |
|
Uize.Class.dualContextMethods - lets you declare one or more dual context methods for the class |
|
Uize.Class.dualContextProperties - lets you declare one or more dual context properties for the class |
|
Uize.Class.stateProperties - lets you declare one or more state properties for instances of the class |
4.7. Declare Private or Public Features
The feature declaration methods can be used either to declare public features or private features.
In UIZE, there is no fundamental difference between private methods or properties and public methods or properties - it's all in the naming. By convention, private features are named with an "_" (underscore) prefix. This has its pros and cons, but one side effect of this is that either private or public features (or a mixture of both) can be declared using the feature declaration methods.
EXAMPLE
_class.instanceMethods ({
_privateInstanceMethod1:function () {
// implementation here
},
_privateInstanceMethod2:function () {
// implementation here
},
publicInstanceMethod1:function () {
// implementation here
},
publicInstanceMethod2:function () {
// implementation here
}
});
In the above example, one call to the Uize.Class.instanceMethods method is being used to declare the _privateInstanceMethod1 and _privateInstanceMethod2 private instance methods, along with the publicInstanceMethod1 and publicInstanceMethod2 public instance methods.
For a more in-depth discussion on implementing private features for a class, consult the section Private vs. Public.
4.8. Feature Declarations are Cumulative
All the feature declaration methods can be called as many times as desired, and calling them repeatedly is cumulative in nature.
This is useful, because it lets you break out declarations into different sections in your code if that makes your code more readable and/or manageable.
EXAMPLE
// Private Instance Methods
_class.instanceMethods ({
_privateInstanceMethod1:function () {
// implementation here
},
_privateInstanceMethod2:function () {
// implementation here
}
});
// ... ... ... ... ... ... ... ... ...
// Public Instance Methods
_class.instanceMethods ({
publicInstanceMethod1:function () {
// implementation here
},
publicInstanceMethod2:function () {
// implementation here
}
});
In the above example, the Uize.Class.instanceMethods method is being called twice - in one section to declare private instance methods, and in the other section to declare public instance methods.
4.9. Dual Context Features
Dual context class features are features that exist both on the class as well as instances of the class.
Examples of dual context features are the various event system methods. For example, the fire instance method lets you fire an instance event, while the Uize.Class.fire static method lets you fire an event on a class. Both the instance and class methods for firing events share the same underlying implementation, where the implementation may contain minor conditionalizing when executing in the instance context versus executing in the class context.
In cases where it is possible (and possibly even desirable) to share the same function between an instance method and a class method, the Uize.Class.dualContextMethods static method can be used to declare such methods in a single statement, rather than separately calling both the Uize.Class.instanceMethods and Uize.Class.staticMethods methods.
Although a less likely scenario, it is also possible to declare dual context properties using the Uize.Class.dualContextProperties static method. This method is present mainly for symmetry and consistency.
For dual context features, it is assumed that the feature is named the same on both the instance and the class. In situations where this is not the case, one should just use the separate methods for defining instance features and class features.
5. Feature Types
The UIZE class system formally supports the following feature types...
5.1. Alphastructor
The alphastructor is a constructor in the alphastructor chain for a class that is executed before the state properties for the instance are initialized.
When a subclass is created, the alphastructor chain for the class is created by copying the alphastructor chain of the superclass and then appending the alphastructor specified for the new subclass.
The alphastructor can be declared for a class in any of the following ways...
5.1.1. Declaring the Alphastructor During Subclassing
The alphastructor can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.
The features declaration object should contain an alphastructor property whose value should be the alphastructor function.
EXAMPLE
var MyClass = Uize.Class.subclass ({
alphastructor:function () {
// implementation of alphastructor
}
});
5.1.2. Declaring the Alphastructor After Subclassing, Using MyClass.declare
The alphastructor can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.
The features declaration object passed to the MyClass.declare method should contain an alphastructor property whose value should be the alphastructor function.
EXAMPLE
MyClass.declare ({
alphastructor:function () {
// implementation of alphastructor
}
});
5.1.3. Declaring the Alphastructor After Subclassing, Using MyClass.instanceMethods
The alphastructor can be declared for a class after the class has already been created, by calling the MyClass.alphastructor method on the class.
When calling the MyClass.alphastructor method, it should be passed a single function parameter that is the alphastructor function.
EXAMPLE
MyClass.alphastructor (
function () {
// implementation of alphastructor
}
);
5.2. Omegastructor
The omegastructor is a constructor in the omegastructor chain for a class that is executed after the state properties for the instance are initialized.
When a subclass is created, the omegastructor chain for the class is created by copying the omegastructor chain of the superclass and then appending the omegastructor specified for the new subclass.
The omegastructor can be declared for a class in any of the following ways...
5.2.1. Declaring the Omegastructor During Subclassing
The omegastructor can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.
The features declaration object should contain an omegastructor property whose value should be the omegastructor function.
EXAMPLE
var MyClass = Uize.Class.subclass ({
omegastructor:function () {
// implementation of omegastructor
}
});
5.2.2. Declaring the Omegastructor After Subclassing, Using MyClass.declare
The omegastructor can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.
The features declaration object passed to the MyClass.declare method should contain an omegastructor property whose value should be the omegastructor function.
EXAMPLE
MyClass.declare ({
omegastructor:function () {
// implementation of omegastructor
}
});
5.2.3. Declaring the Omegastructor After Subclassing, Using MyClass.instanceMethods
The omegastructor can be declared for a class after the class has already been created, by calling the MyClass.omegastructor method on the class.
When calling the MyClass.omegastructor method, it should be passed a single function parameter that is the omegastructor function.
EXAMPLE
MyClass.omegastructor (
function () {
// implementation of omegastructor
}
);
5.3. Instance Methods
Instance methods are functions that are assigned on the prototype object of a class.
When a subclass is created, all instance methods of the superclass are inherited by the subclass through a copy operation. Once a subclass is created, changing the inherited instance methods on the superclass has no effect on the subclass.
Instance methods can be declared for a class in any of the following ways...
5.3.1. Declaring Instance Methods During Subclassing
Instance methods can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.
The features declaration object should contain an instanceMethods property whose value should be an object containing mappings of instance method names to implementation functions.
EXAMPLE
var MyClass = Uize.Class.subclass ({
instanceMethods:{
foo:function () {
// implementation of foo instance method
},
bar:function () {
// implementation of bar instance method
}
}
});
5.3.2. Declaring Instance Methods After Subclassing, Using MyClass.declare
Instance methods can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.
The features declaration object passed to the MyClass.declare method should contain an instanceMethods property whose value should be an object containing mappings of instance method names to implementation functions.
EXAMPLE
MyClass.declare ({
instanceMethods:{
foo:function () {
// implementation of foo instance method
},
bar:function () {
// implementation of bar instance method
}
}
});
5.3.3. Declaring Instance Methods After Subclassing, Using MyClass.instanceMethods
Instance methods can be declared for a class after the class has already been created, by calling the MyClass.instanceMethods method on the class.
When calling the MyClass.instanceMethods method, an instance methods declaration object should be provided, containing mappings of instance method names to implementation functions.
EXAMPLE
MyClass.instanceMethods ({
foo:function () {
// implementation of foo instance method
},
bar:function () {
// implementation of bar instance method
}
});
5.4. Instance Properties
Instance properties are non-function type values that are assigned on the prototype object of a class.
When a subclass is created, all instance properties of the superclass are inherited by the subclass through a clone-copy operation. Once a subclass is created, changing the inherited instance properties on the superclass has no effect on the subclass.
Instance properties can be declared for a class in any of the following ways...
5.4.1. Declaring Instance Properties During Subclassing
Instance properties can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.
The features declaration object should contain an instanceProperties property whose value should be an object containing mappings of instance property names to initial values.
EXAMPLE
var MyClass = Uize.Class.subclass ({
instanceProperties:{
foo:'bar',
baz:'qux'
}
});
5.4.2. Declaring Instance Properties After Subclassing, Using MyClass.declare
Instance properties can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.
The features declaration object passed to the MyClass.declare method should contain an instanceProperties property whose value should be an object containing mappings of instance property names to initial values.
EXAMPLE
MyClass.declare ({
instanceProperties:{
foo:'bar',
baz:'qux'
}
});
5.4.3. Declaring Instance Properties After Subclassing, Using MyClass.instanceProperties
Instance properties can be declared for a class after the class has already been created, by calling the MyClass.instanceProperties method on the class.
When calling the MyClass.instanceProperties method, an instance properties declaration object should be provided, containing mappings of instance property names to initial values.
EXAMPLE
MyClass.instanceProperties ({
foo:'bar',
baz:'qux'
});
5.5. Static Methods
Static methods are functions that are assigned on the class.
When a subclass is created, all static methods of the superclass are inherited by the subclass through a copy operation (the exception to this is non-inheritable statics that are registered with the MyClass.nonInheritableStatics static property). Once a subclass is created, changing the inherited static methods on the superclass has no effect on the subclass.
Static methods can be declared for a class in any of the following ways...
5.5.1. Declaring Static Methods During Subclassing
Static methods can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.
The features declaration object should contain an staticMethods property whose value should be an object containing mappings of static method names to implementation functions.
EXAMPLE
var MyClass = Uize.Class.subclass ({
staticMethods:{
foo:function () {
// implementation of foo static method
},
bar:function () {
// implementation of bar static method
}
}
});
5.5.2. Declaring Static Methods After Subclassing, Using MyClass.declare
Static methods can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.
The features declaration object passed to the MyClass.declare method should contain an staticMethods property whose value should be an object containing mappings of static method names to implementation functions.
EXAMPLE
MyClass.declare ({
staticMethods:{
foo:function () {
// implementation of foo static method
},
bar:function () {
// implementation of bar static method
}
}
});
5.5.3. Declaring Static Methods After Subclassing, Using MyClass.instanceMethods
Static methods can be declared for a class after the class has already been created, by calling the MyClass.staticMethods method on the class.
When calling the MyClass.staticMethods method, a static methods declaration object should be provided, containing mappings of static method names to implementation functions.
EXAMPLE
MyClass.staticMethods ({
foo:function () {
// implementation of foo static method
},
bar:function () {
// implementation of bar static method
}
});
5.6. Static Properties
Static properties are non-function type values that are assigned on the class.
When a subclass is created, all static properties of the superclass are inherited by the subclass through a clone-copy operation (the exception to this is non-inheritable statics that are registered with the MyClass.nonInheritableStatics static property). Once a subclass is created, changing the inherited static properties on the superclass has no effect on the subclass.
Static properties can be declared for a class in any of the following ways...
5.6.1. Declaring Static Properties During Subclassing
Static properties can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.
The features declaration object should contain a staticProperties property whose value should be an object containing mappings of static property names to initial values.
EXAMPLE
var MyClass = Uize.Class.subclass ({
staticProperties:{
foo:'bar',
baz:'qux'
}
});
5.6.2. Declaring Static Properties After Subclassing, Using MyClass.declare
Static properties can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.
The features declaration object passed to the MyClass.declare method should contain a staticProperties property whose value should be an object containing mappings of static property names to initial values.
EXAMPLE
MyClass.declare ({
staticProperties:{
foo:'bar',
baz:'qux'
}
});
5.6.3. Declaring Static Properties After Subclassing, Using MyClass.staticProperties
Static properties can be declared for a class after the class has already been created, by calling the MyClass.staticProperties method on the class.
When calling the MyClass.staticProperties method, a static properties declaration object should be provided, containing mappings of static property names to initial values.
EXAMPLE
MyClass.staticProperties ({
foo:'bar',
baz:'qux'
});
5.7. State Properties
State properties are instance properties with special features that, among other things, ties them into the event system so that changes in their values can be easily observed.
When a subclass is created, all state properties of the superclass are inherited by the subclass through a clone-copy operation. Once a subclass is created, changing the inherited state properties on the superclass has no effect on the subclass. For a detailed discussion of state properties and the various features supported for them, consult the State Properties guide.
State properties can be declared for a class in any of the following ways...
5.7.1. Declaring State Properties During Subclassing
State properties can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.
The features declaration object should contain a stateProperties property whose value should be an object containing mappings of state property names to property profiles.
EXAMPLE
var MyClass = Uize.Class.subclass ({
stateProperties:{
width:{
conformer:function (value) {return Uize.constrain (value,1,50)},
value:10
},
height:{
conformer:function (value) {return Uize.constrain (value,1,50)},
value:10
}
}
});
5.7.2. Declaring State Properties After Subclassing, Using MyClass.declare
State properties can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.
The features declaration object passed to the MyClass.declare method should contain a stateProperties property whose value should be an object containing mappings of state property names to property profiles.
EXAMPLE
MyClass.declare ({
stateProperties:{
width:{
conformer:function (value) {return Uize.constrain (value,1,50)},
value:10
},
height:{
conformer:function (value) {return Uize.constrain (value,1,50)},
value:10
}
}
});
5.7.3. Declaring State Properties After Subclassing, Using MyClass.instanceProperties
State properties can be declared for a class after the class has already been created, by calling the MyClass.stateProperties method on the class.
When calling the MyClass.stateProperties method, a state properties declaration object should be provided, containing mappings of state property names to property profiles.
EXAMPLE
MyClass.stateProperties ({
width:{
conformer:function (value) {return Uize.constrain (value,1,50)},
value:10
},
height:{
conformer:function (value) {return Uize.constrain (value,1,50)},
value:10
}
});
5.8. Dual Context Methods
Dual context methods are functions that are assigned on the class as well as on the prototype object of the class.
Because dual context methods are essentially just combined static and instance methods, all dual context methods of a superclass are inherited by a subclass through the copy operations used when inheriting static and instance methods. The exception to this is a dual context method where the static method is registered with the MyClass.nonInheritableStatics static property as being a non-inheritable static. Once a subclass is created, changing the inherited dual context methods on the superclass has no effect on the subclass.
Dual context methods can be declared for a class in any of the following ways...
5.8.1. Declaring Dual Context Methods During Subclassing
Dual context methods can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.
The features declaration object should contain an dualContextMethods property whose value should be an object containing mappings of dual context method names to implementation functions.
EXAMPLE
var MyClass = Uize.Class.subclass ({
dualContextMethods:{
foo:function () {
// implementation of foo dual context method
},
bar:function () {
// implementation of bar dual context method
}
}
});
5.8.2. Declaring Dual Context Methods After Subclassing, Using MyClass.declare
Dual context methods can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.
The features declaration object passed to the MyClass.declare method should contain an dualContextMethods property whose value should be an object containing mappings of dual context method names to implementation functions.
EXAMPLE
MyClass.declare ({
dualContextMethods:{
foo:function () {
// implementation of foo dual context method
},
bar:function () {
// implementation of bar dual context method
}
}
});
5.8.3. Declaring Dual Context Methods After Subclassing, Using MyClass.dualContextMethods
Dual context methods can be declared for a class after the class has already been created, by calling the MyClass.dualContextMethods method on the class.
When calling the MyClass.dualContextMethods method, a dual context methods declaration object should be provided, containing mappings of dual context method names to implementation functions.
EXAMPLE
MyClass.dualContextMethods ({
foo:function () {
// implementation of foo dual context method
},
bar:function () {
// implementation of foo dual context method
}
});
5.9. Dual Context Properties
Dual context properties are non-function type values that are assigned on the class as well as on the prototype object of the class.
Because dual context properties are essentially just combined static and instance properties, all dual context properties of a superclass are inherited by a subclass through the clone-copy operations used when inheriting static and instance properties. The exception to this is a dual context property where the static property is registered with the MyClass.nonInheritableStatics static property as being a non-inheritable static. Once a subclass is created, changing the inherited dual context properties on the superclass has no effect on the subclass.
Dual context properties can be declared for a class in any of the following ways...
5.9.1. Declaring Dual Context Properties During Subclassing
Dual context properties can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.
The features declaration object should contain an dualContextProperties property whose value should be an object containing mappings of dual context property names to initial values.
EXAMPLE
var MyClass = Uize.Class.subclass ({
dualContextProperties:{
foo:'bar',
baz:'qux'
}
});
5.9.2. Declaring Dual Context Properties After Subclassing, Using MyClass.declare
Dual context properties can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.
The features declaration object passed to the MyClass.declare method should contain an dualContextProperties property whose value should be an object containing mappings of dual context property names to initial values.
EXAMPLE
MyClass.declare ({
dualContextProperties:{
foo:'bar',
baz:'qux'
}
});
5.9.3. Declaring Dual Context Properties After Subclassing, Using MyClass.dualContextProperties
Dual context properties can be declared for a class after the class has already been created, by calling the MyClass.dualContextProperties method on the class.
When calling the MyClass.dualContextProperties method, a dual context properties declaration object should be provided, containing mappings of dual context property names to initial values.
EXAMPLE
MyClass.dualContextProperties ({
foo:'bar',
baz:'qux'
});
6. Class-specific Feature Types
In addition to the standard feature types that are built into the Uize.Class base class, any class can introduce new class-specific feature types for which new feature declaration static methods will be introduced to allow features of those new types to be declared by subclasses of the class.
6.1. Service Methods, as an Example
For instance, the Uize.Service base class introduces the feature type of a service method and provides the Uize.Service.serviceMethods static method for declaring service methods.
So, inherently, service methods can be declared in the featuresByTypeOBJ parameter along with other feature types that were introduced in the Uize.Class base class (instance methods, instance properties, static methods, static properties, state properties, etc.), simply by specifying a serviceMethods property in the featuresByTypeOBJ object.
6.2. Your Own Feature Types
You can introduce your own new feature types, and these feature types will get the benefits of the generalized system for declaring features
Quite simply, if you implement a new base class of which multiple different subclasses will be created, and you define a static method that allows developers to declare features of a new feature type that is introduced in your base class, then features of that type can be declared in the Uize.Class.subclass and Uize.Class.declare methods.
Consider the following example...
EXAMPLE
var ClassWithStateMonitors = Uize.Class.subclass ({
staticMethods:{
stateMonitors:function (monitorsByProperty) {
for (var property in monitorsByProperty) {
var monitor = monitorsByProperty [property];
// code to register monitor for state property
}
}
}
});
var Rectangle = ClassWithStateMonitors.subclass ({
stateProperties:{
width:{
conformer:function (value) {return Uize.constrain (value,1,50)},
value:10
},
height:{
conformer:function (value) {return Uize.constrain (value,1,50)},
value:10
}
},
stateMonitors:{
width:function () {
// monitoring code for width state property
},
height:function () {
// monitoring code for height state property
}
}
});
In the above, very hypothetical example, the class ClassWithStateMonitors is being created and a stateMonitors static method is declared for it. This method is a feature declaration method for a new "state monitor" feature type. Nevermind what this feature does - it's a hypothetical example, after all.
Now, given this new ClassWithStateMonitors class, we can now create subclasses by calling ClassWithStateMonitors.subclass, and for any subclasses of it that we create, we can declare any number of "state monitor" type features by specifying a stateMonitors property in the featuresByTypeOBJ parameter that is passed to the ClassWithStateMonitors.subclass method.
In our hypothetical Rectangle subclass, we're declaring "state monitors" for the width and height state properties. Once again, let's not worry about what this mysterious "state monitor" feature does - the point is that we've created a new feature type at a particular class in the class hierarchy, and all classes that descend from that class can now declare features of that new type.
For concrete examples of class-specific feature types, you can refer to the Uize.Service and Uize.Widget.V2 classes. The Uize.Service class introduces a serviceMethods feature type for the benefit of its subclasses, while the Uize.Widget.V2 class introduces the cssBindings and htmlBindings feature types.
7. Constructor Mechanism
When an instance of a UIZE class is created, construction involves three phases: the alphastructor phase, the state property initialization phase, and the omegastructor phase.
7.1. Alphastructor
The alphastructor is a function that is executed before the state properties for an instance are initialized, and before the omegastructor function is executed.
An alphastructor is declared by specifying a value for the alphastructor property of the feature declaration object when calling the superclass' subclass method.
EXAMPLE
var MyClass = Uize.Class.subclass ({
alphastructor:function () {
this._someArray = [];
}
});
Code inside the alphastructor can set up private "scaffolding" type properties that need to be already in existence by the time any of the state properties are initialized. This allows onChange handlers for the state properties to count on certain foundation having been built.
Because of when it is executed, code inside the alphastructor function cannot rely on the instance's state properties being initialized to the values passed to the constructor.
EXAMPLE
var
MyClass = Uize.Class.subclass ({
alphastructor:function () {
alert (this.get ('foo'));
}
}),
myClass = new MyClass ({foo:'bar'})
;
In this example, when the new instance of MyClass is created with the value 'bar' specified for its foo state property, the alert inside the alphastructor will display the value undefined. This is by design and is an important nuance to take note of.
7.1.1. Alphastructor Chain
.
7.2. Omegastructor
The omegastructor is a function that is executed after the alphastructor function is executed, and after the state properties for an instance are initialized.
7.2.1. Omegastructor Chain
.
8. Inheritance
The Uize.Class base class provides a mechanism for inheriting features declared for the class being subclassed, including the alphastructor, omegastructor, static methods, static properties, instance methods, instance properties, state properties, etc.
The inheritance system that is implemented in the Uize.Class base class overcomes some of the weaknesses of a superficial prototype-based approach to inheritance.
8.1. Overriding Inherited Features
Features that are inherited from a superclass can be overridden in exactly the same way as they are declared.
8.1.1. Overriding Instance Methods and Properties
Overriding instance methods and properties of a superclass is easy. It's just a matter of re-assigning.
EXAMPLE
var MyClass = Uize.Class.subclass ({ // create a class with an instance method
instanceMethods:{
foo:function () {alert ('bar')}
}
});
var MySubclass = MyClass.subclass ({ // create subclass and override inherited instance method
instanceMethods:{
foo:function () {
alert ('foo');
MyClass.doMy (this,'foo'); // call superclass' version of foo instance method
}
}
});
var myClassInstance = new MyClass; // create an instance of the class
myClassInstance.foo (); // call foo method on instance of class
var mySubclassInstance = new MySubclass; // create an instance of the subclass
mySubclassInstance.foo (); // call foo method on instance of subclass
In this example, the foo instance method of the subclass has been overrided so that it first alerts the text "foo" and then calls the foo method implementation from the superclass and alerts the text "bar".
Typically, the code that implements the methods of a subclass is near (or in the same scope as) the code that creates the subclass, so the subclass knows its superclass. When you start digging into subclass modules, you will encounter this reference to the superclass in the form of the _superclass variable (named such by convention).
8.1.2. Overriding Static Methods and Properties
Overriding static methods and properties of a superclass is easy. It's just a matter of re-assigning.
EXAMPLE
var MyClass = Uize.Class.subclass ({ // create a class with a static method
staticMethods:{
myStaticMethod:function () {alert ('foo')}
}
});
var MySubclass = MyClass.subclass ({ // create subclass, overriding inherited static method
staticMethods:{
myStaticMethod:function () {alert ('bar')}
}
});
myClass.myStaticMethod (); // call myStaticMethod on MyClass
mySubclass.myStaticMethod (); // call myStaticMethod on MySubclass
In this example, MySubclass is a subclass of MyClass, and both classes have a static method myStaticMethod. By subclassing MyClass, MySubclass inherits the implementation of myStaticMethod from MyClass. Re-assigning the myStaticMethod property of MySubclass overrides the inherited implementation without affecting MyClass. So, calling myStaticMethod on MyClass will product the text "foo" in an alert dialog, while calling myStaticMethod on MySubclass will product the text "bar" in an alert dialog.
8.1.2.1. Calling a Subclass Version of a Static Method
One may implement a class in such a way that a static method is intended to be overrided by a subclass. And in such cases, one may also wish the superclass' implementation to be guaranteed to always use the subclass' version of the static method.
Calling a subclass' version of a static method can be done in two ways, depending on whether the code is in the implementation for an instance method or for a static method of the superclass.
8.1.2.1.1. The Instance Method Case
Within an instance method's implementation, one can use the Class instance property to reference the actual class of the instance - even in the superclass' code - as in...
var MyClass = Uize.Class.subclass ({
instanceMethods:{
myInstanceMethod:function () {
this.Class.myStaticMethod (); // call myStaticMethod static method on subclass
}
}
});
Now, in the case of an instance of MyClass, the myStaticMethod static method will be called on MyClass. However, in the case of an instance of MySubclass (that is a subclass of MyClass), the myStaticMethod static method will be called on MySubclass, even though the myInstanceMethod instance method is implemented by MyClass. If MySubclass didn't override the implementation inherited from MyClass, then there will be no difference in the outcome.
Let's take a look at an example...
EXAMPLE
var MyClass = Uize.Class.subclass ({ // create a class
staticMethods:{
myStaticMethod:function () {
alert ('MyClass');
}
},
instanceMethods:{
myInstanceMethod:function () {
this.Class.myStaticMethod ();
}
}
});
var MySubclass = MyClass.subclass ({ // subclass the class, overriding static method
staticMethods:{
myStaticMethod:function () {
alert ('MySubclass');
}
}
});
var mySubclassInstance = new MySubclass; // create instance of MySubclass
mySubclassInstance.myInstanceMethod (); // call myInstanceMethod
In this example, MyClass implements the static method MyClass.myStaticMethod. Now, MySubclass overrides the implementation of myStaticMethod inherited from MyClass. The implementation of myInstanceMethod (inherited from MyClass) uses the Class property to get a reference to the actual class of an instance on which the method is being called. So, when this method is called on the instance of MySubclass named mySubclassInstance, the overrided form of myStaticMethod (implemented by MySubclass) is called, resulting in the text "MySubclass" being displayed in the alert dialog - not the text "MyClass".
8.1.2.1.2. The Static Method Case
Within a static method's implementation, one can use the this keyword to reference the actual class - even in the superclass' code - as in...
MyClass.myStaticMethod = function () {
this.myOtherStaticMethod ();
};
Let's take a look at an example...
EXAMPLE
MyClass = Uize.Class.subclass ({ // create a class with two static methods
staticMethods:{
myStaticMethod:function () {
this.myOtherStaticMethod ();
},
myOtherStaticMethod:function () {
alert ('MyClass');
}
}
});
MySubclass = MyClass.subclass ({ // create subclass, overriding inherited static method
staticMethods:{
myOtherStaticMethod:function () {
alert ('MySubclass');
}
}
});
MySubclass.myStaticMethod (); // call inherited static method
In this example, MyClass implements the two static methods MyClass.myStaticMethod and MyClass.myOtherStaticMethod. The implementation of MyClass.myStaticMethod wants to always call the subclass' version of myOtherStaticMethod. So, instead of calling it as MyClass.myOtherStaticMethod (), it calls it as this.myOtherStaticMethod ().
Now, if myStaticMethod is called on MyClass, then this will be a reference to MyClass. However, if myStaticMethod is called on MySubclass, then this will be a reference to MySubclass. Consequently, using this guarantees that the subclass' version will be called. In this example, MySubclass overrides the implementation of myOtherStaticMethod inherited from MyClass, so the statement MySubclass.myStaticMethod () will result in the text "MySubclass" being displayed in the alert dialog - not the text "MyClass".
8.1.3. Overriding State Property Values
The initial value for a state property inherited from a superclass can be overrided by calling the MyClass.set static method that is inherited from the Uize.Class base class.
The initial value for a state property is declared by specifying the value property in the profile for the state property, as follows...
EXAMPLE
var MyClass = Uize.Class.subclass ({ // create a class with a foo state property
stateProperties:{
_foo:{
name:'foo',
onChange:function () {
// code to handle a change in foo's value
},
value:'bar'
}
}
});
var _myClassInstance = new MyClass; // create instance of MyClass
alert (_myClassInstance.get ('foo')); // alert value of foo state property
In the above example, the text "bar" will be displayed in the alert dialog.
Now, the initial value for the foo state property inherited from MyClass can be overrided in a subclass, as follows...
EXAMPLE
var MyClass = Uize.Class.subclass ({ // create a class with a foo state property
stateProperties:{
_foo:{
name:'foo',
onChange:function () {
// code to handle a change in foo's value
},
value:'bar'
}
}
});
var MySubclass = MyClass.subclass ({ // subclass the class, overriding initial value for foo
set:{foo:'NOT BAR'}
});
var _mySubclassInstance = new MySubclass; // create instance of MySubclass
alert (_mySubclassInstance.get ('foo')); // alert value of foo state property
In this example, we have now created a subclass of MyClass named MySubclass. The new subclass inherits the foo state property, along with its initial value of 'bar' that is declared in MyClass. When creating the MySubclass subclass, we use the set declaration to change the initial value of this state property to 'NOT BAR'. This is equivalent to calling MySubclass.set, but we can tuck it neatly into the subclassing statement. Now, the fresh instance of MySubclass that is created, named _mySubclassInstance, will have the initial value 'NOT BAR' for its foo state property, and the alert dialog will display this text.
8.2. Features that All Classes Inherit
As part of the foundation for developing classes in UIZE, the Uize.Class base class provides its subclasses with numerous inherited systems, such as the event system, state properties system, instance creation system, subclassing system, and conditions and derivations.
9. Advanced Topics
9.1. Private vs. Public
By convention, private methods and properties - both instance and static - are distinguished from public methods and properties by prefixing their names with an underscore.
EXAMPLE
var MyClass = Uize.Class.subclass ({
instanceMethods:{
_myPrivateInstanceMethod:function () { // private instance method
// do stuff
},
myPublicInstanceMethod:function () { // public instance method
// do stuff
}
},
staticMethods:{
_myPrivateStaticMethod:function () { // private static method
// do stuff
},
myPublicStaticMethod:function () { // public static method
// do stuff
}
}
});
Technically, such private methods and properties are not thoroughly private and can be accessed as public methods or properties. However, because their names will be changed by the Scruncher when the code is scrunched to minimize its size, one cannot reliably access these methods and properties. This is a kind of "soft privacy".
IMPORTANT
When creating subclasses, it is important not to access methods or properties defined in the superclass with private names. The code will work when it is unscrunched, but there is no guarantee what your subclass will be accessing when all the code is scrunched.
9.2. Non-inheritable Statics
Unless otherwise specified, all static features - methods as well as properties - of a Uize.Class subclass are inherited in turn by their subclasses.
There are times, however, when there is no compelling reason for a static feature of a class to be inherited by its subclasses. In fact, in some cases too many inherited static features can just clutter subclasses in the class hierarchy with cruft that they neither need nor care about. In such cases, the inheritance system of the UIZE JavaScript Framework provides a way to "register" static features of a class as being non-inheritable. This is done using the MyClass.nonInheritableStatics static property.
9.2.1. The MyClass.nonInheritableStatics Static Property
The MyClass.nonInheritableStatics static property is a lookup object, automatically created for a class, in which you can register the static features (methods or properties) of the class that should not be inherited when that class is subclassed.
Each property of the MyClass.nonInheritableStatics lookup object represents a single static feature of the class that should not be inherited by subclasses, where the name of each property should be the name of a static feature (excluding the module name), and the value of each property should be a truthy value (such as true, 1, 'foo', [], {}, etc.). After a class has been created, non-inheritable statics can be registered for that class by assigning properties to the class' MyClass.nonInheritableStatics static property, as shown in the example below...
EXAMPLE
MyClass = Uize.Class.subclass ({
staticMethods:{
someUtilityFunction:function () {
// do something of great utility
}
}
});
MyClass.nonInheritableStatics.someUtilityFunction = 1;
MyClassSubclass = MyClass.subclass ();
alert (MyClassSubclass.someUtilityFunction); // alerts the text "undefined"
In the above example, the MyClass.someUtilityFunction static method of the class MyClass has been registered as a non-inheritable static. This is done by the statement MyClass.nonInheritableStatics.someUtilityFunction = 1. Now, when the MyClassSubclass class is created by calling the MyClass.subclass method, the new subclass that is created does not get the someUtilityFunction static feature. Therefore, the alert statement displays the text "undefined" in the alert dialog.
9.2.1.1. nonInheritableStatics is a Non-inheritable Static
When a class is created, the MyClass.nonInheritableStatics static property is automatically initialized on that class to a fresh object with the value {nonInheritableStatics:1}.
This initial mapping means that the MyClass.nonInheritableStatics static property is, itself, not inheritable by subclasses - subclasses get their own fresh object. So, in our example, when the MyClassSubclass subclass is created, its fresh MyClassSubclass.nonInheritableStatics property does not have an entry for the someUtilityFunction static feature, because it does not have that static feature and the contents of the MyClass.someUtilityFunction object is not inherited by the MyClassSubclass class.
9.2.2. All Types of Statics Can Be Non-inheritable
All types of static features of a class can be registered as non-inheritable - methods as well as properties.
Registering static properties as non-inheritable is accomplished in the same way as for static methods. Consider the following example...
EXAMPLE
MyClass = Uize.Class.subclass ({
staticProperties:{
someStaticProperty:'foo'
}
});
MyClass.nonInheritableStatics.someStaticProperty = 1;
MyClassSubclass = MyClass.subclass ();
alert (MyClassSubclass.someStaticProperty); // alerts the text "undefined"
In the above example, the alert dialog would display the text "undefined", because the MyClass.someStaticProperty static property of the class MyClass has been registered as non-inheritable, so it is not inherited by the MyClassSubclass class that is created by subclassing MyClass.
9.2.3. Utility Functions Need Not Be Inheritable
A good case for making a static method non-inheritable is if it is really just a utility function that happens to be stashed in a class' namespace.
Static methods that are really just utility or helper functions don't care about the context on which they are called. They just happen to be called on a class as a context because they are assigned as properties on a class.
Take, for example, the Uize.Fade.celeration static method of the Uize.Fade class. You may be calling it on the Uize.Fade context, but this utility function's implementation doesn't even reference the this keyword - it just doesn't care about the context on which it is called. You could, in fact, assign this static method to a local variable and then call it using that local variable, and it would behave in just the same way as if it were being called on the class. Consider the following example...
EXAMPLE
var celeration = Uize.Fade.celeration; alert (celeration (.5,.5) (.3)); // alerts the text ".18"
In the above example, our local variable celeration is a function reference. When that local variable is used to call the function in order to generate an acceleration/deceleration curve function, everything works just fine. The alert statement alerts the result of calling the generated curve function with the progress amount of .3, yielding the curve-adjusted blend amount of .18. Because the Uize.Fade.celeration static method doesn't operate on a Uize.Fade subclass or need a Uize.Fade subclass as its context when it is called, there's no compelling reason for it to be inheritable, and so it is not.
9.2.4. When Statics Should Be Inheritable
Whenever a static method or property is intended to be unique to its class context, and it is also desirable for that method or property to be inherited by subclasses, then it should not be registered as non-inheritable.
Examples of some static methods in the Uize.Class base class that are inheritable are the Uize.Class.set, Uize.Class.get, Uize.Class.toggle, Uize.Class.fire, Uize.Class.wire, Uize.Class.subclass, and Uize.Class.stateProperties methods. All of these methods act on the class context. So, for example, calling Uize.Class.subclass is different to calling Uize.Widget.subclass. The Uize.Class.subclass method, which is inherited by all Uize.Class subclasses, uses the context of the class on which it is called. Calling the Uize.Class.subclass method returns a subclass of Uize.Class, while calling the Uize.Widget.subclass method returns a subclass of Uize.Widget - even though both of these methods reference the same function for their implementation (in other words, the statement alert (Uize.Class.subclass == Uize.Widget.subclass) would alert the text "true").
9.2.5. Inheritability of Static Features is Noted in Module References
All static features of modules of the UIZE JavaScript Framework are noted as being either inheritable or non-inheritable in the reference documentation for those features.
The inheritability of the static features of a module is noted in the IMPLEMENTATION INFO notes of the reference documentation for the features in the module's reference. So, for example, if you went to the reference documentation for the Uize.Class.set static method of the Uize.Class base class, you would see it noted that the feature is inherited by subclasses. In contrast, if you went to the reference documentation for the Uize.Fade.blendValues static method of the Uize.Fade class, you would see it noted that the feature is not inherited by subclasses.
9.3. Singletons
The UIZE JavaScript Framework provides support for singletons in the form of the Uize.Class.singleton static method that is implemented in the Uize.Class base class and is inherited by all Uize.Class subclasses.
9.3.1. Every Class Supports Singletons
Because singleton support is implemented in the Uize.Class base class, a singleton for any Uize.Class subclass can be created by calling the singleton static method on the class.
EXAMPLE
var fileSystem = Uize.Services.FileSystem.singleton ();
In the above example, the Uize.Services.FileSystem class is a subclass of the Uize.Service class, which is itself a subclass of the Uize.Class base class. Therefore, a singleton can be created using the Uize.Services.FileSystem.singleton static method that is inherited from Uize.Class.
9.3.2. A Single Singleton
As expected with singletons, multiple separate calls to the singleton static method will return a reference to the same singleton.
EXAMPLE
var fileSystemSingleton1 = Uize.Services.FileSystem.singleton (), fileSystemSingleton2 = Uize.Services.FileSystem.singleton () ; alert (fileSystemSingleton1 === fileSystemSingleton2); // alerts "true"
In the above example, both the fileSystemSingleton1 and fileSystemSingleton2 variables are set to be a singleton of the Uize.Services.FileSystem class. Therefore, when their values are compared using a strict equality, they are found to be equal.
9.3.3. Compelling Use Cases for Singletons
A typical and compelling use case for the singleton feature is with service classes, where one typically wants to share a single instance of a service class amongst multiple disparate users of the service.
In the case of service classes, one may instantiate and set up the singleton of a service in the environment (which will likely be a Web page for services that are to run in Web applications). Then, in other code that is to run in the environment and use the service, the singleton of the service can simply be obtained and used - the service doesn't need to be initialized by all the users because the same shared singleton of the service class is initialized and set up in the environment.
9.3.4. Singleton Scope
As a convenience, the singleton static method provides a way to optionally create singletons in a custom scope.
A scope for a singleton of a class is specified using the optional scopeSTR parameter, as follows...
EXAMPLE
var fileSystemForBuildScripts = Uize.Services.FileSystem.singleton ('buildScripts');
In the above example, a singleton of the Uize.Services.FileSystem class is being created in a scope we have decided to call "buildScripts". Now, any other build script code that wants to get a reference to this singleton can perform the same operation, specifying 'buildScripts' as the value for the scopeSTR parameter. With this kind of arrangement, other code can now use the same Uize.Services.FileSystem service class in a different scope. In different scopes, the different singletons may be set up with different service adapters and initialized differently to perform different I/O.
9.3.4.1. A Single Singleton Per Scope
The exception to the rule of a single singleton is when using the singleton scope feature.
EXAMPLE
var
fileSystemInScopeA = Uize.Services.FileSystem.singleton ('scopeA'),
fileSystemInScopeB = Uize.Services.FileSystem.singleton ('scopeB')
;
alert (fileSystemInScopeA === fileSystemInScopeB); // alerts "false"
In the above example, two singletons of the Uize.Services.FileSystem class are being created: one in a scope we've called "scopeA" and the other in a scope we've called "scopeB". When the values of the fileSystemInScopeA and fileSystemInScopeB variables are compared using a strict equality, they are found to not be equal. However, if we called the singleton method multiple times for "scopeA", we would always get back the same singleton for this scope, and similarly with "scopeB".
9.3.4.2. Designing Classes to Support Singleton Scope
In order to be able to take advantage of the singleton scope feature, your classes need to be designed correctly.
In order to correctly support singletons in different scopes, your class should not store state for a singleton as statics on the class, but should instead use instance properties. Provided your classes are designed correctly, you can have multiple singletons instantiated for different scopes that you define, so that different sets of distributed code can share their own singleton instances, and for each differently scoped singleton the singleton may be initialized or set up in a different way.
9.3.4.3. Singleton Scope is Globalish
A singleton scope is effectively global for a given class (so, not global in an absolute sense, but globalish).
Therefore, if you have distributed code that is making use of the singleton feature and you are specifying a custom scope, you need to have a good degree of confidence that you won't have totally different code trying to use the same scope name for a singleton of a class where the initialization and usage of the singleton is different. This is the age old problem with things that are global in nature. The globalness of singletons is both the upside and the downside, so you should use the singleton feature sparingly.
9.3.5. Initial State for Singletons
A singleton can be created with an initial state by specifying the optional propertiesOBJ second parameter.
EXAMPLE
var fileSystemForBuildScripts = Uize.Services.FileSystem.singleton ('',{defaultEncoding:'utf8'});
In the above example, a singleton is being created for the Uize.Services.FileSystem class. Because the first parameter of the singleton method is the singleton scope, if we want to use the default scope we need to specify an empty string. The second parameter is the state for the singleton, where the value is an object that will be passed to the constructor of the class if the singleton has not yet been created for the scope, or used in a call to the set method if the singleton already exists in the scope.
9.3.6. Use the Singleton Feature Sparingly
While there are a few compelling use cases for singletons, one should nevertheless use the singleton feature sparingly and with caution.
The very advantage of singletons, which is that they can be shared by disparate pieces of code, can also become a disadvantage. Singletons can be regarded as being, in effect, globals in the scope of the class on which they are created, or the singleton scope in which they are created. This consequence brings with it the kinds of drawbacks that globals have more generally.
Singletons aren't as egregious as global variables, and the singleton scope facility goes some way to alleviating the globalish nature of singletons. Nevertheless, before resorting to using singletons, consider first if there are other alternatives. In UIZE, you will find singletons mostly advocated for use with service classes.
9.4. The "no new" Mechanism
The JavaScript new operator is optional when creating instances of Uize.Class subclasses, and you can make the new operator optional for your own object constructors using the newly added Uize.noNew static method.
9.4.1. Creating Instances of Uize Classes
Because the Uize.Class base class utilizes the "no new" mechanism, one can create instances of any Uize.Class subclass either using the new operator or not.
EXAMPLE
// this works
var mySlider1 = new Uize.Widget.Bar.Slider ({minValue:0,maxValue:100,value:50});
// Look ma, no "new"!!!
var mySlider2 = Uize.Widget.Bar.Slider ({minValue:0,maxValue:100,value:50});
9.4.2. All Uize Classes Get the Benefit
Because of the way in which the "no new" mechanism is implemented in the Uize.Class base class, any class that is derived from a Uize.Class base class using the subclass method gets the same benefit, including classes that you create for your own applications.
This means, for example, that any widget class you create by subclassing the Uize.Widget class will get the same benefit. Consider the following example...
EXAMPLE
// we create a widget class var MyWidgetClass = Uize.Widget.subclass (); // this works var myWidgetClassInstance1 = new MyWidgetClass (); // Look ma, no "new"!!! var myWidgetClassInstance2 = MyWidgetClass ();
9.4.3. Applies for Other Uize Objects
The "no new" mechanism, that is implemented in the Uize.noNew static method, has been applied to various other Uize objects (such as the Uize.Color object) that are lightweight objects rather than full Uize.Class subclasses.
So, for example, one can create instances of the Uize.Color object or the Uize.Str.Builder object without needing to use the new operator. Consider the following example...
EXAMPLE
// this works
var fuchsia = new Uize.Color ('#ff0fff');
// Look ma, no "new"!!!
var olive = Uize.Color ('#808000');
9.4.4. Using the Uize.noNew Method
An object constructor that supports the "no new" mechanism can easily be created using the Uize.noNew static method.
In cases where you're creating Uize.Class subclasses, you don't need to worry about the Uize.noNew method because the "no new" mechanism is built right into the Uize.Class base class, so all Uize classes get the benefit. However, in cases where you're defining your own lightweight objects, you can use the Uize.noNew method to create an object constructor where the new operator is optional. Consider the following example...
EXAMPLE
// define the Food object
var Food = Uize.noNew (
function (name,type) {
this.name = name;
this.type = type;
}
);
// create an instance of Food using the new operator
var apple = new Food ('apple','fruit');
alert (apple.type); // alerts the text "fruit"
// create an instance of Food without using the new operator
var rice = Food ('rice','grain');
alert (rice.type); // alerts the text "grain"
What you'll notice from the above example is that the Uize.noNew method is quite simple - it takes a single parameter, which is the constructor function that initializes the new instance. This means that you can easily take an existing object constructor function and upgrade it to one that supports the "no new" mechanism by wrapping it inside a call to the Uize.noNew method, which then returns a wrapper constructor that becomes your new object constructor. Consider the following before-and-after example...
BEFORE
// must always use "new" with this constructor
function Food (name,type) {
this.name = name;
this.type = type;
}
AFTER
// "new" is optional with this constructor
var Food = Uize.noNew (
function (name,type) {
this.name = name;
this.type = type;
}
);
Notice that you need to assign the result of the Uize.noNew method call, and so your original constructor function no longer should have the name.