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.