posts - 176, comments - 240, trackbacks - 0, articles - 7

    witrix平台中的tpl模板技术的重点在于标签定义的设计, 在于如何最大限度的发挥xml格式的表达能力。
    tpl自定义标签的基本结构如下:
    <Namespace:TagName tpl:tag="realTagName"
        tpl:noborder="${booleanExprInCompileContext}"
        tpl:ignore="${booleanExprInCompileContext}"
        attrName="stringOrExpression" cp:attributeInCompileContext="atringOrExpression"
        OtherNamespace:OtherAttrName="stringOrExpression"
        >
        bodyContent
    </NameSpace:TagName>
    自定义标签总是处在某一名字空间中, tpl名字空间中的属性由tpl编译器负责进行解析并处理, 而cp名字空间中的属性在编译期能够访问,其他名字空间的属性被完全忽略, 一般只有decorator会识别这些属性(例如cache:timeout).所有无名字空间的属性都相当于是自定义标签的调用参数,在标签运行的时候 可以在标签内部访问到。
    tpl通过对namespace的使用, 避免了系统属性, decorator属性与普通标签属性之间的相互干扰, 这与JSF和Tapestry都是不同的。
    tpl:tag属性指定此标签在编译时对应的真实标签名, 即编译期会识别RealTagName而不是Namespace:TagName。tpl:noborder为true的时候相当于是直接编译 bodyContent, 例如用来在编译期控制是否在界面上使用某种边框。
    tpl:ignore为true的时候,此标签将被忽略而不会被编译。
    bodyContent在编译期成为tagBody变量, 由自定义标签自己决定如何处理, 这种方式比FreeMarker的<#nested>机制要灵活和强大的多. 例如在标签内部我们可以使用<cp:compile src="${tagBody}" /> 这等价于 FreeMarker的<#nested>. 也可以使用
    <cp:compile src="${tagBody.existingChild('header')}" />从bodyContent中取出header子节点. 甚至我们可以对tagBody进行加工之后再进行编译.

posted @ 2005-11-22 23:09 canonical 阅读(1012) | 评论 (0)编辑 收藏

    经济学的核心概念是合同(contract),多个利益主体(具有不同的价值目标)在交互中达成一致协议。这本质上是个多目标优化的问题。这与物理学的精 神是有着本质区别的。物理学传统上认为世界是完美的,存在着唯一的真理。根据Lagrange原理,最低能量原理等,物理世界总是在众多可比的备选结构中 选择那唯一最优的结果。在经济学中不存在唯一的价值取向,并不是所有的事物之间都是能够进行比较并排出座次的。比如,我们大多数人会认为10个老婆饼比一 个老婆饼好,10个葱油饼比一个葱油饼好,10个老婆饼加10个葱油饼比一个老婆饼加一个葱油饼好,但是10个老婆饼和10个葱油饼之间如何比较,却是没 有确定的结果的。数学上,我们说可以建立偏序(partial order)但无法建立全序。因为多个利益主体对同一事物的评价是不可比的(不同的),因而可以产生交换.交换是一个互惠互利的过程. A交出了部分老婆饼换来了葱油饼,因为A觉得自己的老婆饼很多,还是葱油饼更有吸引力一些,而同样B认为自己的葱油饼很多,他宁肯再要一些老婆饼. 通过一番讨价还价的交互过程,我们可以达到所谓的Pareto有效(optimal): 在最终的优化配置中,没有人能够在不使别人受损的情况下使自己得益. 即继续交换下去,A或B中的一个就会觉得不值得了. 注意到经济学的这种双赢性质与军事学和权谋术也是不同的,权谋讲求争锋相对, 损人即利己, 进攻是最好的防守等等.
   
    多目标优化,意味着我们在一族矢量中寻找最优的一个,尽管在每个维度上我们都能很容易的作出比较和判断,但综合起来却需要反复的权衡。在凸分析 (Convex Analysis)中标量化(Scalarization)是寻找Pareto有效点的常用方法. 即定义一个价格矢量, 优化时考虑总体价格. 价格的存在意味着市场的存在, 意味着我们在考虑优化问题的时候,可以只考虑自身与市场的交互,而不用考虑众多其它利益主体的存在(类似于物理中的场方程). 理论上可以证明,在均衡价格处可以实现Pareto最优。


    说了这么多经济学,它和软件有什么关系呢。稍微留意一下就会发现,现在软件中越来越强调合同和涉众(stakeholder)利益了(参考use case)。早期,面向过程编程时,系统的目标比较单一:满足当前系统功能需求。基本上系统中的每一个函数在编写的时候都服从于一个目标,即当前系统的运 行。在编制的时候多半都想着向着最终的目标迈进,系统的各个部分之间是精确(detailed)匹配的。(想想VB的代码吧)。采用面向对象的思想方法之 后,系统中出现了多个利益的实体,它们定义并保护着自身的利益。系统的目标不再是完成功能,而是实现用户价值,稳定运行,便于维护等多重价值目标。在面向 对象以后,我们经常会发现,最终我们实现的功能点会多于面向过程开发的时候。很多对象函数在当前系统中最终并没有调用,但考虑到重用性和完备性等,我们还 是编写了相应的代码。很多商用组件对象的功能集更是大得惊人,我们永远只是用到其中的一小部分。系统的架构便是由对象之间相互协作并相互竞争支撑起来的.   在面向对象设计中, 我们说对象对外暴露的接口是与外部世界达成的contract, 而接口函数则反映了一次交互过程.有时我们采用如下方法, 让调用双方都能平等的获得处理机会.
class A{
   void someFunc(B b){
      b.someFunc();
   }
}

    XP敏捷编程强调快速迭代, 但绝不意味着不进行架构设计. 按照级列设计理论, 复杂性是分级的, 我们要采用满足当前需求的最简单的设计,而不是绝对意义上的最简单的设计. 而且简单与完备性还是两回事,虽然简单,但是功能仍然要是完备的. 这是个架构性问题. 迭代是个逐步精化的过程,而很少是格式塔式的革命. 在XP中我们应该更加强调architecture centric.
    重构是不影响系统外部特征的情况下,对系统内部结构的修改. 但我们现在要从结构A走向结构B,重构的路径到底在哪里。推翻了重新写并不是重构。虽然XP强调当前,不要过多考虑未来,但这只是个重点问题,并不意味着 不考虑未来,我们需要为复杂性的方向性发展保留出一定的通道。实际上重构的过程中,架构本身对应的概念是基本不变的,只是结构在调整,在细化。如果你大脑 痴呆,先天遗传不利,无论怎么重构知识结构大概也难以避免被淘汰的命运. 重构也会陷入Pareto最优点,因为一些对象作为即得利益者,让它放弃自己的功能集并不是那么容易的事情。它会争辩说,我和XX建立了关联,基于我的 YY功能,ZZ已经作了大量开发而且已经发布出去。。。

    目前软件设计中没有"市场"的概念, 在权衡系统功能归属的时候,我们只能两两进行: 这个功能放在对象A里好呢还是放在B里好呢. 是一个相当费力的过程.
   
    最后,再为物理学说句话。表面上看起来,物理学是由最优化原理支配的,但它还要受到所谓对称性的制约。很多时候当我们面临两难选择的时候,对称性会帮助我 们作出选择。对称性(根据Nother定理,守恒律也是对称性的一种)维护了物理学内部的结构张力。

posted @ 2005-11-22 17:59 canonical 阅读(585) | 评论 (2)编辑 收藏

    过程控制的要点是哪些?我从以下几点来考虑这个问题。
 第一点是测量,衍生出定义,标识,记录,评判等。测量需要在具有分歧的变化之处进行(与其它过程的交连之处)。
  第二点是预测,预测不准的原因一般是信息不足或者是非线性。确定性系统中可以产生貌似随机的混沌现象。物理学中混沌控制的基本要点是缩短控制间隔,尽量利 用确定性的信息,而且间隔减小之后,多半可以部分恢复线性。此外,控制所需的信息也并不需要是整个动力学过程的全部信息,只需要是某个动力学切面上的信息 即可。

 第三点是控制点的选择,控制点分为时点(时机)和位点(对象)。有时需要在系统中制造出某个瓶颈来便于施加控制。

 第四点是执行,控制的基本策略分为负反馈和正反馈,这就需要信息的传递(交流)。正反馈策略在非线性系统控制中其实非常重要(饱和)。

 第五点是协调(组织),控制的整体结构具有周期结构(节奏),网状结构(交互),层次结构(抽象)等。

 第六点是变化,有序的动态结构变化称为演化(持续改进),而演化最重要的是方向,或者说是目标,或者说是价值。

 第七点是学习,改进是学习特别是从错误中学习的结果。传统制造业的控制因为流程非常固化,其实并不注重学习。

 越来越复杂的系统最终只有一个词能够描述:生态。

posted @ 2005-11-22 17:58 canonical 阅读(286) | 评论 (0)编辑 收藏

quartz是一个高质量的任务调度软件包。其主要组成部分为:

Scheduler接口: quartz的执行线程,它根据Trigger决定调度时刻,根据JobDetail的说明实例化并运行Job

JobDetail类: 可持久化的任务描述信息。任务虽然分组,但是仅用作管理标示,任务之间并无实质性关联, 例如无法定义job chain。

Trigger类:任务的调度策略。这里的特点是调度策略与任务描述分开,调度策略和任务描述都可以分别在Scheduler注册,然后再关联起来。JobDetail与Trigger的关系是一对多。

JobDataMap: 将任务的运行时可持久化状态信息从JobDetail类中分离出来

Job接口: 任务的执行代码

StatefulJob接口: 无状态任务对应的JobDataMap可以认为是只读的,而有状态的任务在多次执行过程中保留对JobDataMap所作的修改,一个后果是有状态任务无法被并发执行。

JobExecutionException类: 可以通过JobExecutionException调整调度程序的下一步动作
Calendar接口: 用于从trigger的调度计划中排除某些时间段,例如假期等。

以上几个部分的交互关系如下:
class JobImpl implements Job{
    public void execute(JobExecutionContext context) throws JobExecutionException{
        JobDetail detail = context.getJobDetail();
        JobDataMap dataMap = detail.getJobDataMap();
        ...
    }
}

scheduler.addCalendar("myHolidays", holidayCalendar, false);
trigger.setCanlendarName("myHolidays");

JobDetail jobDetail = new JobDetail(jobName, jobGroupName, JobImpl.class);

scheduler.scheduleJob(jobDetail, trigger);

JobDetail可以设置如下属性:
1. Durability: non-durable的任务当不再与任何active trigger关联的时候将会从scheduler中被自动删除。
2. Volatility: volatile的任务在scheduler的两次启动之间不会被持久化
3. RequestsRecovery: 如果在执行过程中程序意外崩溃,标记为"request recovery"的任务在scheduler重起之后将会被再次执行,此时JobExecutionContext.isRecovering()返回true.

Trigger可以设置如下属性:
1. misfireInstruction: 设定当trigger错过了触发时刻的时候需要采取的处理策略

SimpleTrigger按照固定的时间间隔进行触发
startTime, endTime, repeatCount, repeatInterval

CronTrigger按照日历间隔进行触发
seconds minutes hours day-of-month month day-of-week

在quartz内部,QuartzSchedulerThread按照时间顺序选择trigger(没有任务优先级的概念), 然后在JobRunShell中运行Job。

JobRunShell中的调用顺序如下:

TriggerListener.triggerFired
    Called by the Scheduler when a Trigger has fired, and it's associated JobDetail is about to be executed.

TriggerListener.vetoJobExecution
    Called by the Scheduler when a Trigger has fired, and it's associated JobDetail is about to be executed.

JobListener.jobToBeExecuted
    Called by the Scheduler when a JobDetail is about to be executed (an associated Trigger has occured).

Job.execute
    Called by the Scheduler when a Trigger fires that is associated with the Job.
 
JobListener.jobWasExecuted
    Called by the Scheduler after a JobDetail has been executed, and be for the associated Trigger's triggered(xx) method has

been called.

Trigger.executionComplete
    Called after the Scheduler has executed the JobDetail associated with the Trigger in order to get the final instruction

code from the trigger.

TriggerListener.triggerComplete
     Called by the Scheduler when a Trigger has fired, it's associated JobDetail has been executed, and it's triggered(xx)

method has been called.

SchedulerListener.triggerFinalized [if(trigger.getNextFireTime() == null)]
     Called by the Scheduler when a Trigger has reached the condition in which it will never fire again.

posted @ 2005-11-22 17:55 canonical 阅读(1085) | 评论 (0)编辑 收藏

  hibernate等O/R Mapping软件包中使用javabean来作为数据的载体, 但这些bean一般除了get/set等数据访问方法之外没有什么其它的业务方法。 前一段时间有人认为这是所谓贫血的领域模型(Anemia Domain Model),引发了一场讨论。 其实这些bean的作用仅是表达了领域内的数据关系, 本身并不可能作为完整的领域模型层存在。 在数据层,我们所需要的是数据对外暴露,因为我们无法预知这些数据的使用方式, 就象是实验数据发表出来以后你无法预知别人如何分析一样,这时信息流是开放的,向外的:信息在这里,放马过来吧。 而在业务逻辑层,复杂的逻辑控制交织在一起,我们需要精细的控制信息通道,通过函数封装,我们反转了信息流的方向:取到什么数据是由调用者提供的信息决定 的。
     实际上,在ORM软件中使用的bean基本上与一个Map类似,只是它具有java Class所提供的元数据,而访问数据时又必须通过get/set方法,因而在这些方法中能够根据元数据动态的作出响应。在witrix平台的统一数据访 问框架中主要基于Map等通用数据类型,而不是个性化的bean。为了使得Map具有与bean一样的动态响应能力,只需要加入meta的支持即可。
interface IExMap extends Map{
     IMapChecker getChecker();
  Map getModifications();
  ...
}
在get/set之前可以通过IMapChecker来实现动态处理,对Map中数据所作的修改也可以记录下来

posted @ 2005-11-22 17:53 canonical 阅读(292) | 评论 (0)编辑 收藏

    在程序中需要返回一个数据集合的时候, 应该尽量选用标准的Java集合类接口,例如List, Map等. 有时也见到有人选择返回Iterator对象, 一般情况下这不是很好的选择. Iterator对象的功能有限, 而且存在一种即时消费的特点, 我们一般不能把一个Iterator保存起来留待以后使用. 而且JDK提供的集合类也不能从Iterator直接构造出来,例如没有 new ArrayList(myIterator), 这样为数据复制造成一定的困难.
    Iterator在理论上的好处之一是可以支持延迟加载数据, 但是实现起来比较繁琐而且单条加载也是比较低效的. 在witrix平台中如果需要延迟加载数据集合, 我一般选择使用IPageViewer接口
       interface IPageViewer{
           public List getAll();
           public int getTotalCount();
           public List listPage(int startPos, int maxCount);
       }
    IPageViewer通过getAll()可以转换到List, 也可以通过 new Pager(pageViewer,pageSize).itemIterator()得到单条数据的Iterator, 其内部采用分页加载模式. 搜索引擎返回的结果和数据库查询的结果都可以使用这一接口.

posted @ 2005-11-19 11:04 canonical 阅读(317) | 评论 (0)编辑 收藏

 Ivar Jacobson 在其著作"Aspect-Oriented Software Development with Use Cases"中将AOP(Aspect-Oriented Programming)上升为一种贯穿软件开发全生命周期的设计技术,由建筑学的隐喻引出了decoration overlay(existion + extensions)的策略。在建筑学中,首先基础设计图规划了房间的空间划分,电气工程师基于这张基础图决定电气设施排布图,而装修设计师可能依据基 础设计图决定装修方案。所有这些扩展(extension)设计图叠加到基础设计图之上构成的最终设计方案决定了房间每个部分的最终功能。这也有些象是中 国传统彩画的印制技术:每次印刷一种颜色,多重叠加之后构成成品。
 Jacobson指出传统的面向对象设计将会产生两个问题: tangling和scattering。tangling指的是一个组件要参与多个业务, 因而组件具有的功能是所有业务所需功能的超集, 虽然很多功能只在某一项特殊业务中使用,并无重用需求, 但组件开发者在设计和开发时却无法局域化, 必须同时理解所有需求。 scattering指的是某一特定需求可能需要多个组件协同操作来完成, 这要求实现代码分散在多个组件之中,而没有一个整体概念。这两个问题的产生是因为缺乏一种全局的separation of concern。AOSD(Aspect-Oriented Software Development)正是Jacobson指出的解决方案。
 AOP 在基本的技术含义上可以认为是实现了基本动作与修饰语之间的正交分解,Jacobson将其扩大化为基础架构(architecture)与扩展 (extension)之间的正交分解,将architecture centric设计与use case, AOP等概念联系起来。 Jacobson指出程序中在两个不同的层面上存在着独立的正交分解结构,一个是组件结构(具体化为Class),一个是业务逻辑结构(具体化为use case)。The element structure identifies where elements of the system are located in the element namespace. It structures the elements hierarchically in terms of layers, packages, classes, and the features within these classes. The use case structure defines how you overlay functionality onto the element structure. 因为use case需要多个组件的参与,当我们将use case结构叠加到组件结构之上时就会产生tangling和scattering。Jacobson指出每一个use case是一个功能切片(slice), 并为use case中的概念找到了相应的程序对应物,因而可以在后期通过AOP技术将use case slice织入到系统基础架构中,从而构成一种全程建模技术。use case中的generalization可以对应于对象继承, 包含(include)可以对应于对象组合和调用, 而扩展(entension)就对应于AOP。某些use case具有共性,如常见的CURD(Create,Update,Read, Delete)操作,可以被抽象为参数化的use case, 这可以对应于模板(template)概念。
 我在数年前也注意到了tangling现象,一些类的功能不断膨胀而完备性却始终无法保证。在概念 上我们可以将功能集正交分解为不同的接口,但实现时却需要集中实现,因为它们对应于同一个对象实体。当时我主要通过interface的 aggregation来解决这个问题,例如采用IDynamicInterfaceSupport接口,动态注入接口实现.
     interface IDynamicInterfaceSupport{
      Object queryInterface(Class interfaceClass, Object user);
   void addInterface(Class inf, Object impl);
  }
  使用接口来处理这些问题很多时候效果并不好。首先接口要求预先有一个明确的定义,要求明确标注出功能的独特性,这样在合成的时候具有一定的困难。例如在C ++中我们将对象如果分解为大量的接口,则使用的时候需要不断的queryInterface,十分麻烦。只有弱化种类型区别,才能消除这种合成障碍。例 如在脚本语言中,我们可以自由的使用对象的所有函数,而不需要知道它是哪个接口具体提供的。我在C++中主要通过代码生成技术构造一个合成代理对象来解决 这个问题,在java中主要使用interface的extends。接口意味着某个明确的定义,而模板(template)在这方面的要求则弱得多,模 板只要求对象具有需要的函数,有时可以做到更好的可重用性。接口的另一个困难在于无法捕获cross-cutting concern。在没有AOP的代码注入技术之前我们主要是通过继承来构造扩展点,这些都是所谓侵入性(intrusive)的设计,要求不必要的附加层 次,而且具有很强的事前假定。
  在jsplet中WebObject的注册格式可以认为是实现了action slice。WebObject具有的全部功能可以通过多个action集合组装出来,而这多个action slice共享一个对象名和成员变量空间。
 <type name="Demo">
  <!-- 如果未指定object, 则缺省为WebObject类型 -->
  <object class="demo.MyWebObject" />
  <listener>
   <url-listener url="/demo/ActionSliceA.jsp" />
   <url-listener url="/demo/ActionSliceB.jsp" />
  </listener>
 </type>
       在tpl模板中,因为xml格式定义了明确的结构信息,所以我们比较容易的定义扩展点,并注入新的功能,例如<c:decorator>标签。这也类似于AOP的操作。

posted @ 2005-11-19 11:03 canonical 阅读(725) | 评论 (1)编辑 收藏

交叉表(Cross Table)的基本特点是具有横纵两个自由延展的维度,而平面表结构只有一个可延展的维度,因为平面表的列名和列数是确定的。例如,地区的产品销售数量,在平面表中表达为
district_id product_id sell_num
如果表现为交叉表,则为
           productA  productB
districtA   sellNum   sellNum
districtB   sellNum   sellNum
这种结构上的失配需要通过程序逻辑来进行调整。

注意到平面表结构只具有一个可延展的维度,而join可以看作是该维度上的连接操作。因此我们可以将交叉表看作是多个简单平面表结构并置的结果。即分解为
A:        
           productA
districtA   sellNum
districtB   sellNum

B:
           productB
districtA   sellNum
districtB   sellNum

横向维度的扩展在程序中表达。

SqlInfo结构封装了这种简单平面表的分解片断。
class SqlInfo{
    List fieldNames;
    SQL sql;
    String keyField;
}

我们在程序中通过JoinBuilder来实现横向维度的构造
JoinBuilder.begin(sqlInfoA)
           .join(sqlInfB)
           .leftJoin(sqlInfoC)
           .end();
生成的sql语句示例如下
select sqlInfoA.fieldNames, sqlInfoB.fieldNames
from sqlInfoA.sql join sqlInfoB.sql
on sqlInfoA.keyField = sqlInfoB.keyField

posted @ 2005-11-19 11:02 canonical 阅读(295) | 评论 (0)编辑 收藏

数据导出的功能大致可以分解为三个部分: 1. 从数据源读取一条记录 2. 将一条记录导出为指定格式 3. 循环调用1和2
首 先我们需要一种机制来对外暴露数据源(一种Container)中的数据,Iterator模式恰能满足要求。其次,我们需要一种机制来对一系列数据进行 处理,这对应于Visitor模式。第三,在组合Iterator模式和Visitor模式的处理过程中,我们需要表达出平面表数据集的基本特征。
在witrix平台中,平面表数据导出和转换通过TablePageProcessor对象来完成,
class TablePageProcessor{
 IPageViewer viewer;

 public Object process(ITableVisitor visitor){
  Pager pager = new Pager(viewer, pageSize);
  Iterator it = pager.itemIterator();
  visitor.visitBegin(headers);
  while(it.hasNext()){
   Object row = it.next();
   if(!visitor.visitRow(row))
    break;
  }
  return visitor.visitEnd();
 }
}

interface ITableVisitor{
 void visitBegin(List headers);
 boolean visitRow(Object row);
 Object visitEnd();
}

IPageViewer是暴露平面表数据的标准接口,它通过Pager对象的包装之后可以提供各种Iterator.
ITableVisitor体现了平面表数据的基本特征: header + 一系列row, 存在数据边界(起始与终止)
TablePageProcessor固化了IPageViewer和ITableVisitor的组合过程。
ITableVisitor可以有多种实现,例如CSVBuilder, ExcelBuilder等等。

posted @ 2005-11-19 11:01 canonical 阅读(321) | 评论 (0)编辑 收藏

分页的功能由两部分组成:取数据和计算分页。其中取数据的功能由IPageViewer接口实现
interface IPageViewer{
 int getTotalCount();
 List getAll();
 int listPage(int startPos, int maxCount);
}
Pager是用户调用时的接口
class Pager{
 public List getAll(){}
 public List listPage(){}
 public int getPageCount(){}
 public int getPageSize(){}
 public int getCurrentPage(){}
 ...
}
Pager使用IPageViewer作为数据供体,自身仅提供分页计算的功能。在witrix平台中, IPageViewer是表格数据的标准列举方式,因为与List接口相比,IPageViewer容许部分加载。
IPageViewer 可以有多种实现,如ListPageViewer, XmlPageViewer, ExcelPageViewer, DbTablePageViewer等。一般情况下Pager提供的功能已经足够了,特殊情况下可以通过继承来扩展。例如卡片浏览和分页浏览模式之间的互 相切换通过派生类ItemPager来实现。

posted @ 2005-11-19 11:00 canonical 阅读(235) | 评论 (0)编辑 收藏

仅列出标题
共18页: First 上一页 10 11 12 13 14 15 16 17 18 下一页