软件开发的思维模式

对于软件开发,有两种思维模式:

  1. 命令式
  2. 说明式

命令式的思维模式是这样:按照每个步骤描述要做的工作和任务,先做什么,后做什么,顺序是不能颠倒的;

说明式的思维模式是,描述一个物体或者概念,它有什么特性,和其他物体有什么关系。


对于编程语言,实际上是命令式与说明式思维混合在一起的。

比如,我们熟悉的C语言,其中,说明式的语句包括:

  1. 函数定义与声明
  2. 变量(全局、局部、静态)的定义与说明
  3. struct定义
  4. enum定义
  5. typedef定义
  6. 分支语句(if-else, switch),循环语句,都属于说明性的部分
而表达式及其组成的语句,则是命令式的,它们描述了一个任务按照什么样的顺序逐步执行;

C++和java语言中class的定义,则是非常强大的说明性语句。

但是,这些语言中说明的成分还是太少太弱了,而且有一个非常显著的特点:你不能扩展自己的说明性语法。 

为什么我这么看中说明性语法呢?
我们考虑一个应用程序,比如一个计算器(带有GUI界面那种),这个程序中大部分内容是可以说明的:
  1. GUI界面描述
  2. 事件处理方式
只有加减乘除计算部分,需要命令式描述方法,计算出用户希望的结果。然后将数据送到窗口的控件上进行显示。

显然,窗口及控件的创建、将按钮的click事件关联到一个回调上这些功能,如果用xml一类的描述性语言编写,更加简单和清晰。

一个软件中,总是存在相当部分的对象和数据,是基本不变化的,而另外一些数据,则是要求被处理的。 比如,一个字处理软件,它的GUI,菜单,事件等,在软件启动后,基本不会发生变化;而用户键入的文字,则是经常性的变化。

也就是说,软件由两部分组成,一部分是程序的框架,是不变化或者仅有少数变化的;另外一部分则是程序的处理数据,有程序的输入、输出和中间数据组成。

打个比方来说哦,程序的框架就是生产线,被处理的数据就是生产线上的产品。

不过,在我们开发软件时,这两者经常被混合讨论的,这使得软件开发的难度高而效率低。

如果程序的框架,能够被简单的描述出来,而不需要开发者花费大量的力气去定义和创建,那么,程序的开发难度将大幅降低,而效率的大幅提高。

这是我这么看中说明性语言的原因。


关注软件运行时状态
软件在运行时,有一系列各种各样的对象组成,对象直接相互引用和关联。当软件处于空闲状态时,这些对象什么也不做;当软件在处理数据时,数据在不同的对象之间被分割、转换、传递、复制、组合,最终形成需要的数据,并以某种方式输出出来。

为了构建这样一个运行时系统,软件开发者(对于OO开发者)需要在合适的时间,创建合适的对象,并和其他对象关联起来。 这里面,“ 合适”与“ 其他对象”  是两个概念,是非常模糊,有时甚至是不可到达的。

正是由于创建合适的对象如此的困难,以至于设计模式中专门列出一个“创建模式”,来描述创建合适对象要使用的各种方法。合适的时间通常和效率、性能相关,解决起来并不是很复杂。

"其他对象“这个范围是非常难以界定的,对于不同的要求,一个对象需要知道不同种类的对象。为此,我们发明接口技术。 接口技术对这个问题很重要,但是,接口有时候也是相互依赖的,而且,接口的修改,对于它的实现类和使用它的类来说,无疑是灾难。


而对于被处理的数据,情况则更加复杂。被处理的数据,可能存在一个复杂的逻辑关系,需要开发者为其单独建模,这样,被处理数据的模型有可能比被处理数据更加复杂;另外,被处理数据,实际上涉及与磁盘、网络等介质交换,这就涉及数据的解析与保存。

因此,从这个角度来说,如果我们能够描述出软件运行时的数据构成,然后让编译器自动构建出这种运行时内存的话,开发的效率和稳定性将极大的提高。

数据网络
通用的数据结构,主要由线性表、树、图以及字典表(struct和class可以看作经过优化的字典,他们都是通过名称访问的)。对象间的相互引用关系,建立了一个及其复杂的网络。 这个复杂网络中的数据,它们的值是相互映射,并保持同步的。

除了一些非常简单的应用外,大多数有实际价值的应用软件,都会建立这样一个数据网络。 对于一些较为复杂的应用软件,被处理的数据会形成临时性的数据网络。

无论是那种网络,由于引入了OO编程思想,开发者极力对外隐藏不必要的数据,因此,以OO编程思想指导的软件,其数据网络由若干小网络连接而成,每个小网络可以看作一个小层次。

GUI的数据网络
很多GUI都可以通过xml语言来描述。从这方面来说,GUI界面是非常典型的树结构。
但是GUI应用程序却远没有这么简单。

单个GUI窗口的数据网络
单个GUI窗口是一个标准的树结构。但是,GUI窗口需要和用户交互,因此,它有很多隐藏的网络。

以窗口/控件ID或者名称为key的字典网络
GUI系统为了让其他模块能够访问窗口上的控件,会提供ID或者name。这些 ID或者name就会形成一个字典数据结构,提供外部访问使用。例如,在HTML的DOM模型中,可以通过getElementById,取得标签对象。

输入和输出数据网络
一个Login窗口,必然要提供username和password两个数据到外部,让其他模块处理。它有可能需要其他模块提供已经保存过的username和password数据来进行预填。
对于窗口系统中的绝大多数对话框来说,输入和/或者输出数据是不可避免的,当然也有例外,如About对话框,完全不需要额外的输入和输出。

有的时候,数据可能来自多个方面。

如一个文件选择对话框,它要求输入的数据包括:标题、文件扩展名过滤表等;而输出则包括选择的文件名。除此之外,它从系统的磁盘上读取目录和文件信息,显示给用户。

输入和输出网络和GUI的树型网络有密切关系:
输入的数据,将映射到GUI树中,某个控件的属性;而输出的网络,则是从GUI树中采集到对应的数据。

这涉及到数据网络直接的相互映射问题。

GUI树的节点之间还存在额外的网络链接:
例如,一个播放器控制面板中,就存在密切的联系:
  1. 开始按钮按下后,停止按钮变为可用,反之亦然;
  2. 在播放进度上选择不同进度,播放的实际就会变化;
  3. 等等。
在通常的编程中,我们通常会通过一些变量,如播放状态变量、当前进度变量等,保存现有状态,然后在按钮或者是进度条的事件中,直接改变相关这些变量的值的同时,改变相关控件的属性。
如果仔细观察,在这些公认的设计思路中,存在一个隐含的数据网络,它包含状态变量、当前进度变量,而且,播放、停止按钮的enable属性,是和状态变量对应的;播放进度条的位置属性和显示播放时间的标签的文本属性,是和当前进度变量对应的。

从上面的总结来看,无论代码多么复杂,它总是由若干存在映射关系的对象网络组成的。这些网络,主体上都是树形网络(或者可以看作xml的DOM结构)。

xml的DOM结构是混合树与字典(节点的属性表)的结构。

因此,我们将窗口的逻辑关系,抽象为若干DOM树的映射关系。

DOM树之间的映射研究

对于xml,可以通过xpath进行节点的选择。 我们可以利用xpath来对DOM树进行节点间的映射。

一对一的映射关系
DOM树A的节点和DOM树B的节点进行一对一的映射。

例如,播放器控制面板的例子。
控制器面板UI的xml示意(简化版本)

<window>
    <button name="play"/>
    <button name="stop"/>
    <progress name="progress"/>
   <label name="current_time"/>
</window>
对应的状态控制的xml表述是
<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;
}



  
  

这是利用类似C++代码表示的伪码。其中 "="表示将右边的值绑定到左边; "=="表示左右两边互相绑定。与C++语言中的赋值和比较是不一样的含义。


这是一种外部映射机制。它的特点是,不需要接触两个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);
     })
}

推荐使用这种不直接生成的方法,它的耦合性更小。

映射时的数据转换
在映射时,可以进行数据转换。如上例中,book的category可以映射为对应的图片文件。author部分,可以将多个节点的值链接起来,组成一个数值。

对于映射而来的数据,是无法进行双向转换的。除非定义双向映射函数。

C++对象的DOM化
上面的一切基础,都是将数据想象为一个DOM为基础的。但是,C++对象和DOM之间的区别还是非常大的。

为了解决这个问题,我们创建一个C++类的meta对象,这个meta对象,将数据描述为一个DOM结构。

比如,对于一个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)))
);

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对象。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值