我最近在做某一个开源产品的项目,项目组的开发方式是敏捷开发+TDD。
项目场景:开发一套开源的持续集成环境,集成SVN、Jira、Hudson/Jenkins、Git、Trac、Sonar等,这些平台都是假设部署在Linux上,用java程序在各个平台上实现“创建项目”、“创建用户”、“分配角色权限”等功能。例如,通过编写java程序,在trac上创建一个项目,分配这个用户的某些权限
我用测试先行的方式,先写测试样例,用最原始的方式为每个方法(createProject,createRole)编写单元测试,这里说一下,在部署了Trac的Linux服务器上创建项目或角色等,都是通过Linux安装了Trac提供的命令实现功能,例如创建项目就是"trac-admin /usr/share/trac/projects/project1 initenv project1 sqlite:db/trac.db",创建角色并分配到某个默认权限的命令是"trac-admin /usr/share/trac/projects/project1 permission add role1 WIKI_ADMIN"。SVN也会有类似的实现方式,但其他平台可能未必是如此,可能是通过服务器提供的接口来实现的。
java连接Linux远程执行命令是使用第三方的jar,叫做"com.trilead.ssh2",通过建立Connection作为连接,用Session执行命令
在编写了单元测试并成功实现功能后,发现其实这两个功能的实现方式都是相同的,只是限于命令的不同而已(事实上,通过TDD测试先行,有些时候会为开发实现带来很多方便之处,这里省略一万字……),为了让功能最大重用,分离变化点,把所有的执行都当做命令实现类(例如Trac创建项目叫做TracCreateProjectCommand类,Trac创建角色叫做TracCreateRoleCommand,如此类推),各自实现一个Command接口,命令接口的执行通过一个叫做CommandExecutor的类来负责,这样,一个有雏形的基于命令模式的设计模式初步呈现。
经过多次代码实现和对应的单元测试后,再对各个Command接口实现类进行进一步封装,例如所有Trac相关的使用TracCommand抽象类执行操作,该类实现Command接口,同时作为TracCreateProjectCommand和TracCreateRoleCommand类的父类,该父类用于作为这两个子类的模板,封装了java远程SSH2的实现,子类只需要实现对应的返回命令方法即可。
经过如此的封装后,从整体角度来看,外部调用CommandExecutor执行一条Command接口实现子类,执行对应的功能,此调用过程的设计理念是一个典型的命令模式。而命令实现类内部的模板实现方式,则是典型的模板方法模式
最终的类图结构如下