接口属于客户

“接口属于客户,不属于它所在的类型层次结构。”这是Robert C. Martin在他的名著《敏捷软件开发:原则、模式、实践》中提出来的观点。因此,接口应该表达客户领域的语义,由客户代码定义和控制,并和客户代码一起打包。

计划经济和市场经济的一个关键区别在于:计划经济是卖方(供应商)驱动生产,而市场经济是买方(消费者)驱动生产。计划经济的做法是:我按我的想法生产商品,管你合用不合用。结果往往如同刘若英的歌《一辈子的孤单》里唱的那样:“喜欢的人不出现,出现的人不喜欢”——你提供的不是消费者想要的,消费者想要的你没有提供。而市场经济的做法是:深入调研消费者的真实需求,针对这个需求生产满足这个需求的产品。结果是个双赢的局面:消费者的需求得到满足,供应者得到利润,同时杜绝了绝大部分的人力、资源和成本浪费。

现在我们都知道计划经济彻底失败,市场经济占了主流。但是软件开发实践中,很多开发者不明白这个道理,不自觉地遵循了计划经济的思路。他们虽然知道要面向接口编程,但却错误地从实现者的角度定义接口,并且将接口和实现类一起打包,而不是和接口的客户一起打包。

下面我们以开发在线考试系统为例,比较这两种方式的优缺点。下图是在线考试系统的基本领域模型:

在线考试中,我们可以把整个系统分解为两个模块:题库模块(questionlib)和考试模块(exam)。题库模块负责管理题库和试题,而考试模块负责创建考试,生成试卷,接收考生答案、计算分数等等。

在题库模块中可以管理多个题库(QuestionLib),每个题库包含一批试题(Question),试题有多种类型,图中列出了单选题(SingleChoiceQuestion)和判断题(JudgementQuestion)。单选题会包括一批选项(ChoiceOption)。

在考试模块中,我们在创建一场考试(Exam)的时候需要从指定的题库中抽取一定数量的各种题型的试题,生成一份试卷(ExamPaper)。每个考生(Student)拿到这份试卷生成自己的答卷(AnswerPaper),针对试卷中的每道试题给出自己的答案(QuestionAnswer).

为了能够从题库中抽取试题生成试卷,必须定义一个题库服务接口(QuestionLibService),题库模块负责实现这个接口,而考试模块调用这个接口生成试卷。这个接口的定义如下:

1
2
3
public interface QuestionLibService {
ExamPaper generateExamPaper(Exam exam, QuestionLib questionLib);
}

这个接口表明:要根据考试exam的设定(包含哪些题型,每种题型包含多少道试题等)从指定的题库questionLib中抽选试题,生成一张试卷。

这里关键的设计决策是:

由谁来定义、控制和拥有这个接口?也就是说,这个接口属于题库模块还是考试模块?

大多数消费者选择由题库模块——接口的实现方——来定义、控制和拥有这个接口。因为这样做非常符合直觉:这是一个“题库”服务接口,当然应该由题库模块实现,并和题库模块一起打包。