通过行为参数化传递代码

行为参数化是可以帮助你处理频繁变更的需求的一种软件开发模式

引言

1.首先我们看下实现从苹果列表中选出所有的绿色的苹果的代码

public static List<Apple> filterGreenApples(List<Apple> inventory){
        //存储所有选出的苹果
        List<Apple> result = new ArrayList<>();
        for(Apple apple : inventory){
            //选出绿色苹果
            if("green".equals(apple.getColor())){
                result.add(apple);
            }
        }
        return result;
    }

结果输出

public static void main(String[] args) {
        // 苹果列表
        List<Apple> list = Arrays.asList(new Apple(80,"green"),new Apple(155,"green"),new Apple(120,"red"));
        //挑选苹果
        List<Apple> listGreen = filterGreenApples(list);
        for(Apple apple:listGreen){
            System.out.println("苹果颜色是:"+apple.getColor()+";重量是"+apple.getWeight()+"g。");
        }
    }
//执行结果
苹果颜色是:green;重量是80g。
苹果颜色是:green;重量是155g。

2.当我们只有这一个需求的时候,上述实现没有问题,如果我们需要筛选红色的苹果呢,那我们可以将颜色作为参数进行实现

    public static List<Apple> filterGreenApples(List<Apple> inventory,String color){
        //存储所有选出的苹果
        List<Apple> result = new ArrayList<>();
        for(Apple apple : inventory){
            //选出颜色为color的苹果
            if(apple.getColor().equals(color)){
                result.add(apple);
            }
        }
        return result;
    }

此时我们可以通过颜色挑选苹果

//挑选绿色苹果
 List<Apple> listGreen = filterGreenApples(list,"green");
//挑选红色苹果
 List<Apple> listRed = filterGreenApples(list,"red");
//挑选xx颜色的苹果
 List<Apple> listRed = filterGreenApples(list,"xx");
……

3.这时候我们又有新的需求来了,我们要根据苹果的重量来筛选,比如筛选重量大于150的苹果,我们根据上面的解决办法,实现如下:

public static List<Apple> filterGreenApples(List<Apple> inventory, int weight){
        //存储所有选出的苹果
        List<Apple> result = new ArrayList<>();
        for(Apple apple : inventory){
            //选出重量大于weight的苹果
            if(apple.getWeight()>weight){
                result.add(apple);
            }
        }
        return result;
    }

此时我们可以通过苹果的重量挑选苹果

//挑选苹果
        List<Apple> listWeight = filterGreenApples(list,150);
//执行结果

4.那么当我们想要既能通过苹果的重量筛选&也能通过颜色进行筛选呢?我们需要添加上述2和3的两个代码;当然,我们还是可以根据上面的思路,然后增加一个标志位来判断是根据颜色还是根据重量筛选,实现如下:

public static List<Apple> filterGreenApples(List<Apple> inventory, String color,int weight,boolean flag){
        //存储所有选出的苹果
        List<Apple> result = new ArrayList<>();
        for(Apple apple : inventory){
            // 参数flag表示标志位,flag为true表示通过颜色筛选,false表示通过重量筛选
            if((flag && apple.getColor().equals(color))||(!flag && apple.getWeight()>weight)){
                result.add(apple);
            }
        }
        return result;
    }

调用方式如下:

// 通过颜色筛选
List<Apple> listColor = filterGreenApples(list,"green",150,true);
// 通过重量筛
List<Apple> listWeight = filterGreenApples(list,"green",150,false);

建议最好不要这样用,首先可读性不强,flag的true和false分别代表什么呢?其次,当Apple含有其他属性时(比如产地、甜度等),编写的代码就会比较复杂,这时候就需要引入“行为参数化”这个开发模式,行为参数化就是用来处理频繁变更的需求的开发模式。

行为参数化传递参数的方式

1.建模,即选择的苹果的策略

引言中我们使用的方式是“值的参数化”,这种方式并不灵活,我们需要将苹果的属性抽象出来(不考虑实际有哪些属性),返回一个boolean值,告诉用户是否满足筛选条件(即符合这个属性);这就是一个抽象的标准,我们通过这个标准建模:

public interface ApplePredicate {
    // 任何类要实现此接口都要实现test方法,传入的参数必须是Apple类型,返回值为boolean
    boolean test (Apple apple);
}

2.使用多个实现实现不同的选择标准

这时候我们可以通过接口的实现类实现多个筛选标准了,比如我们需要筛选出重的苹果,那我们就可以这样实现:

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

筛选出绿色的苹果的实现如下:

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

3.传递代码
这时候我们将ApplePredicate进行了各种实现,我们怎么利用这些实现来达到我们筛选的目的呢?我们需要给filterApples方法添加一个参数,让它接收ApplePredicate对象,对Apple对象进行条件测试。这就是行为参数化:让方法接受多种行为(或战略)作为参数,并在内部使用,来完成不同的行为。那我们看下修改后的filterApples方法:

 public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){
        List<Apple> result = new ArrayList<>();
        for(Apple apple : inventory){
            //ApplePredicate对象p封装了测试苹果的条件
            if(p.test(apple)){
                result.add(apple);
            }
        }
        return result;
    }
  • 传递代码/行为
//首先AppleHeavyWeightPredicate和AppleGreenColorPredicate都是ApplePredicate的实现类
        ApplePredicate p1 = new AppleHeavyWeightPredicate();
        ApplePredicate p2 = new AppleGreenColorPredicate();

看到这边相信大家都知道下一步怎么用这个方式来筛选一个绿色的苹果了

        List<Apple> listGreen = filterApples(list,new AppleGreenColorPredicate());

这表明filterApples方法的行为取决于你通过ApplePredicate对象传递的代码,这就相当于你把filterApple方法行为参数化了。
其实在这个例子当中,最重要的就是test方法的实现,它定义了filterApples方法的新行为,但是filterApples只能接受对象,所以我们必须把test方法实现的代码包裹在ApplePredicate对象里,即我们通过一个实现了test方法的对象来传递布尔表达式,这种做法类似于用在“传递代码”。通过下面的图我们形象的看一下


image.png
  • 多种行为,一个参数
    行为参数化的好处在于你可以把迭代要筛选的集合的逻辑与对集合中么个元素应用的行为区分开来,这样我们可以重复使用同一个方法,给它不同的行为来达到不同的目的。


    image.png

    4.优化代码
    通过上面这种实现方式,虽然可以让代码适应需求的变化,但是这个过程很啰嗦,我们需要声明很多只要实例化一次的类。这时候我们就想到了java中的匿名类。那我们的实现如下:
    定义筛选标准建模&filterApples实现方式不变,只是将需要实例化的类使用匿名类声明

//筛选红色苹果
        List<Apple> listRed= filterApples(list, new ApplePredicate() {
            @Override
            public boolean test(Apple apple) {
                return "red".equals(apple.getColor());
            }
        });

这种方式虽然省去了声明类的这个步骤,但是还是也还是存在不易读以及笨重的问题,java8中通过lambda表达式很好的解决了这个问题,让代码看起来更干净。
上述代码可以使用lambda重写如下:

//筛选红色苹果
        List<Apple> listRed2= filterApples(list,(Apple apple)->"red".equals(apple.getColor()));

5.list类型的抽象化
进行过上述抽象后,filterApples方法只适用于Apple,我们还可以通过将list类型抽象化,使我们的代码更加灵活。

  • 抽象标准建模
public interface Predicate<T> {
    boolean test(T t);
}
  • 抽象filter方法
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;
    }
  • 调用方式一致
//筛选红色苹果
        List<Apple> listRed= filterApples(list,(Apple apple)->"red".equals(apple.getColor()));

这时候filter方法就不仅仅可以用于Apple的列表了,香蕉、橘子等都可以适用了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容