您是否想知道FOR如何影响您的代码? 他们如何限制您的设计,更重要的是如何将您的代码转换为无人为含义的多行代码?
在这篇文章中,我们将看到如何将for的简单示例(由Francesco Cirillio提供- 反if活动)转换为更具可读性和精心设计的示例。
因此,让我们从使用FOR的原始代码开始:
public class Department {private List<Resource> resources = new ArrayList<Resource>();public void addResource(Resource resource) {this.resources.add(resource);}public void printSlips() {for (Resource resource : resources) { if(resource.lastContract().deadline().after(new Date())) { System.out.println(resource.name()); System.out.println(resource.salary());}}}}
请参见printSlips方法。 如此简单的方法,只有10条线计数白线,但违反了最重要的规则之一,该方法在其内部混合了不同级别的抽象而不只是做一件事。
正如罗伯特·C·马丁(Robert C. Martin)在他的著作《函数应该做的一件事》中指出的那样。 他们应该做得很好。 他们应该只做[…]。 如果一个函数仅执行比该函数的指定名称低一级的步骤,则该函数正在做一件事[…]。 。
因此,根据给定的方法外观定义,让我们回顾一下以前的方法,看看有多少事情在做
printSlips方法? 具体四个 。
public void printSlips() {for (Resource resource : resources) { #Cycleif(resource.lastContract().deadline().after(new Date())) { #Selection#Media System.out.println(resource.name()); #ContentSystem.out.println(resource.salary());}}}
该方法是循环,选择资源,访问内容以及访问媒体。 看到它们每个都属于不同的抽象级别,打印到控制台应该处于不同的级别,以检查资源是否尚未过期。
让我们看看Francesco提出的解决方案。
首先要做的是将主要功能分为三个类和两个接口,一个用于迭代资源,另一个用于选择尚未过期的资源,另一个用于打印资源。 通过这种方法,我们正在创建一个旨在扩展的解决方案,并且还提高了可读性。
现在是时候编写代码了:
如果资源满足实现条件,则将使用谓词接口来实现。
public interface Predicate {boolean is(Resource each);}
例如,在我们的例子中,接口的实现如下所示:
public class InForcePredicate implements Predicate {public boolean is(Resource each) {return each.lastContract().deadline().after(new Date());}}
我们将条件转移到InForcePredicate类。 请注意,如果我们要创建一个检查合同是否到期的类,我们将创建一个实现Predicate的新类,其内容类似于return each.lastContract()。deadline()。before(new Date()) ;
下一个接口是Block接口,它将实现对媒体的访问。 在这种情况下,要进行控制台:
public interface Block {void evaluate(Resource resource);}
及其实现:
public class PrintSlip implements Block {public void evaluate(Resource resource) {System.out.println(resource.name()); System.out.println(resource.salary());}}
再次注意,更改信息的发送位置(控制台,文件,网络等),只需实现Block接口即可。
最后一个类是一个包含资源迭代器的类,还提供了调用先前创建的每个接口的方法:
public class ResourceOrderedCollection {private Collection<Resource> resources = new ArrayList<Resource>();public ResourceOrderedCollection() {super();}public ResourceOrderedCollection(Collection<Resource> resources) {this.resources = resources;}public void add(Resource resource) {this.resources.add(resource);}public void forEachDo(Block block) {Iterator<Resource> iterator = resources.iterator();while(iterator.hasNext()) {block.evaluate(iterator.next());}}public ResourceOrderedCollection select(Predicate predicate) {ResourceOrderedCollection resourceOrderedCollection = new ResourceOrderedCollection();Iterator<Resource> iterator = resources.iterator();while(iterator.hasNext()) {Resource resource = iterator.next();if(predicate.is(resource)) {resourceOrderedCollection.add(resource);}}return resourceOrderedCollection;}}
请参阅接下来的三个要点:
- 第一个是构造函数接收资源列表。
- 第二个是select方法接收一个谓词,该谓词将被执行到迭代器中,以知道是否可以选择打印资源。 最后,返回带有资源且没有到期合同的ResourceOrderedCollection的新实例。
- 第三个forEachDo方法接收一个Block接口,该接口由资源列表的每个元素调用。
最后使用以前开发的类修改了Department类:
public class Department {private List<Resource> resources = new ArrayList<Resource>();public void addResource(Resource resource) {this.resources.add(resource);}public void printSlips() {new ResourceOrderedCollection(this.resources).select(new InForcePredicate()).forEachDo(new PrintSlip());}}
注意,现在printSlips方法包含具有相同抽象级别的单个可读行。
请注意,类名和接口名取自Francesco的示例,但是如果我也要这样做,则应选择更多具有代表性的名称。 Cirillo的方法不错,但需要考虑一些小方面。 例如,它具有“ 垂直问题 ”: Predicate接口中的InForcePredicate实例使用五行源代码来封装单个语句。
我们探索了问题的两种可能解决方案,这是Cirillio提出的最后一种解决方案。 对于此问题,还有许多其他可能且正确的解决方案,例如,使用“ 模板方法模式” ,或混合使用Lambdaj和(或不使用) 闭包( Lambdaj语法可能会有些混乱)。 它们都有优点和缺点,但是它们都使您的代码更具可读性,而且更重要,所有功能都只能做一件事情,它们只能做得很好。
作为这篇文章的最后说明, JDK 8将提供对本机关闭的支持,还将提供Lambdaj现在提供的许多功能。 同时, JDK 8不稳定(计划于2013年中期完成)或您的旧代码(从JDK 8的角度来看) 不稳定 , Lambdaj确实是一个很好的同伴。
我们不断学习。
参考: 避免FORs –我们的JCG合作伙伴 Alex Soto的“ 反若战役”,来自“ One Jar to Rule Allm All”博客。
翻译自: https://www.javacodegeeks.com/2012/11/avoiding-fors-anti-if-campaign.html