Message Chains与Fluent Interface
Martin Fowler在其名著《重构》一书,提到了Message Chains坏味道。这种坏味道的表现特征是当调用者需要执行某个功能时,需要调用连续的多个方法,才能最终达成目的。这种调用方法的消息传递就像链条一样,因此Fowler将其命名为Message Chains。
这种坏味道暴露了过多实现细节。它将获得最终结果的整个过程暴露无遗。它不厌其烦地陈述着:首先该获得什么对象,然后再调用返回结果的什么方法获得中间对象,接着,再调用中间对象的对应方法去获取结果,如此传递下去,直到抵达我们的目的地。服务的提供者弄错了调用者的角色。调用者不是游客,需要导游展示沿途的风景;调用者更像是餐厅的食客,只在乎菜品的色香味,却并不关心这道菜品是怎样做出来的,那是厨师的职责。
导致Message Chains坏味道的原因是设计者没有很好地理解封装的概念,他希望服务更周到一些,可这种周到带来的结果是自讨没趣。
在设计中,有另外一种常见的模式与之相似,却能取得不同的效果,那就是Fluent Interface模式,它通常被翻译为“连贯接口”。Fluent Interface主要应用在DSL(Domain Specific Language)中。Martin Fowler将其定义为“能够为一系列方法调用中转或维护指令上下文的行为”。Fluent Interface中定义的方法常常是返回该类型自身的实例。Fluent Interface模式在很多动态语言如php、ruby中得到大量运用。C#的LINQ也使用了该模式。ThoughtWorks的意见领袖Neal Ford在其系列文章Evolutionary architecture and emergent design中,介绍了该模式的运用。文中提供了如下代码,以演示Java中如何实现Fluent Interface:
public class Appointment { private String _name; private String _location; private Calendar _startTime; private Calendar _endTime; public Appointment(String name) { this._name = name; } public Appointment() { } public Appointment name(String name) { _name = name; return this; } public Appointment at(String location) { _location = location; return this; } public Appointment at(Calendar startTime) { _startTime = startTime; return this; } public Appointment from(Calendar startTime) { _startTime = startTime; return this; } public Appointment to(Calendar endTime) { _endTime = endTime; return this; } public String toString() { return "Appointment:"+ _name + ((_location != null && _location.length() > 0) ? ", location:" + _location : "") + ", Start time:" + _startTime.get(Calendar.HOUR_OF_DAY) + (_endTime != null? ", End time: " + _endTime.get(Calendar.HOUR_OF_DAY) : ""); } }
以下是Fluent Interface的使用:
AppointmentCalendarChained calendar = new AppointmentCalendarChained(); calendar.add("dentist"). from(fourPM). to(fivePM). at("123 main street");
其中,AppointmentCalendarChained类的add()方法返回的是实现了Fluent Interface模式的Appointment对象。
采用Fluent Interface模式的代码,具有自我阐述领域逻辑的能力,它不同于Message Chains,因为它的意图并不是要展现消息传递的过程。Fluent Interface中每个方法都是调用者需要关注的。而且,Fluent Interface方法可以自由组合,并不利于封装。由于调用方式是完全一致的,只要有了合理的命名,代码的连贯性就如行云流水。领域驱动设计的布道者Eric Evans曾提及,他所接触到的Fluent Interface都是用来装配Value Object。Value Object没有具有业务领域意义的实体与之对应,它们可以被随心创建随心抛掉。本例中的Appointment正是这样的Value Object。有关Value Object的知识,请参考Eric Evans的经典名著Domain Driven Design-Tackling Complexsity in the Heart of Software。