2.1.4 接口
qooxdoo支持和Java相似的接口类型,接口定义看起来和类的定义非常类似。
例子:
qx.Interface.define("qx.test.ISample",{
extend: [SuperInterfaces],
properties: {"color": {}, "name": {} },
members:
{
meth1: function() {},
meth2: function(a, b) {
this. assertArgumentsCount(arguments,2, 2);
},
meth3:function(c) {
this. assertInterface(c,qx.some.IInterface);
}
},
statics:{
PI: 3.14
},
events :{
keydown: "qx.event.type.KeyEvent"
}
});
对接口的实现
在类声明中使用implement关键字引入一个或多个接口后,这个类就必须实现接口的所有的属性、成员、事件,否则系统就会抛出错误。
具体使用示例如下:
qx.Class.define("qx.test.Sample", {
implement: [qx.test.ISample],
properties: {
"color": { check: "color"},
"name": { check: "String"}
},
members:
{
meth1: function() { return 42; },
meth2: function(a, b) { return a+b },
meth3: function(c) { c.foo() }
}
events :
{
keydown : "qx.event.type.KeyEvent"
}
});
验证
qx.Class有几个静态方法来检查类或对象对接口的实现:
• qx.Class.hasInterface(): 检查给的类或它的父类是否已经引入接口这个接口
qx.Class.implementsInterface(): 检查在接口中所有的方法定义在类是是否全部实现,给出不需要类实现的接口。
它还可以检查属性方法的设置。
2.1.5 Mixins
Mixin是可以被合并到其他类的一些代码和变量的集合,它们和类比较像,但不可实例化。它们和接口不同,因为它们包含的功能的实现代码。他们通常是由少数几个成员组成,实现一些特定的功能。
Mixin用于共享功能,不能继承。它们通常被用来对现有的类进行扩展或进行功能补充。
下面是一个Mixin的定义示例:
qx.Mixin.define("name", {
include: [SuperMixins],
properties: {
"tabIndex": {check: "Number", init: -1}
},
members:
{
prop1: "foo",
meth1: function() {},
meth2: function() {}
}
});
用法
这是一个简单的例子,演示的如何将MMixinA, MMixinB合并到类ClassC中。
第一个mixin:
qx.Mixin.define("demo.MMixinA", {
properties: {
"propertyA": {
check: "String",
init: "Hello, I’m property A!\n"
}
},
members:
{
methodA: function(){
return "Hello, I’m method A!\n";
}
}
});
第二个mixin:
qx.Mixin.define("demo.MMixinB", {
properties: {
"propertyB": {
check: "String",
init: "Hello, I’m property B!\n"
}
},
members: {
methodB: function() {
return "Hello, I’m method B!\n";
}
}
});
合并Mixin的类:
qx.Class.define("demo.ClassC", {
extend : qx.core.Object,
include : [demo1.MMixinA, demo1.MMixinB],
members : {
methodC : function() {
return this.getPropertyA() + this.methodA()
+ this.getPropertyB() + this.methodB()
+ "Nice to meet you. Thanks for your help!";
}
}
});
当使用下面的方法调用ClassC类的methodC()方法就会得到下面的结果:
var classC = new demo.ClassC;
var result = classC .methodC();
/*
* Result:
* Hello, I’m property A!
* Hello, I’m method A!
* Hello, I’m property B!
* Hello, I’m method B!
* Nice to meet you. Thanks for your help!
*/
2.2 属性
2.2.1 属性介绍
Qooxdoo内置了一个非常容易使用和先进的类属性变量管理系统。为了理解它的先进性,我们先来看看纯JavaScript如何对内部属性进行管理。
通用属性处理
假设我们的对象obj有一个宽度的属性。
如果你对常规高级程序设计语言熟悉的话,你不应该用下面的方法直接访问对象属性:
obj.width = 200; // setting a value
var w = obj.width; // getting the current value
相反,你应该通过调用属性的访问方法(getter)和赋值方法(setter)对对象的属性进行管理:
obj.setWidth(200); //setting a value
var w = obj.getWidth(); //getting the current value
当然,直接访问属性可能会更快,因为这样不需要通过调用函数来对属性进行间接管理。尽管如此,在实践中间接管理属性并不是一个不好的做法,因为间接访问属性可以隐藏内部实现细节,大大提高了代码的可维护性(就好像你不会在编写web程序时使用汇编代码一样,虽然汇编代码速度更快)。典型的实现取值和赋值的方法看起来像下面的代码,在类定义的成员部分声明取值和赋值方法:
members: {
getWidth : function(){
return this._width;
},
setWidth : function(width){
this._width = width;
return width;
}
}
和Java或其他高级编程语言中的取值赋值访求非常类似。不过,即使是一个很简单的属性变量也需要大量的编码录入,这看起来是不是很不方便。而更先进的功能,如属性的类型检查,性能优化,当属性的值发生变化时触发事件等等都需要通过手工编码来完成。如下面的例子:
members:{
setWidth:function(width){
If(typeof width!="number"){//Make sure it is a valid number
Throw new Error("Invalid value: Need a valid integer value:" + width);
};
if (this._width != width){ //Optimization:Only set value,if different from the existing
this._width = width;
//User code that should e run for the new value
this.setStyleProperty("width", width+"px");
};
return width;
}
}
大部分的代码是对传入的数据进行验证和存储管理。而对属性操作的用户代码很短。
Qooxdoo属性处理
让我们看看上面的例子如果使用qooxdoo属性实现如何编写代码。属性是在类定义的properties 部分进行声明。只有对属性进行特殊设置才需要setter代码,而值发生改变时会触发一个apply事件:
properties : {
width : { check : "Number", apply : "applyWidth" }
}
members :{
applyWidth : function(value){
this.setStyleProperty("width", value + "px");
}
}
相比之下,前面示例的冗长代码变的更短更清晰明了,也客观的反映了事物的实质。
类的每一个属性都可以定义一个apply方法。一旦你为apply键定义了映射方法,那么这个属性的值发生改变时(属性初始化不会)就会触发你给定的方法。如果属性没有定义apply,那么系统只会对属性进行简单处理以及基本存储。
尽管还需要对每个属性写一些代码,但它实际上只需要非常少的代码就可以完成尽可能多的功能:如属性类型是自动检查的(在上面的示例中),此外,只有当属性的值发生改变时新值才会被存储(调用apply声明的方法)。这是一个很小但非常重要的优化。
Change Events(变化事件)
qooxdoo是基于事件的编程框架。而属性发生变化时触发事件就是这个概念很好的一个例子。
每个属性都可以选择一个监听器来观察属性的变化。这意味着在任何时候只要属性发生改变系统就会自动触发一个事件(qx.event.type.Data实例),触发的事件是通过在属性定义中的event关键字来定义的,你可以为被触发的事件命名一个任何你喜欢的名称,但qooxdoo框架有一个约定俗成的命名规范:"change + Propertyname",如:“changeWidth”就是宽度属性发生改变时被触发事件名称。为了在值发生变化时得到通知的,你只需将一个事件侦听器附加到对象实例上来监听这个事件就可以了。
例如:你为Widge的一个实例widget定义一个属性element,并且定义了一个event事件changeElement。当element发生改变时系统就会自动触发changeElement事件。
properties:{
element:{ event: "changeElement" }
}
如果上面的事件被触发,系统会象下面代码所示设置DOM原素的内容:
widget.addEventListener("changeElement",function(e){
e.getValue().innerHTML="Hello World";
});
在上面的代码中匿名函数作为一个事件处理程序接收事件对象,并将接收的对象作为变量调用预定义的方法getValue()返回属性的新值。
可用的方法
qooxdoo的properties不仅确保所有属性的行为具有一致性,也保证了对属性的访问和赋值使用相同的API方法,而且方法的名字是很容易理解。每个属性创建(至少)以下的设定方法:
• setPropertyName(): 赋值方法 (“setter”)为属性设置一个新值。
• getPropertyName(): 访问方法 (“getter”)返回属性的当前值。
此外,对于布尔类型的所有属性(定义时chech:“Boolean”)还提供以下的方法:
• isPropertyName(): 和getPropertyName()方法相同
• togglePropertyName(): 在true和false两值之间变换
属性组
Qooxdoo系统可以使用一步调用来设置多个属性的值。如qx.ui.core.Widget有一个属性组padding。如设置padding组中的四个属性值paddingLeft, paddingRight, paddingTop和 paddingBottom。
widget.setPadding(10, 20, 30, 40);
这一个调用所得到的结果和下面代码的结果一样
widget.setPaddingTop(10);
widget.setPaddingRight(20);
widget.setPaddingBottom(30);
widget.setPaddingLeft(40);
从上面的示例可以看到属性组的方便性。
简写支持
另外,属性组也支持一些像CSS简写方式一样的处理。意思就是说在一个调用中你可以只定义几个边缘数据,其它的让系统自动填充:
widget.setPadding(top,right,bottom,left); //four arguments
widget.setPadding(top,right+left,bottom); //three arguments
widget.setPadding(top+bottom,right+left); //two arguments
widget.setPadding(top+right+bottom+left);//one argument
正如你看到的,这也可以减少代码量,使它更容易使用。
顺便说一句:一个属性组的值也可以使用数组做为第一个参数来使用如下面的两条代码执行相同的功能:
widget.setPadding(10,20,30,40);//arguments list
widget.setPadding([10,20,30,40]); //first argument as array
2.2.2 属性的其它详细信息
声明
下面的代码创建一个名为myProperty的属性并自动生成相应setMyProperty()和getMyProperty()方法。
qx.Class.define(“A”,{
properties : {
myProperty : { nullable : true }
}
})
在属性定义中至少要有一个初始化的键值,可以是nullable或 inheritable。否则,第一次调用getter系统将系统停止运行并抛出一个异常,因为属性的值还没有定义。
注:作为一种初始化键值的替代方式,可以在类的构造函数中通过调用初始化函数来完成属性的初始化,如上面属性的初始化函数为:initmyproperty(value)。
处理属性值的变化
你可以使用多种方法来处理每个属性的变化(只有当新的赋值与原来的值不相同时才意味着属性发生了改变)。为了处理属性的变化,作为类的开发人员最简单也是性能最高的解决方案是定义一个apply方法进行处理。做为类的使用者(也就是类的实例的创建者)如果在属性声明中提供了属性变化相对应的事件,那么最好也是最简单的方法就是为类的实例添加一个监听器来监测属性变化事件的发生。
定义一个apply方法
在属性的设置中添加一个apply关键字,并为它添加一个处理方法,处理方法指向一个函数名称,这个函数必须在类的members节中进行定义,通常这个函数不能被直接调用,所以将它的访问控制设置为保护是一个好主意(函数名称的第一个字母为下划线_)。
apply方法的返回值会被忽略,第一个参数是实际值,也就是新值,第二个参数是原来的旧值,最后一个参数是属性的名称,对于这样的参数传递可以方便你使用一个函数过程来处理多个属性的变化。第二个和第三个参数是可选的,可以不进行设置。
例如:
properties : {
width : {apply:"_applyWidth"}
},
members : {
_applyWidth:function(value,old,name){
// do something...
}
}
只有当属性的值改变时apply方法才会被调用。
注意:当属性的值使用的数据类型为引用类型(如:对象或数组)时,apply方法也会被调用,因为这些对象和值也是不同的。这是JavaScript的一种功能,不是qooxdoo特殊性。(更多的技术说明请参阅API文档的中的qx.core.Property)
提供一个事件接口
在多数情况下,对于使用类的用户为类实例定义一个事件来处理属性的变化是一个不错的做法。事件定义使用event关键字,它的值就是事件触发的名称。
qooxdoo触发qx.event.type.Data类型的事件时,允许用户使用getData()和getOldData()方法分别访问属性的新值和原值。
注意:对于属性来说事件应该是唯一对外公开的,所以事件的访问控制定义为保护或私有不是一个好主意。
例如:
properties:{
label:{event:"changeLabel"}
}
// later in your application code:
obj.addListener("changeLabel",function(e){
alert(e.getData());
});
初始值
所有的属性都支持值的初始化。初始化的值被属性引擎分别进行存储。当使用reset方法时,属性的值可以被退回到初始状态。
定义一个初始值
设置属性的初始值有两种方法。
设置属性的初始值首选的方法是在属性的配置中通过init关键字设置,用户可以单独使用这个关键字,也可以结合nullable(与/或)inheritable使用。
properties : {
myProperty : { init : "hello" }
}
另一种方法是在类的构造函数中设置属性的初始值,这种方法只有在第一种访求不能满足需求的情况下才推荐使用。
在构造函数中使用初始化函数this.initMyProperty(value) 允许你为属性设置复杂的非基本数据类型的值(也就是引用类型的数据),这些数据不允许在实例间共享,它们是实例级唯一的。另一种情况就是当使用国际化程序时使用地方化的初始值,因为this.tr()在类定义中不能使用,而在构造函数中你却可以使用静态函数qx.locale.Manager.tr() 或者使用this.tr() 方法对属性进行地方化初始值设置。
注意:在属性中加入deferredInit:true设置,可以延迟上面所述的引用类型的初始化。
qx.Class.define("qx.MyClass",{
construct: function(){
this.initMyProperty([1, 2, 4, 8]);
},
properties:{
myProperty:{deferredInit:true}
}
});
应用初始化值
可以使用用户自定义的应用方法来应用属性的初始值。为此用户可以在构造函数调用init方法:this.initMyProperty(value)——这个方法改变了属性的值所以将触发apply方法的调用。当然,只有属性定义中存在apply或event入口的情况下才会发生这种情况。
如果你不使用init方法来初始属性的值,那么你必须确保从类中创建实例时属性的值是一致的。即使属性没有初始化,getter也可以返回初始化值,在某些情况下这种情况是允许的,如属性没有定义apply或event。但有些情况下,开发人员就需要小心的调用init方法了,否则getter就会返回内部状态不一致的错误信息(这是由于init和apply的值不一致造成的)。
像调用this.initMyProperty(value)方法一样,您可以使用用户定义的初始值作为参数调用setter方法。这时系统也会调用属性的apply方法,而不像平时的情况下,如果属性值已经设置了相同的值,就不调用apply。
construct:function(){
this.base(arguments);
this.setColor("black");//apply will be invoked
this.setColor("black");//apply will NOT be invoked
},
properties:{
color:{
init: "black",
apply: "_applyColor"
}
},
members :{
_applyColor:function(value,old){
// do something...
}
}
这个例子说明了由于前面提到的初始值和应用值的不一致性导致了属性默认动作不同。
construct:function(){
this.base(arguments);
this.initColor();//Initialize color with predefined value
this.initStore([]);//Initialize store with empty array
},
properties:{
color:{
init:"black",
apply:"_applyColor"
},
store:{
apply:"_applyStore"
}
},
members:{
_applyColor:function(value,old){
// do something...
},
_applyStore:function(value,old){
// do something...
}
}
在上面的例子中,你可以看到属性的不同的使用方法及如何设置初始值。如果你不想在不同实例之间共享“引用类型”(如数组,对象),那么属性初始值必须在构造函数设置,而不是在属性的声明中确定。
利用翻译软件并通过自己的理解做出以上文章,如有不妥,欢迎指正chshj@126.com