框架往往是这样产生的:我们拥有了开发某种类型应用的大量经验,并开发了一些这种类型的应用,我们总结这种类型的应用中共性的东西,将其提炼到一个高的层次中,以备复用。这个“高的层次”的东西便是框架的原型。随着我们经验的不断积累,框架也会不断的向前完善、发展。框架,正如其名,就是一个应用的骨架,选用的框架的好坏直接决定了基于其上构建的应用的质量。在确定了一个框架后,我们在骨架的缝隙里为其添加“血”和“肉”,便成为一个应用。
框架源于应用,却又高于应用。
我今天要说的是,正是因为框架源于应用,所以在提炼框架的时候,我们往往不自觉的为框架作过多的假设。这些假设来源于孵化框架的具体应用中的一些潜在的“规则”或约束。为什么了?因为我们常常希望,使用了框架之后,这个孵化了框架的应用再基于这个框架来重新构建应该非常简单。这种简单性会在两种情况下出现:一是你成功地抽象出了一个非常好的框架;二是你抽象出的框架与孵化框架的应用紧密的耦合在一起。如果没有设计框架的经验,我们陷入第二种情况是必然的。
框架与孵化框架的应用的紧密耦合,换句话说,就是为框架作过多的针对这个具体应用的假设。在这种有过多假设的环境下设计框架导致的最直接的后果是:降低了框架的可复用性。我们提炼框架的目的是为了使之能在各个类似的应用中很好的复用,而依赖于太多的假设来设计框架将大大降低这一美好的目标。
框架为应用作过多的假设的一个非常具体的现象就是,框架越俎代庖,把本来是应用要做的事情揽过来自己做。这是一种典型的吃力不讨好的做法。框架越俎代庖,也许会使得一个应用的开发变得简单,却会给其它更多想使用该框架的应用增加了本没有必要的束缚和负担。
框架只做该做的事情,而哪些事情是该做的,取决于设计者的判断,而判断的正确与否取决于设计者的经验和能力。
我们设计框架时,往往在框架中提供了很多内置的组件,但是,框架不应该强迫应用使用任何一个最要、核心的组件。相反,框架应该允许应用提供组件的自定义实现来替换掉内置的组件。这个可以通过组件的接口设计并暴露之而非常容易的做到。比如,我们的框架可以规定消息头MessageHeader中必须包括哪些字段,但框架不能强制说MessageHeader就只能包括这些字段。这个区别正是接口与实现(类)的区别。框架提供的是一系列的接口和这些接口之间的相互联系,以构成骨架;应用实现这些接口以形成“血”和“肉”来填充这个骨架从而得到一个“有机体”。
空谈了这么多,举两个例子吧,这两个例子都是关于ESFramework的。
第一个例子是,有段时间将ESFramework定位为一个应用框架,期望其能适用于所有的C/S应用,于是,在ESFramework中包含了大把与应用相关的东西,使得ESFramework越来越复杂和庞大。正如,能治百病的药治不了任何病一样,能满足于所有应用的框架几乎不会被任何一个应用采用。对这个错误的解决方案的改成是,将ESFramework定位于一个单纯的通信框架,这会大大拓宽它的复用范围。(更详细描述可以参见
ESFramework 最新进展 -- ESFramework体系)
第二个例子是,在早期版本的ESFramework中有个名为ServiceType的枚举,它将所有的消息进行了分类,说实话,这种分类非常适合IM系统,但对其它C/S系统并不一定合适。而且ESFramework还针对这个ServiceType分类提供了对应的内置的消息处理器(详细情况)。现在看起来,这种做法是多么的笨!在后期的ESFramework版本中,ESFramework对消息如何分类再没有任何干涉,那些本不应该存在的消息处理器也删除了。取而代之的是使用一个DataDealerBagList,应用可以向其中添加任何消息处理器,只要应用将消息处理器与消息类型进行了正确的匹配就可以。
两个例子说完了,最后总结一下,我们的第一个经验是:不要为框架作“过多”的假设,而不是:不要为框架作“任何”假设。一个没有任何假设的框架,从另一个方向看,就是一个万能的、能解决任何问题的框架,我们知道,这样的框架是不存在的,即使存在,也是毫无用处的。
不要为框架作“过多”的假设,那么何谓“过多”了?有很多特性/组件,我们可以一眼就分辨出,它是应该存在于框架中,还是应该交给应用。但是,也有一些特性/组件,它们的所宿地就不是那么清楚了,这时,需要设计者的权衡,这种权衡恰恰是最体现设计者内功的地方。难怪有人说,软件设计也是门艺术,因为随时随地你都可能碰到需要权衡的地方!(每个程序员都希望当架构师,但是架构师并不是那么好当,因为架构师就像一个艺术家一样,需要做非常多恰当的权衡。如果任何人都能作出和你同等水平的决策,那你设计的这个决策便不值钱了。软件的艺术之美源于权衡(Trade-off))
|