之前主要是Entity的一个大概流程,本文主要介绍Cesium的属性,比如defineProperties,Property(ConstantProperty,CallbackProperty,ConstantPositionProperty)以及createPropertyDescriptor的相关内容,研究一下Cesium对Object的属性设计和使用方式。

我们以Entity为例,看看它是如何封装自己的属性:

function Entity(options) {
var id = options.id;
if (!defined(id)) {
id = createGuid();
}
this._id = id;
this._name = options.name;
this._description = undefined;
this._position = undefined;
this._rectangle = undefined;
} // Key 1:defineProperties
defineProperties(Entity.prototype, {
id : {
get : function() {
return this._id;
}
},
// Key 2:createRawPropertyDescriptor
name : createRawPropertyDescriptor('name'),
// Key 3:createPropertyDescriptor
description : createPropertyDescriptor('description'),
// Key 4:createPositionPropertyDescriptor
position : createPositionPropertyDescriptor('position'),
// Key 5:createPropertyTypeDescriptor
rectangle : createPropertyTypeDescriptor('rectangle', RectangleGraphics)
});

如上,我截取了五个属性:id,name,description,position,rectangle。每个属性的创建方式可以说大同小异。下面看看用户是如何使用Entity的这些属性:

var obj = new Cesium.Entity();
var id = obj.id;
obj.id = 123; // 不起作用
obj.name = "Hello World"
obj.description = "empty entity";

如上说明亮点,Cesium中的成员变量和属性方法之间就是一个下划线_,比如成员变量为_id,对应的属性方法为id,这是Cesium中的一个规范。另外这些属性也是提供了set&get方法来进行赋值和取值,所以可以直接通过=运算符。下面我们由易到难,分别介绍。

defineProperties

Cesium.defineProperties本质上就是Object.defineProperties。我们不妨看一下它的定义:

Object.defineProperties(obj, props)

结合之前Entity.id代码,我们大概可以掌握通过Object.defineProperties封装多个属性的方式。通过下面这个范例,我们来强化一下理解:

function obj(){
this._id = undefined;
} var o1 = new obj(); Object.defineProperties(obj.prototype, { // or o1
"property1": {
//value: "Hello",
//writable:true
set: function(value){
this._id = value;
},
get: function(){
return this._id;
}
},
"property2": {
value: "Hello",
writable: false,
configurable:false
}
// etc. etc.
}); o1.property1 = 100;
o1._id = 150;
var nValue = o1.property1;
o1.property2 = 200;// writable属性为false,赋值无效

如上的代码中,我们创建了obj这个function,然后对obj.prototype这个对象创建了两个属性property1&property2,两个属性创建的方式略有不同,我是想通过这种不同来强调需要注意的地方,大家在编码的时候还是要遵守一种风格,保持一致性。首先,props参数,value,set&get,writable,confugurable以及enumerable。value和set&get都是用来赋值的,两种方式是有冲突的,二选一,两中方式略有不同,个人理解如果采用set&et,property1就是_id的引用。其次,writable,confugurable以及enumerable三个属性的默认值为false,具体的作用可以参考API,当然通过字面意思也不难理解。最后,defineProddrties的第一个参数为obj,所以可以使用原型或new一个obj出来,Cesium使用的是前者,用不用原型对应的原型链还是有一些差别的。下面是对比图,前者是obj+set&get的方式,set和get也是obj实例的,后者是prototype+set&get,property1为引用,而set&get是prototype的实例。想一下,假如是prototype+value呢?这个其实是prototype的内容,我们就不过于跑题了。

这样,通过defineProperties的属性封装,我们保证了每一个Entity中,其原型中,对每一个属性都对应一个set和get方法的能力(需要与否则看实际情况),同时属性值对应function中的实例(而不是原型中提供该属性)。

Property

在进入createProperty这类方法前,我们先了解一下CesiumProperty,然后在一步步的展开。首先,个人认为这些Property在本质上和defineProperties并无本质的区别,defineProperties直接封装一些基本类型,如果是Object,这种accessor是引用的形式。因此,通过Property的封装,将引用或复制的权利交给Object的设计者,同时提供一些特殊功能,满足特殊的需求,比如SampledProperty提供插值功能,MaterialProperty则专门针对材质。这里我们主要通过两个有代表性的,Entity中涉及到的Property,了解一下Cesium中Property的实现。

  • ConstantProperty

  • CallbackProperty

ConstantProperty

如下是ConstantProperty的一个精简的代码片段,结合个人的注释,应该能对其有一个大概的认识:

function ConstantProperty(value) {
// 属性值
this._value = undefined;
// 是否提供克隆方法
this._hasClone = false;
// 是否提供判断相等的方法
this._hasEquals = false;
// 提供属性值变化的事件
this._definitionChanged = new Event();
this.setValue(value);
} defineProperties(ConstantProperty.prototype, {
// 属性是否为常量,只读
isConstant : {
value : true
},
definitionChanged : {
get : function() {
return this._definitionChanged;
}
}
});
// 获取属性值的方法,如果提供clone方法,如果没有直接返回_value
ConstantProperty.prototype.getValue = function(time, result) {
return this._hasClone ? this._value.clone(result) : this._value;
};
// 赋值方法,判断是否提供了_hasClone&_hasEquals方法
// 属性值变化会产生event事件
ConstantProperty.prototype.setValue = function(value){}
// 判断是否相等的方法
// 如果_hasEquals为true,则会调用该对象的equal方法来判断
ConstantProperty.prototype.equals = function(other) {}

可见,相比基本类型,ConstantProperty是一个高级版的属性,首先在构造函数的参数中,value可以是一个基本类型,如此,该ConstantProperty会蜕化成defineProperties下直接封装的属性,当然,getValue,setValue以及equals方法本质上也是基本类型之间的赋值和对比,唯一不同的是提供了属性值变化的事件。如果value是一个obj,则用户可以提供clone以及equals方法,满足自己的特殊需要,当然你也可以不提供,那该obj就是默认的赋值和对比操作符。

CallbackProperty

下面是该属性的一个精简版,主要列出和ConstantProperity的不同处,方便对比。

function CallbackProperty(callback, isConstant) {
this._callback = undefined;
this._isConstant = undefined;
this._definitionChanged = new Event();
this.setCallback(callback, isConstant);
}
defineProperties(CallbackProperty.prototype, {
isConstant : {
get : function() {
return this._isConstant;
}
}
});
CallbackProperty.prototype.getValue = function(time, result) {
return this._callback(time, result);
};

首先,ConstantProperity就是传进来一个value,返回该value,就是一个直来直去的思路。但有些时候事情并不是一成不变,比如在不同的状态下,希望返回不同的属性值。这种情况就需要提供回调方法,用户自己提供一个callback函数,CallbackProperty则通过setValue来绑定该回调方法,通过getValue来调用该方法,至于返回的属性值是什么,用户通过该callback方法自己负责。这就是CallbackProperty的应用场景。

Property

当然,除了ConstantProperity和CallbackProperty之外,Cesium还提供了CompositeProperty、SampledProperty、TimeIntervalCollectionProperty、MaterialProperty、PositionProperty、ReferenceProperty等。各个属性内部实现不一,但都会提供setValue&getValue等方法。当然,为了统一标准,Cesium提供了Property类,提供了一套接口来供用户调用:

Property.equals = function(left, right)
Property.arrayEquals = function(left, right)
Property.isConstant = function(property)
Property.getValueOrUndefined = function(property, time, result) {
return defined(property) ? property.getValue(time, result) : undefined;
};

这样,用户可以不必过于纠结具体的property,通过Property提供的方法获取结果,比如上述代码中给出的Property.getValueOrUndefined,内部调用getValue获取属性值。

createPropertyDescriptor

当然,上述的这些Property还是处于Object类型,和obj._id还是同一个级别,和还需要经过封装,提供property1这样的形式,使其符合defineProperties规范,这样才能提供accessor的能力。这就是createProperty函数的作用:

function createProperty(name, privateName, subscriptionName, configurable, createPropertyCallback) {
var obj = {
configurable : configurable,
get : function() {
return this[privateName];
},
set : function(value) {
var oldValue = this[privateName];
value = createPropertyCallback(value); if (oldValue !== value) {
this[privateName] = value;
this._definitionChanged.raiseEvent(this, name, value, oldValue);
} if (defined(value) && defined(value.definitionChanged)) {
this[subscriptionName] = value.definitionChanged.addEventListener(function() {
this._definitionChanged.raiseEvent(this, name, value, value);
}, this);
}
}
};
return obj;
}

如上就是createProperty方法的一个精简版,粗看一下,创建了一个obj并返回,而且该对象有三个成员变量:configurable,set&get(function),和之前介绍的defineProperties.props参数如出一辙,现在可以肯定这个方法的作用就是把各种Property加工成符合defineProperties规范的属性。当然这样的判断并不严谨,感觉的成分更大一些,我们结合Entity.description属性的创建过程具体了解一下。

function Entity(options) {
this._description = undefined;
} defineProperties(Entity.prototype, {
id : {
get : function() {
return this._id;
}
},
description : createPropertyDescriptor('description')
});

如上是Entity.description属性,同时我还保留了id属性,用来参考。如果把createPropertyDescriptor函数替换为createProperty返回的obj,大概如下:

function Entity(options) {
this._description = undefined;
} defineProperties(Entity.prototype, {
id : {
get : function() {
return this._id;
}
},
description : {
configurable : configurable,
get : function() {
// ……
},
set : function(value) {
// ……
}
};
});

这样就确定一定以及肯定了这个结论:createProperty就是返回一个符合defineProperties规范的属性。刨根问底的话,还是有两处疑问,首先,Entity.description调用的createPropertyDescriptor,又是如何传递到createProperty方法。其次,我们只是大概了了解了createProperty,内部具体做了什么还没有关注。逐个击破。

createPropertyDescriptor('description');
function createPropertyDescriptor(name, configurable, createPropertyCallback) {
return createProperty(name, '_' + name.toString(), '_' + name.toString() + 'Subscription', defaultValue(configurable, false), defaultValue(createPropertyCallback, createConstantProperty));
}
function createConstantProperty(value) {
return new ConstantProperty(value);
}

可见,description是该属性的accessor,对应Entity内部的属性变量是'_' + name.toString(),也就是_description,这个是Cesium默认的属性变量和属性accessor接口之间的规范。configurable默认为false,createPropertyCallback如果为null,则采用ConstantProperty类型,对应的是createConstantProperty回调函数,暴露了我C++程序员的身份。这样,createPropertyDescriptor就很自然的过渡到createProperty方法。接着,再看看createProperty内部的实现:

function Entity(options) {
this._description = undefined;
} defineProperties(Entity.prototype, {
id : {
get : function() {
return this._id;
}
},
description : {
configurable : false,
get : function() {
return this[_description];
},
set : function(value) {
var oldValue = this[_description];
var value = new ConstantProperty(value); if (oldValue !== value) {
this[_description] = value;
this._definitionChanged.raiseEvent(this, "description', value, oldValue);
}
}
};
});

此时privatename就是_description,相比id属性,description属性本质上没有差别,只是多了一些繁枝缛节,多了一些异常判断,多了一些Event事件响应,当然还有对value的封装,比如description时,将value封装成了ConstantProperty类型,可以算的上是一个加强版的属性accessor方式。

有了这样一套机制,不用写过多的代码,通过createPropertyDescriptor就可以很好的实现属性accessor的封装,代码间接,同时考虑了很多异常,并会有事件来满足用户的各种情况,提高稳定性。同时createPropertyCallback参数也提供了更多的选择,来满足我们的各类需求,或者没有需求,比如在Entity中,对属性accessor用如下三种形式:

  • createRawPropertyDescriptor

  • createPropertyTypeDescriptor
  • createPositionPropertyDescriptor

有了上面的理解,我们大概做一下说明,应该不难理解他们的差别和不同的作用,我们在自己的使用场景中可以因地制宜。

createRawPropertyDescriptor&createPositionPropertyDescriptor

function createRawProperty(value) {
return value;
} function createRawPropertyDescriptor(name, configurable) {
return createPropertyDescriptor(name, configurable, createRawProperty);
}
function createConstantPositionProperty(value) {
return new ConstantPositionProperty(value);
} function createPositionPropertyDescriptor(name) {
return createPropertyDescriptor(name, undefined, createConstantPositionProperty);
}

这个没啥可说的吧,RawProperty比ConstantProperty还简单,人如其名,不加任何粉饰。同理createPositionPropertyDescriptor和createPropertyDescriptor也差不多,只是Property不是默认的ConstantProperty,而是ConstantPositionProperty。

createPropertyTypeDescriptors

rectangle : createPropertyTypeDescriptor('rectangle', RectangleGraphics)
function createPropertyTypeDescriptor(name, Type) {
return createPropertyDescriptor(name, undefined, function(value) {
if (value instanceof Type) {
return value;
}
return new Type(value);
});
}

这个稍有不同,通过闭包的方式,这时对应的createPropertyCallback是用户自定义的,他甚至不是一个Property而是一个function类。我们可以这样理解,createPropertyDescriptor只是负责封装实现属性accessor,createPropertyCallback原意是让用户创建各类Property对象,但在Entity里面扩展了一下,并没用严格规定必须是Property类,而是可以用其他的function类,让createPropertyDescriptor的使用场景更广泛了。下面这种写法是否看起来就亲切很多,只是因为这类Graphics太多,所以Cesium用模版的思想抽象为Type参数,简化编码提高稳定,这也是值得我们学习的一点,Cesium在代码质量上的标准还是很严格的。

function createRectangleGraphics(value) {
return new RectangleGraphics(value);
}
function createPropertyTypeDescriptor(name) {
return createPropertyDescriptor(name, undefined,createRectangleGraphics);
}

CallbackProperty

刚才我们已经比较全面的介绍了Property相关的各类情况,尽管我们提到了CallbackProperty,但并没有涉及如何使用,有点遗忘的再回去看看CallbackProperty的实现,重点是getValue方法,我们下面通过一个例子,看看如何通过CallbackProperty实现自定义效果,范例链接

function createDescriptionCallback() {
var description = "Hello,World, ";
return function(time, result) {
return description + time.toString();
};
} entity.description = new Cesium.CallbackProperty(createDescriptionCallback(), true); Property.getValueOrUndefined(entity.description,time);

当然,得益于之前Entity.description通过createPropertyDescriptor提供了accessor接口,这里,等号运算符会调用set方法,只是将oldValue(ConstantProperty)更改为value(CallbackProperty)。此时,内部通过Property.getValueOrUndefined->CallbackProperty.getValue->this._callback,实现回调函数的调用。

总结

如上是Cesium在Property模块的一些实现原理和使用方式,我们简单总结一下知识点如下,不知道大家有什么收获,是否能够从Cesium优雅的设计和封装中学到什么奇技淫巧。

  • Property

  • ConstantProperty
  • CallbackProperty
  • defineProperties
  • createPropertyDescriptor
  • createRawPropertyDescriptor
  • createPositionPropertyDescriptor
  • createPropertyTypeDescriptor

Cesium原理篇:Property的更多相关文章

  1. Cesium原理篇:5最长的一帧之影像

    如果把地球比做一个人,地形就相当于这个人的骨骼,而影像就相当于这个人的外表了.之前的几个系列,我们全面的介绍了Cesium的地形内容,详见: Cesium原理篇:1最长的一帧之渲染调度 Cesium原 ...

  2. Cesium原理篇:3最长的一帧之地形(2:高度图)

           这一篇,接着上一篇,内容集中在高度图方式构建地球网格的细节方面.        此时,Globe对每一个切片(GlobeSurfaceTile)创建对应的TileTerrain类,用来维 ...

  3. Cesium原理篇:7最长的一帧之Entity(下)

    上一篇,我们介绍了当我们添加一个Entity时,通过Graphics封装其对应参数,通过EntityCollection.Add方法,将EntityCollection的Entity传递到DataSo ...

  4. Cesium原理篇:3最长的一帧之地形(3:STK)

    有了之前高度图的基础,再介绍STK的地形相对轻松一些.STK的地形是TIN三角网的,基于特征值,坦白说,相比STK而言,高度图属于淘汰技术,但高度图对数据的要求相对简单,而且支持实时构建网格,STK具 ...

  5. Cesium原理篇:3D Tiles(2)数据结构

    上一节介绍3D Tiles渲染调度的时候,我们提到目前Cesium支持的Cesium3DTileContent目前支持如下类型: Batched3DModel3DTileContent Instanc ...

  6. Cesium原理篇:3最长的一帧之地形(1)

    前面我们从宏观上分析了Cesium的整体调度以及网格方面的内容,通过前两篇,读者应该可以比较清楚的明白一个Tile是怎么来的吧(如果还不明白全是我的错).接下来,在前两篇的基础上,我们着重讨论一下地形 ...

  7. Cesium原理篇:3最长的一帧之地形(4:重采样)

           地形部分的原理介绍的差不多了,但之前还有一个刻意忽略的地方,就是地形的重采样.通俗的讲,如果当前Tile没有地形数据的话,则会从他父类的地形数据中取它所对应的四分之一的地形数据.打个比方 ...

  8. Cesium原理篇:6 Renderer模块(1: Buffer)

    刚刚结束完地球切片的渲染调度后,打算介绍一下目前大家都很关注的3D Tiles方面的内容,但发现要讲3D Tiles,或者充分理解它,需要对DataSource,Primitive要有基础,而这要求对 ...

  9. Cesium原理篇:6 Renderer模块(2: Texture)

    Texture也是WebGL中重要的概念,使用起来也很简单.但有句话叫大道至简,如果真的想要用好纹理,里面的水其实也是很深的.下面我们来一探究竟. 下面是WebGL中创建一个纹理的最简过程: var ...

随机推荐

  1. Python标准库--typing

    作者:zhbzz2007 出处:http://www.cnblogs.com/zhbzz2007 欢迎转载,也请保留这段声明.谢谢! 1 模块简介 Python 3.5 增加了一个有意思的库--typ ...

  2. C++中的命名空间

    一,命名空间(namespace)的基本概念以及由来 1.什么是标识符: 在C++中,标识符可以是基本的变量,类,对象,结构体,函数,枚举,宏等. 2.什么是命名空间: 所谓的命名空间是指标识符的可见 ...

  3. Hawk 5. 数据库系统

    Hawk在设计之初,就是以弱schema风格定义的.没有严格的列名和列属性.用C#这样的静态强类型语言编写Hawk,其实并不方便.但弱schema让Hawk变得更灵活更强大. 因此,Hawk虽然之前支 ...

  4. iOS逆向工程之Theos

    如果你对iOS逆向工程有所了解,那么你对Tweak并不陌生.那么由Tweak我们又会引出Theos, 那么什么是Theos呢,简单一句话,Theos是一个越狱开发工具包,Theos是越狱开发工具的首先 ...

  5. CRL快速开发框架系列教程十(导出对象结构)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  6. 【转】外部应用和drools-wb6.1集成解决方案

    一.手把手教你集成外部应用和drools workbench6.1 1.         首先按照官方文档安装workbench ,我用的是最完整版的jbpm6-console的平台系统,里面既包含j ...

  7. [修正] Firemonkey TFrame 存档后,下次载入某些事件连结会消失(但源码还在)

    问题:Firemonkey TFrame 存档后,下次载入某些事件连结会消失(但源码还在) 解决:(暂时方法) type TTestFrame = class(TFrame) public const ...

  8. JVM类加载

    JVM的类加载机制就是:JVM把描述类的class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被JVM直接使用的Java类型 ClassLoader JVM中的ClassLoade ...

  9. 移动应用App测试与质量管理一

    测试工程师 基于Html的WebApp测试, 现在一些移动App混Html5 HTML5性能测试 兼容性 整理后的脑图 测试招聘 弱化大量技术考察 看重看问题的高度 看重潜力 测试经验 质量管理 专项 ...

  10. (转) 将ASP.NET Core应用程序部署至生产环境中(CentOS7)

    原文链接: http://www.cnblogs.com/ants/p/5732337.html 阅读目录 环境说明 准备你的ASP.NET Core应用程序 安装CentOS7 安装.NET Cor ...