通过行为参数化传递代码

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

引言

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

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

结果输出

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

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

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

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

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

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

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

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

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

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

public static List filterGreenApples(List inventory, String color,int weight,boolean flag){
        //存储所有选出的苹果
        List 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 listColor = filterGreenApples(list,"green",150,true);
// 通过重量筛
List 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 filterApples(List inventory,ApplePredicate p){
        List 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 listGreen = filterApples(list,new AppleGreenColorPredicate());

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


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


    image.png

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

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

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

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

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

  • 抽象标准建模
public interface Predicate {
    boolean test(T t);
}
  • 抽象filter方法
public static  List filter(List list,Predicate p){
        List result = new ArrayList<>();
        for(T e:list){
            if(p.test(e)){
                result.add(e);
            }
        }
        return result;
    }
  • 调用方式一致
//筛选红色苹果
        List listRed= filterApples(list,(Apple apple)->"red".equals(apple.getColor()));

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

你可能感兴趣的:(通过行为参数化传递代码)