“意图导向编程”,Programming by Intention
,也称目的导向编程/自顶向下编程.
其基本思想是:
每个问题均可以分解成一系列的功能性步骤,在写代码的过程当中,会按照顺序有意识的去完成每个步骤;而意图导向编程则是先假设每个步骤都有一个理想的方法来完成,而不关注每一个步骤的具体实现,在这种状况下,须要关心的是每一个方法的输入参数,返回值以及什么样的名字最符合它的含义。算法
例如,建立一个服务,它接受一个业务交易,而后提交,具体的需求以下:编程
- 交易信息开始于一串标准的ASCII字符串。
- 这个信息字符串必须转换成一个字符串的数组,数组存放的值是这次交易用到的领域语言(domain language)中所包含的词汇元素(token)。
- 每个词汇元素必须标准化(第一个字母大写,其他字母小写,空格和非字符数字的符号都要删掉)。
- 超过150个词汇元素的交易,应该采用不一样于小型交易的方式(不一样的算法)来提交,以提升效率。
- 若是提交成功,API方法返回true,不然返回false。
基于“意图导向编程”的思想,咱们假设有一个类,类中有一个API实现上面的服务:设计模式
public class Transaction{ public Boolean commit(String command){ Boolean result = true; String[] tokens = tokenize(command); normalizeTokens(tokens); if(isALargeTransaction(tokens)){ result = processLargeTransaction(tokens); }else{ result = processSmallTransaction(tokens); } return result; } }
commit()
方法是程序API
,用于提供服务,而其余方法(tokenize()、normalizeTokens()、isALargeTransaction()、processLargeTransaction()、processSmallTransaction()
)都不属于这个对象API
,仅仅是实现过程当中的功能性步骤,称之为“辅助方法”(helper methods
),暂时能够将他们视为私有方法。数组
经过这样的编码方式,能够将精力集中在如何分解最终目标,以及那些全局性的问题上。
而且这种实现方式,与直接把全部代码写到一个很长的方法里相比并无增长工做量,不一样点在于思考的方式和编码的顺序(先理清总体流程,再深刻细节)。dom
若是遵循意图导向编程,那么代码将会:函数
代码的质量标准之一就是内聚性。
以类为例,每一个类都应该根据职责来定义,而且应该只有一个职责。类内部包括方法、状态以及与其余对象之间的关系,若是各个方面都紧密相关,而且围绕着这个类的惟一职责,则说这个类的内聚性很强。单元测试
若是一个方法只实现总体职责中一个单独的功能点,则说这个方法的内聚性很强。测试
人的思惟方式是单线程的,当进行“多任务”的时候,其实是在多个任务之间快速切换而已,人们仍旧习惯于一次只思考一件事情。意图导向编程正是利用这一事实,用思惟链条单一性的特定去建立一样具有单一性的内聚方法。优化
经过阅读最初的实例代码:编码
public class Transaction{ public Boolean commit(String command){ Boolean result = true; String[] tokens = tokenize(command); normalizeTokens(tokens); if(isALargeTransaction(tokens)){ result = processLargeTransaction(tokens); }else{ result = processSmallTransaction(tokens); } return result; } }
能够发现该服务的实现流程是:
获取到一个指令,而后对指令进行分词,再把分词后获得的指令标准化,判断须要进行交易处理的类型,根据判断结果来决定进行大型事务仍是小型事务的处理,最后返回结果。
由于上面的代码只涉及到“作什么”,而不是具体的“如何作”,这种状况下,不须要注释也能读懂代码的基本逻辑,这得益于规范的命名和步骤的清晰界定。
考虑下面的实现方式:
public class Transaction{ public Boolean commit(String command){ Boolean result = true; // tokenize the string some code here some more code here even some more code here that sets tokens // normalize the tokens some code here that normalize tokens some more code here that normalize tokens even some more code here that normalize tokens // see if you have a large transaction code that determines if you have a large transaction set lt = true if you do if(lt){ // process large transaction some code here to process large transaction some more code here to process large transaction }else{ // process small transaction some code here to process small transaction some more code here to process small transaction } return result; } }
上面的实现方式是将全部逻辑写在一个大方法中,而且加了详尽的注释,但与意图导向编程的实现方式相比,注释显得很没有必要,而且代码太多,给人的心理无形中形成一种压力。
在程序的代码错误修复过程当中,寻找错误所在才是最花时间的。在遵循意图导向编程时,因为一个方法只作一件事,这个时候,若是出现错误,则可试试下面的办法:
相比于费力的查阅一大段复杂的代码,这种调试方法发现代码错误的速度要快不少。
重构系统:保持系统行为不变的状况下,更改它的结构。
加强系统:增长或修改系统的行为以符合新的需求。
重构一般认为是“清理”系统中写得糟糕的代码,重构的一个基本实现方式是:把一部分代码从一个巨大的方法中抽取出来,放到一个属于它本身的新方法中,而在原来代码中的那个位置直接调用这个新方法。
因为原来方法的一部分临时变量也须要迁移到新方法中,因此须要多个步骤才能完成一个函数的提炼。
若是采用意图导向编程,一开始就是辅助方法了,只须要把公用的辅助方法迁移到其余类便可。这样的重构是很快的(复制-粘贴)。
当系统实现后,有新需求加进来了,如:与第三方程序交互时,因为第三方程序的缘由,再也不支持某些旧版词汇,这个时候须要更新一个词汇元素,如:
public class Transaction{ public Boolean commit(String command){ Boolean result = true; String[] tokens = tokenize(command); normalizeTokens(tokens); updateTokens(tokens); if(isALargeTransaction(tokens)){ result = processLargeTransaction(tokens); }else{ result = processSmallTransaction(tokens); } return result; } }
有新需求加进来的时候,只须要在API
方法的实现流程中增长updateTokens()
方法,其余都不须要修改到,把影响降到了最低。
若是修改了标准化的算法,则更改normalizeTokens()
方法便可,其余都无需改动。在修改的过程当中,代码能很快定位。
设计的基本建议:使用服务的客户端,在设计时应该遵守的是服务的接口定义,而不是服务的具体实现。
在上面的实现中,辅助方法被定义成了私有方法,是为了避免想与外部对象发生关联,但这种状况下就不利于方法的单元测试。
咱们只能对commit()
方法进行单元测试,即测试服务的总体行为。此时测试状况比较复杂,会有不少种因素致使测试失败。
能够有以下解决办法:
- 若是辅助方法只是实现单个服务的一部分,则不必单独测试辅助方法,测试这个服务流程便可。
- 若是某些辅助方法是能被其余服务使用到的,则须要将该辅助方法单独到其余的类中,而且定义成公有的方法,则对原来辅助方法的调用就变成了对新类方法的调用,而且新类的公有方法是能进行单元测试的。
为了提升类的内聚性,须要把这个类不该该有的方法迁移到其余类中,这样可让这个类所关注的东西减小。
意图导向编程建立的方法只完成一个功能,这样避免了迁移方法是常常遇到的问题:包含不能迁走的部分。
当一个方法只作一件事时,要么所有迁移,要么不迁移。
方法迁移难,还可能因为它直接关联到了类中的状态变量,在使用意图导向编程时,习惯于将参数传递到辅助方法,而后获取一个返回结果,而不是让方法直接使用对象的状态。
从以前的重构和加强可当作,当增长需求时,只须要在流程中增长对应的辅助方法;
当须要修改需求时,只须要修改对应的辅助方法。这种修改和扩展容易定位而且不影响其余代码。
上面的例子中,若是有两个不一样的交易类型,流程步骤同样(分词、标准化、更新、处理),但每一步的实现方式不同。
使用意图导向编程时,处理每一个辅助方法具体实现不同,commit()
方法是同样的,这个时候,能够很容易的应用模板方法模式
。