2018-09-25


java学习笔记(三)

简单的讲讲行为参数化传递代码,这也是Java8实战的第二章

应对不断变化的需求

在软件工程中,一个从所周知的问题就是,不管你做什么,用户的需求总是会变的(PM的需求总是会变的)。编写能够应对变化需求的代码并不容易。行为参数化就是要帮助你处理频繁更变的需求的一种软件开发模式。一言以蔽之,它意味着拿出一个代码块,把它准备好却不去执行它。这个代码块以后可以被你程序的其他部分调用,这意味着你可以推迟这块代码的执行。例如,你可以将代码块作为参数传递给另外一个方法,稍后再去执行它。这样,这个方法的行为就基于那块代码被参数化了。

我们现在来看一个例子,查找库存中所有绿色苹果。

初试牛刀:筛选绿苹果

可能你选择最初的解决方案就是这样:

private static List<Apple> filterGreenApples(List<Apple> apples) {
    List<Apple> result = new ArrayList<Apple>();
    for (Apple apple : apples) {
        if ("green".equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}

一个 if 语句就能达到我们想要的功能,如果现在还需要筛选红色的苹果呢?只需要简单的复制这个方法,改变函数名和 if 条件也能达到目的。但是如果要筛选各种颜色的苹果呢?你就需要考虑将其抽象化,以应变不断变化的需求。

再展身手:把颜色作为参数

给方法加一个颜色参数,这样就能适应变化了:

private static List<Apple> filterApplesByColor(List<Apple> apples, String color) {
    List<Apple> result = new ArrayList<Apple>();
    for (Apple apple : apples) {
        if (color.equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}

是不是比前面的简单了?但是现在有需要筛选重量超过150克的苹果,你会不假思索的写下:

private static List<Apple> filterApplesByWeight(List<Apple> apples, int weight) {
    List<Apple> result = new ArrayList<Apple>();
    for (Apple apple : apples) {
        if (apple.getWeight() > weight) {
            result.add(apple);
        }
    }
    return results;
}

解决方法很简单,但是你复制了大部分的代码来实现遍历库存,并对每个苹果应用筛选条件。这样破坏了DRY(Don’t Repeat Yourself 不要重复自己)的软件工程原则。你需要考虑如何将颜色和重量结合成一个方法。

第三次尝试:对你能想到的每个属性做筛选

一个笨拙的尝试:

private static List<Apple> filterApples(List<Apple> apples, String color, int weight, boolean flag) {
    List<Apple> result = new ArrayList<Apple>();
    for (Apple apple : apples) {
        if ((flag && apple.getWeight() > weight) || (!flag && color.equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}

这是个非常糟糕的解决方案,不明语义的 flag ,如果再增加属性,那又该怎么解决呢?需要组合筛选又怎么办呢?为了实现灵活的筛选,可以考虑如何将行为参数化。

行为参数化

现在需要一种比添加很多参数更好的方法来应对变化的需求。让我们退一步来看看更高层次的抽象。一种可能解决方案是对你的悬着标准建模:你考虑的是苹果,需要根据Apple的某些属性(比如它是绿色的吗?重量超过150克吗?)来返回一个布尔值。我们把它称为谓词。让我们定义一个接口来对选择标准建模:

public interface ApplePredicate {
    boolean test(Apple apple);
}

这样就可以实现不同的选择标准了

选绿色苹果:

public class AppleGreenColorPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
}

选重的苹果:

public class AppleHeavyWeightPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}

你可以把这些标准看作 filter 方法的不同行为。这些和“策略设计模式”相关,它让你定义一族算法,把他们封装起来(称为“策略”),然后运行时选择一个算法。下面就是将filterApples方法让它接受一个ApplePredicate对象就可以了。

第四次尝试: 根据抽象条件筛选

新的filterApples方法是这样的:

private static List<Apple> filterApples(List<Apple> apples, ApplePredicate<Apple> applePredicate) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (applePredicate.test(apple)) {
            result.add(apple);
        }
    }
    return appleList;
}

现在代码已经灵活多了,读起来也很简单明了。现在你可以创建不同的ApplePredicate对象,并将它们传递给filterApples方法。这样就可以根据不同的条件来创建一个类并且实现ApplePredicate就可以了。

选择红苹果:

public class AppleRedAndHeavyPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return "red".equals(apple.getColor()) && apple.getWeight() > 150;
    }
}

List<Apple> filterApples = filterApples(inventory, new AppleRedAndHeavyPredicate());

现在filterApples方法的行为已经取决于通过ApplePredicate对象来实现了。这就是行为参数化了!行为参数化的好处在与你可以把迭代要筛选的集合的逻辑与对集合中每个元素应用的行为区分开来。这样你就可以重复使用同一个方法,给它不同的行为来达到不同的目的。

但是很令人遗憾的是,filterApples方法只接受对象,你的代码必须包裹在对象里以内联的方式”传递代码“。你已经看见了,可以把行为抽象出来让你的代码适应需求变化,但这个过程很啰嗦。

对付啰嗦

现在当要把新行为传递给 filterApples 方法的时候,你不得不声明几个ApplePredicate接口的实现,然后实例化。这很浪费时间。有没有更简单的方式呢?Java 有一种匿名类的机制,你可以同时声明并实例化一个类。

第五次尝试:使用匿名类

下面是使用匿名类重筛选红苹果的例子:

List<Apple> filterApples = filterApples(inventory, new ApplePredicate(){
    @Override
    public boolean test(Apple apple) {
        return "red".equals(apple.getColor());
    }
});

又进一步简化了,但是匿名类还是不够好。匿名类有些笨重,有着过多的模板代码。有时候会令人费解,这里就不举例子了。

这里你是不是想到什么了,既然匿名类还不够简洁,那使用匿名表达式(lambda)呢?

第六次尝试:使用Lambda表达式

使用lambda来简化代码,筛选红苹果:

List<Apple> filterApples = filterApples(inventory, apple -> "red".equals(apple.getColor());

通过Lambda的方式来筛选红苹果并且是重苹果:

List<Apple> filterApples = filterApples(inventory, apple -> "red".equals(apple.getColor()) && apple.getWeight() > 150);

不得不承认这代码已经很干净了,就只是简单的描述问题本身。现在我们可以在通往抽象的路上更进一步,就是把filterApples方法和ApplePredicate接口泛化,是他们不仅仅适用Apple。

第七次尝试:将 List 类型抽象化

public interface Predicate<T>{
    boolean test(T t);
}

public static<T> List<T> filter(List<T> list, Predicate<T> p) {
    List<T> result = new ArrayList<>();
    for(T e : list) {
        if(p.test(e)) {
            result.add(e);
        }
    }
    return result;
}

现在你可以把 filter 方法用在香蕉,葡萄等水果和其他列表上了。这就是Java8 给我们带来的,灵活性和简洁性之间微妙的平衡点。

总结

  1. 行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
  2. 行为参数化可以让代码更好的适应不断变化的要求,减轻工作量。
  3. 传递代码,就是将新行为作为参数传递给方法。但在Java8之前这实现起来很啰嗦。为接口生命许多只是用一次的实体类而造成的啰嗦代码,在Java8之前采用匿名类来减少。
  4. JavaAPI包含了很多可以用不同行为进行参数化的方法,包括排序、线程等。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容