技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 编程语言 --> 从Moco谈程序库设计

从Moco谈程序库设计

浏览:739次  出处信息

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

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

public void shouldWork() {
... setup server ...
try {
  server.start();
  ... your test here ...
 } finally {
  server.stop();
}
}

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

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

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

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

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

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

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

server.request(and(by(uri("/foo")), by(method("put")))).response("bar");
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接口。

QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
后一篇:闲话命名 >>
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1