top-down是一种分解(decomposition)策略,bottom-up是一种合成(composition)策略。
top-down的优势: 简单,分解是人类思维的特长;推迟构建细节,预先将细节变化的骚扰封装到类体系结构中;较早的找出功能,进行整体组织。
top-dowm的劣势: 受底层复杂度影响比较大,因为底层复杂度一开始可能不会被细致考虑。
bottom-up优势: 较早的考虑底层复杂度,有利于设计出更好的高层组件,并有利于底层组件的通用性。
bottom-up弱势: 对于人类思维,从底层开始进行抽象的难度比较大;底层零件可能与高层组件不适合;底层的复杂度可能会把设计过程击垮。
大部分时候我们都是两种方法同时运用,一个项目开始,将采用自顶向下的方法;个人、团队总会积累很多自己的类库、组件、工具、框架,随时可以运用到项目中,不少公司会有专门的团队负责这些事情,这些都是一种自底向上的方式。
不是说复用了几个组件就叫做自底向上的设计,自底向上设计思想力求最终实现需求的代码尽量简洁精悍,而位于其下各种层次的工具、组件持续不断的扩展完善是核心。
Unix风格的mini-languages思想与自底向上异曲同工,不过Paul Graham讲的lisp自底向上在语言层面给予了支持,而Unix是围绕C的一种思考、开发方式。
Eric Raymond提到一个场景,即每百行的bug率(好像我们常用的是千行bug率,因为我们生产率高?),一个需求用原始的语言实现可能需要1000行代码,在工具、类库的支持下可能只需要几十行,直接的效果是代码短了,阅读更容易,可以减少bug的发生提高代码品质。
参考Eric Raymond对语言的分类the
Taxonomy of Languages可以看到,他把regexps、Yacc、Lex、make、XSLT、PostScript等这些,都归入mini
language范围中,因为它们都是针对特定领域专门的解决方案。
Eric Raymond提到三种实现袖珍型语言的方法或者说努力方向,2种是正确的最后一种是错误的:
1. 将程序问题的描述提升一个层次,比通用语言的描述更确切更可读。
2. 使代码更趋近于对处理特定领域问题的精简描述,就像是一种简单的语言。但注意底层代码封装的应当是复杂的数据结构,而不是控制流程,控制流程应当更显示的交由用户决定,而不是由底层隐式的代办。
3. 在原有的基础上扩展来构造mini language。因为不停的补丁和功能特性的添加,将在不知不觉中导致隐式流程和混杂的数据结构,带来复杂性而失去袖珍型的含义。正确的做法是重新设计、审视。
{
Order o1 = new Order();
customer.addOrder(o1);
OrderLine line1 = new OrderLine(6, Product.find("TAL"));
o1.addLine(line1);
OrderLine line2 = new OrderLine(5, Product.find("HPK"));
o1.addLine(line2);
OrderLine line3 = new OrderLine(3, Product.find("LGV"));
o1.addLine(line3);
line2.setSkippable(true);
o1.setRush(true);
}
FluentInterface是指这样的风格:
{
customer.newOrder()
.with(6, "TAL")
.with(5, "HPK").skippable()
.with(3, "LGV")
.priorityRush();
}
这种形式的代码简短清晰,表达的意义明确,甚至领域专家也能明白,Martin Fowler把这种归为Internal DSL的一种形式,衡量接口是否流畅也是基于DSL的品质。
Evans的说法不言自明。
Piers Cawley的Fluent Interfaces也谈了他的想法,以及怎样写Fluent interfaces的一些内容。
DSL还没有确切的正式定义,Arie van
Deursen定义如下:领域特定语言(DSL)是一种编程语言,或者是可执行的规范语言,通过适当的抽象符号,针对也仅限于为特定问题域提供有力的描述。DSL通常比较小,只有有限的抽象符号,因此也叫作micro-languages或little
languages,但针对特定问题域拥有强大的描述能力。面向业务数据处理系统的4GL将是DSL全面发展的结果。
发展过程:子函数库(Subroutine libraries) -> 面向对象框架、组件框架(Object-oriented
frameworks、component frameworks) -> DSL。
DSL设计方法:
1. 确定问题域
2. 收集问题域的相关知识
3. 将知识提炼成一定数量的语义符号和操作
4. 设计一个能够精简的描述问题域中应用程序的DSL
5. 创建实现语义符号的库
6. 设计和实现一个编译器,将DSL程序转换成一系列的库调用
7. 使用DSL编写程序,进行编译
DSL实现方式:
1. 解释或者编译。从头开发一个编译器或者解释器的成本可能太高,也可以从基础语言上扩展来实现,就是指下面的三种方法了。
2. 嵌入式语言(Embedded
languages)或领域特定的库。在基础语言的功能上实现针对特定问题域的组件、库(即可以看作是用户自定义语法)。优点是重用了基础语言的编译器、解释器,缺点是可能受限于基础语言,使得DSL表达力不足。
3. 预处理或者宏处理。即使用代码生成或者脚本、宏技术,优点是简单,缺点是静态检查、优化不是在领域层进行,反馈的错误信息也是基础语言层的,运行时的。
4. 可扩展的编译器或解释器。即开发一个通用的编译器或者解释器,可以扩展运用到多个其它领域,Tcl解释器是一个好的例子,它已经被扩展运用到很多领域中。
Arie van Deursen的文章给出了大量的参考资料,通过这些可以全面的了解DSL的各个方面。
Internal DSL, External DSL,具体描述参考Martin
Fowler的DomainSpecificLanguage和DslBoundary
可以看出,Martin将上面提到的DSL实现方式的2 Embedded languages或Libraries称为Internal
DSL,而其它的几种形式则称为External DSL。
DSL的分类是件很让人疑惑的事情,很多时候仅仅是概念上的不同而已,Martin在文中解释了Internal DSL与API的区别,External
DSL与通用语言(GPL)的区别。其实在.Net这样的平台下,要明确的区分开Internal DSL和External
DSL都有点困难,例如RegularExpression、XSLTransformation、LINQ等平台就已经提供了。
Fluent Interface为Internal
DSL的一种形式,Nhibernate等是Internal还是External的,自己开发的OR框架呢?进行细节的探究也没什么必要了。