从Moco谈程序库设计

开发 后端
设置服务器,也就是在什么样的请求,给什么样的应答。Moco有一个很关键的起点,那便是DSL,要知道,之前的一段时间我刚刚完成了《领域特定语言》的翻译。于是,API的可读性成了一件非常重要的事。

Moco在程序库设计包括两个方面,如何设置服务器和如何让服务器运行起来。

先说简单的,如何让服务器运行。最简单的选择是让用户自己启动服务器,然后,在测试结束之后关闭服务器。这么一来,每个测试都大概会是这样:

  1. public void shouldWork() {  
  2.   ... setup server ...  
  3.   try {  
  4.     server.start();  
  5.     ... your test here ...   
  6.   } finally {  
  7.     server.stop();  
  8.   }  

作为一个框架,留给客户使用的接口一定简单,把实现细节封装在框架以内。如果每个测试都这么写,用户很快就会骂娘了。现在的Moco的做法是使用了running方法,把Server启停的细节封装了起来。

  1. running(server, new Runnable() {  
  2.   @Override 
  3.   public void run() throws Exception {  
  4.     assertThat(helper.get(root()), is("foo"));  
  5.   }  
  6. }); 

我知道你不喜欢匿名内部类,我也不喜欢,但是,这是Java的局限。等到Java 8驾临,相信一切会有所改观。

设置服务器,也就是在什么样的请求,给什么样的应答。Moco有一个很关键的起点,那便是DSL,要知道,之前的一段时间我刚刚完成了《领域特定语言》的翻译。于是,API的可读性成了一件非常重要的事。

说起来很简单,但是,要知道匹配请求的条件有很多,而且还可能任意组合。如果这些条件是彼此独立的(“或”),我可以用变参来解决,但有时,这些条件是相关的(“与”),那该怎么办呢?拜函数式编程思路所赐,我想到了函数组合的方式。

我知道,Java没有一等公民的函数,但是,Java里有一等公民的对象,我们可以用对象模拟函数,事实上,这也是很多面向对象程序设计语言解决函数组合的一种方式,而这种对象称为函数对象,也叫functor。

把需要的条件封装成一个个函数对象,然后通过几个简单的运算符就可以将它们组合起来。目前Moco里提供了and和or两个运算符用于组合。

  1. server.request(and(by(uri("/foo")), by(method("put")))).response("bar");  
  2. server.request(or(by("foo"), by(uri("/foo")))).response("bar"); 

随着面向对象思路的普及,函数组合的写法会越来越普遍的,它会成为程序员工具箱中的必备品。

DSL是一个重要的考量,所以,这里没有直接new对象,而是用函数(比如uri、method)做了一层封装。或许你会说,那为了可读性,我可以把类名设计成一个可读的样子,但new一个对象的问题在哪呢?就在new上。一方面,new是为了创建一个对象,这是实现细节,与我们要表达的内容不在一个层次,另一方面,你会发现多个new会让这句话显得不连贯。这句话?是的,我们的写法就像是用一句话声明一个东西一样,而我们期望自己讲的内容有一定的连贯性,而这才是DSL。

一旦设计成DSL,单个函数的文档也就失去了意义,更重要的是一个用法的文档。所以,在Moco里,我写了文档,却没有写JavaDoc。

在Moco里,请求和应答里可能有同样的东西,比如file,而request和response的参数类型是不一样的。如果file能够返回不同类型是***的,可惜在Java里面,我们不可能根据返回类型做重载。一种解决方案是为request和response分别设计一个函数,比如requestFile和responseFile,但显然,这种做法会把同样的逻辑分散到两个类实现里,而且这样需要共享的东西不只是file,还有其它一些东西。

在计算机问题里,加上一个中间层永远是一个重要的解决方案。这也是by的目的所在。这样一来,这些共享的东西就可以做成一个抽象(Resource),对请求来说,只要有一个by,就可以适配成Matcher。

在Moco设计中,还涉及到了一个框架设计中很重要的点:类型。Java是静态类型程序设计语言,利用好类型,就可以在编译期把错误报出来,而不会留到运行时,fail fast是很重要的一个程序设计实践。

Moco目前支持一些Content Type检测。如果把这个Content Type放到Resource里面,你会发现不是所有Resource都需要这个,比如method,我们当然可以在Resource接口里支持Content Type,在不需要的地方抛出异常。

在Moco里,我的做法是引入了一个ContentResource,支持Content Type,它继承于Resource,这样依赖,需要Content Type的接口(比如content),直接支持ContentResource,而其它部分继续支持Resource,这样,如果一不小心用错的话,编译就会报错。

再有一点是关于Publish接口,在Moco的实现里有一个Setting,还有一个BaseSetting,如果观察实现,在设置Request会创建出一个BaseSetting,但其返回的接口是Setting。这么做的一个原因是,因为Setting出现在用户可以使用的接口上,而BaseSetting是留给内部实现的,它提供了一些用于内部实现的方法,而用户是不应该使用这些方法的,所以,将二者做了一个分离,这样一来,保证了用户不会误操作。同样处理的还有HttpServer和ActualHttpServer。

小结一番,简化用户接口,设计DSL,利用好类型,区分Publish接口。

原文链接:http://dreamhead.blogbus.com/logs/232745877.html

责任编辑:林师授 来源: blogbus
相关推荐

2010-01-19 18:04:02

C++标准程序库

2010-01-27 17:36:24

C++程序库

2010-02-24 16:11:42

Python程序库

2010-01-19 09:39:43

C++标准程序库

2010-01-14 09:43:26

C++标准程序库

2010-01-19 09:39:43

C++标准程序库

2013-04-22 09:21:43

2013-08-15 09:39:20

MOCOJava

2010-01-15 14:59:54

C++标准程序库

2013-02-21 10:13:36

2010-09-29 14:21:22

2020-03-31 22:09:01

React应用程序

2011-12-06 10:04:12

QQ手机移动应用应用设计

2011-04-19 08:59:33

2009-06-18 10:13:00

2024-01-22 15:26:27

前端开发JavaScrip

2015-04-20 09:50:58

程序员

2014-07-26 22:18:51

2015-04-29 09:58:48

开源C++

2015-04-14 11:15:18

程序员创业程序员谈创业
点赞
收藏

51CTO技术栈公众号