top-down bottom-up mini-languages (little language) Fluent-Interface DSL
top-down bottom-up具体描述参考Paul Graham的Programming Bottom-Up
top-down design 自顶向下:传统的方法,将最初的需求一步步细化切分,到最终实现时的粒度。
bottom-up design 自底向上:在原始语言环境下建立很多库函数、基础组件,使用这些库函数和基础组件去搭建应用系统。就像先使用原始的工具做好各种各样的积木,再用积木去搭建各种形状的房子。
top-down是一种分解(decomposition)策略,bottom-up是一种合成(composition)策略。
top-down的优势: 简单,分解是人类思维的特长;推迟构建细节,预先将细节变化的骚扰封装到类体系结构中;较早的找出功能,进行整体组织。
top-dowm的劣势: 受底层复杂度影响比较大,因为底层复杂度一开始可能不会被细致考虑。
bottom-up优势: 较早的考虑底层复杂度,有利于设计出更好的高层组件,并有利于底层组件的通用性。
bottom-up弱势: 对于人类思维,从底层开始进行抽象的难度比较大;底层零件可能与高层组件不适合;底层的复杂度可能会把设计过程击垮。
top-down是一种分解(decomposition)策略,bottom-up是一种合成(composition)策略。
top-down的优势: 简单,分解是人类思维的特长;推迟构建细节,预先将细节变化的骚扰封装到类体系结构中;较早的找出功能,进行整体组织。
top-dowm的劣势: 受底层复杂度影响比较大,因为底层复杂度一开始可能不会被细致考虑。
bottom-up优势: 较早的考虑底层复杂度,有利于设计出更好的高层组件,并有利于底层组件的通用性。
bottom-up弱势: 对于人类思维,从底层开始进行抽象的难度比较大;底层零件可能与高层组件不适合;底层的复杂度可能会把设计过程击垮。
大部分时候我们都是两种方法同时运用,一个项目开始,将采用自顶向下的方法;个人、团队总会积累很多自己的类库、组件、工具、框架,随时可以运用到项目中,不少公司会有专门的团队负责这些事情,这些都是一种自底向上的方式。
不是说复用了几个组件就叫做自底向上的设计,自底向上设计思想力求最终实现需求的代码尽量简洁精悍,而位于其下各种层次的工具、组件持续不断的扩展完善是核心。
mini-languages (little language) 具体描述参考Eric Raymond的Minilanguages
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。因为不停的补丁和功能特性的添加,将在不知不觉中导致隐式流程和混杂的数据结构,带来复杂性而失去袖珍型的含义。正确的做法是重新设计、审视。
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。因为不停的补丁和功能特性的添加,将在不知不觉中导致隐式流程和混杂的数据结构,带来复杂性而失去袖珍型的含义。正确的做法是重新设计、审视。
FluentInterface 具体描述参考Martin Fowler的FluentInterface
例如创建一个订单,一般做法可能是这样:
private void makeNormal(Customer customer)
{
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);
}
{
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是指这样的风格:
private void makeFluent(Customer customer)
{
customer.newOrder()
.with(6, "TAL")
.with(5, "HPK").skippable()
.with(3, "LGV")
.priorityRush();
}
{
customer.newOrder()
.with(6, "TAL")
.with(5, "HPK").skippable()
.with(3, "LGV")
.priorityRush();
}
这种形式的代码简短清晰,表达的意义明确,甚至领域专家也能明白,Martin Fowler把这种归为Internal DSL的一种形式,衡量接口是否流畅也是基于DSL的品质。
关键之处在于综合考虑这些方法被使用的上下文环境,确定返回的类型。例如上面示例中的with()方法是新增一个订单行项目并添加到订单中,一般直观的做法是返回新创建的订单行项目对象,但在这个上下文中它返回的是订单对象。
Martin提到一个问题,方法名称、返回类型等设计,在这个方法被使用的上下文环境中,是有意义和流畅的,但对于对象本身,可能没有什么意义,难于理解(因为脱离了上下文环境)。
Martin 也提到这种方法目前运用的不多,关于它的优点和缺点有待试验验证。Eric Evans提到在值对象上使用这种方式比较方便。对于上面的示例,我们可以设想,假如with()方法在某些上下文环境中要求返回订单行项目对象,才能使上下文环境变得fluent,那就存在冲突了。因为值对象被使用的上下文环境会比较单一,所以Eric
Evans的说法不言自明。
我们看看实际中运用的例子,例如NHibernate中的IQuery、 ICriteria接口,以及它内部的SqlString等不少对象,JMock的那些示例等等,也都只是在值对象、服务对象上运用。用mini-language、bottom-up的概念来看,存在特定领域能够运用也就足够,一个解决方案需要面向通用领域总是不大可能的,或者带来的复杂性将超过方法本身的优势而变得无效。
Piers Cawley的Fluent Interfaces也谈了他的想法,以及怎样写Fluent interfaces的一些内容。DSL,具体描述参考Arie van Deursen的Domain-Specific Languages: Annotated Bibliography
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的各个方面。
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框架呢?进行细节的探究也没什么必要了。