以一个c++程序员的角度来和大家聊聊我们程序员的思维
- 命令式
按照每个步骤描述要做的工作和任务,先做什么,后做什么,顺序是不能颠倒的; - 说明式
描述一个物体或者概念,它有什么特性,和其他物体有什么关系。
从编程语言来看,说明式和命令式的思维式混合在一起的。就拿我们大学一开始就要求学的c语言来看,它的说明式语句就包括了
- 函数定义
- 变量的定义和说明(包括了动态、静态、全局)
- 结构体定义
- 枚举定义
- typedef定义
- 分支、循环语句
而代码中的表达式和其组成,则是命令式的。他们描述一个任务应该按照什么样的顺序去执行,c++和java中,class类的定义,就是一个很强的说明式语句
可是这些语言的说明成分还是太少了
为什么会这么看重说明性语句
举个例子,开发一个简单的计算器,有界面的哈,可以分成这几点
- 界面(view层)
- 逻辑层
- 数据层
只有加减乘除处理,用到命令式描述方法,计算出用户希望的结果,数据处理好之后就通过参数的形式发到view层进行显示。==>窗口及控件的创建、将按钮的click事件关联到一个回调上
一个软件中,会存在相当一部分的对象和数据,是基本不变化的,而另外一些数据,则是要求被处理的。 比如软件的GUI、菜单、事件等,在软件启动后,基本不会发生变化;而用户输入的信息,则是经常性的变化(静态和动态数据)
==>也就是说,软件由两部分组成,一部分是程序的框架,是不变化或者仅有少数变化的;另外一部分则是程序的处理层(逻辑层、数据管理层、接口层)由程序的输入、输出和中间数据组成。
如果看过《软件工程》就知道这个是变换式设计的思维,它的第二步就是确定输入、输入和数据变换之间的界线(跟不上的自己翻开《软件工程》–结构化设计)程序的框架就是生产线,被处理的数据就是生产线上的产品。
开发软件时,这两者经常被混合讨论的,这使得软件开发的难度高而效率低。如果程序的框架,能够被简单的描述出来,而不需要开发者花费大量的力气去定义和创建,那么,程序的开发难度将大幅降低,而效率的大幅提高。
这就是看中说明性语言的原因。
关注软件运行时状态
软件在运行时,有一组又一组各式各样的对象,直接相互引用和关联(成分之间的关系设计时很重要的,最低程度的内聚是偶然内聚,成分直接不存在任何关系,单纯凑在一起而已。最高程度的内聚,功能内聚,所有的成分对于完成一个单一的功能都是基本的。你可以试下,这两种程度放在项目开发中带来的效益时相差很大的,这就是那句总是被说了很多次却真的能用上的时候却很少,“高内聚低耦合”)。当软件处于空闲状态时,这些对象什么也不做;当软件在处理数据时,数据在不同的对象之间被分割、转换、传递、复制、组合,最终形成需要的数据,并以某种方式输出。
为了构建这样一个系统,开发者需要在合适的时间,创建合适的对象,并和其他对象关联起来。 这里的“ 合适”与“ 其他对象” 是两个概念,是非常模糊,有时甚至是不可达到的。正是由于创建合适的对象如此的困难,以至于设计模式中还专门列出一个“创建模式”,来描述创建合适对象要使用的各种方法。合适的时间通常和效率、性能相关,解决起来并不是很复杂。
"其他对象“这个范围是非常难以界定的,对于不同的要求,一个对象需要知道不同种类的对象。所以,就有了接口技术的产生。 接口技术对这个问题很重要,可接口有时候也是相互依赖的,而且,接口的修改,对于它的实现类和使用它的类来说,无疑是灾难,特别是那种有点历史的代码,动一下,就会突然抱错,维护的难度很大。而对于被处理的数据,情况则更加复杂。被处理的数据(看过电箱、机房那些线路的排布吗,有密集恐惧症的建议别看),可能存在一个复杂的逻辑关系,需要开发者为其单独建模,这样,被处理数据的模型有可能比被处理数据更加复杂;另外,被处理数据,实际上涉及与磁盘、网络等介质交换,这就涉及数据的解析与保存。
因此,从这个角度来说,如果我们能够描述出软件运行时的数据构成,然后让编译器自动构建出这种运行时内存的话,开发的效率和稳定性将极大的提高。
数据网络
通用的数据结构,主要由线性表、树、图以及字典表(结构体和类可以看作经过优化的(数据)字典(这是结构化设计重要的一环),他们都是通过名称访问的)。对象间的相互引用关系,建立了一个及其复杂的网络。 这个复杂网络中的数据,它们的值是相互映射,并保持同步的。除了一些非常简单的应用,大多数有实际价值的应用软件,都会建立这样一个数据网络。 对于一些较为复杂的应用软件,被处理的数据会形成临时性的数据网络。开发者极力对外隐藏不必要的数据和关闭不必要的访问端口,因此其数据网络由若干小网络连接而成,每个小网络可以看作一个小层次。
GUI的数据网络
很多GUI都可以通过xml静态语言来描述和编写。从这方面来说,GUI界面是非常典型的树结构。你以为GUI只是这样?怎么可能。单个GUI窗口的数据网络单个GUI窗口是一个标准的树结构。界面窗口需要和用户交互吧,因此,它有很多隐藏的网络。以窗口/控件ID或者名称为key的字典网络。GUI系统为了让其他模块能够访问窗口上的控件,会提供ID或者name,熟悉前端的就知道:class、id。这些 ID或者name就会形成一个字典数据结构,提供外部访问使用。例如,在HTML的DOM模型中,可以通过getElementById,取得标签对象。
输入和输出数据网络
一个Login窗口,必然要提供username和password两个数据到外部,让其他模块处理。它有可能需要其他模块提供已经保存过的username和password数据来进行预填。对于窗口系统中的绝大多数对话框来说,输入和/或者输出数据是不可避免的,当然也有例外,就像About对话框,完全不需要额外的输入和输出。
可是有的时候呢,数据可能来自多个方面:一个文件选择对话框,它要求输入的数据包括:标题、文件扩展名过滤表等;而输出则包括选择的文件名。除此之外,它从系统的磁盘上读取目录和文件信息,显示给用户。
输入和输出网络和GUI的树型网络有密切关系:
输入的数据,将映射到GUI树中,某个控件的属性;而输出的网络,则是从GUI树中采集到对应的数据。这就涉及到数据网络直接的相互映射问题。
GUI树的节点之间还存在额外的网络链接:
例如,一个播放器控制面板中,就存在密切的联系:
- 开始按钮按下后,停止按钮变为可用,反之亦然;
- 在播放进度上选择不同进度,播放的实际就会变化;
在通常的编程中,我们通常会通过一些变量,如播放状态变量、当前进度变量等,保存现有状态,然后在按钮或者是进度条的事件中,直接改变相关这些变量的值的同时,改变相关控件的属性。在这些公认的设计思路中,存在一个隐含的数据网络,它包含状态变量、当前进度变量,而且,播放、停止按钮的enable属性,是和状态变量对应的;播放进度条的位置属性和显示播放时间的标签的文本属性,是和当前进度变量对应的。从上面的总结来看,无论代码多么复杂,它总是由若干存在映射关系的对象网络组成的。这些网络,主体上都是树形网络。
xml的DOM结构是混合树与节点的属性表的结构。
因此,我们将窗口的逻辑关系,抽象为若干DOM树的映射关系。
(感受下VUE,就知道什么是DOM关系了)
DOM树之间的映射研究
对于xml,可以通过xpath进行节点的选择。 我们可以利用xpath来对DOM树进行节点间的映射。一对一的映射关系
DOM树A的节点和DOM树B的节点进行一对一的映射。
<window> // id的访问速度会比class快
<button name="play"/>
<button name="stop"/>
<progress name="progress"/>
<label name="current_time"/>
</window>
对应的控制状态的静态代码是:
<controller>
<variable name="play_state" type="enum">
<option value="playing"/>
<option value="stoped"/>
</variable>
<variable name="curtime" type="time"/>
<variable name="progress" type="percent"/>
</controller>
play_state将绑定到play或者stop button上
curtime将绑定到current_time label上,progress将绑定到progress控件上。
语法:
binding player_controller(window : DOM, ctl: DOM)
{
window://button[@name='play']/@enable = expr(ctl://variable[@name='play_state'] != playing);
window://button[@name='stop']/@enable = expr(ctl://variable[@name='play_state'] != stoped);
window://progress/@postion == ctl://progress;
window://current_time/@text = ctl://curtime;
}
这是一种外部映射机制。
特点是,不需要接触两个DOM的内部构成,就可以改变其内容。还有一种较为直观的方法,即利用xlst,将controller的内容嵌入到window的DOM中
<window >
<import-dom name="ctl"/>
<button name="play" enable="{expr(ctl://variable[@name='play_state'] != playing}"/>
<button name="stop" enable="{expr(ctl://variable[@name='play_state'] != stoped)}"/>
<progress name="progress" postion="{ctl://progress}"/>
<label name="current_time" text="{ctl://curtime}"/>
</window>
“{” “}” 分别表示的是绑定机制
列表数据的映射
通常会有很多列表数据,需要填充。举个xml的栗子:
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2003</year>
<price>49.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
将该数据映射到一个listview控件中。listview由4列组成,首先,我们使用xslt的映射方式
<listview>
<import-dom name="book"/>
<foreach select="book://book"/>
<row>
<column>
<image src="{getimage('book:@category}"/>
</column>
<column>
<label text="{'book:title'}"/>
</column>
<column>
<label text="{connect(book:author)"}/>
</column>
<column>
<label text="{book:year}"/>
</column>
<column>
<label text="{book:price}"/>
</column>
</row>
</foreach>
</listview>
还有另外一种方式,再不需要改变listview的定义内容的情况下,通过外部改变其结构
bind listview(row_templ : DOMTemplate, book:DOM)
{
foreach(book://book, {
DOM row = form_template(row_templ);
row:colum[0]/image/@src = getimage(book:@category);
row:column[1]/label/@text = book:title;
row:column[2]/label/@text = connect(book:author);
row:column[3]/label/@text = book:year;
row:column[4]/label/@text = book:price;
listview.append(row);
})
}
推荐使用这种不直接生成的方法(试下Spring maven的组合,简单)它的耦合性更小。
映射时的数据转换
在映射时,可以进行数据转换。如上例中,book的category可以映射为对应的图片文件。author部分,可以将多个节点的值链接起来,组成一个数值。对于映射而来的数据,是无法(注意)进行双向转换的。除非定义双向映射函数。
C++对象的DOM化
(坚持看到这里了)上面的一切基础,都是将数据想象为一个DOM为基础的。但是,C++对象和DOM之间的区别还是非常大的。为了解决这个问题,我们创建一个C++类的meta对象,这个meta对象,将数据描述为一个DOM结构。(c++还能和DOM扯上,当然,C++还能加速web的访问呢)
对于一个Display类,进行描述
class Display
{
public:
int width;
int height;
int depth;
};
描述它的DOM化结构
Node display_node(
"display" //node name,
attribute("width", OFFSET(Display, width), INT, 800,range(1,2000)),
attribute("height", OFFSET(Display,height), INT, 600,range(1,2000)),
attribute("depth", OFFSET(Display,depth), INT, 16, emu(8,16,24,32)))
);
和nodejs里的node不是一个概念。上面写到的Node类描述一个Node节点,Node节点包括属性和可能存在的子节点。
- “display” 是节点的名称
- attribute会创建属性:
- "width"是属性名称
- OFFSET(Display,width) 是一个宏,它创建一个属性访问的对象,在这里,我们通过width成员相对与Display的偏移来访问
- INT 表示属性的基本类型位integer
- 800 表示默认值
- range(1,2000) 是对width值的一个约束,也就是说,该属性取值必须在1到2000之间
这个DOM的结构,可以通过xpath来访问。
DOM化的结构有很多有用的功能,如,完成代码的序列化。我们可以轻易的将Display对象序列化为xml对象。