java8行为参数化-逐步尝试实现代码传递

行为参数化是指一个方法的功能,部分或全部由传递给这个方法的某个或多个参数决定,但这些参数不是一般意义上的值(一个字符串或数字),它代表了一个具体的行为,其本质是代码传递,表现可能有多种:对象、匿名类、java8里的Lambda表达式(或方法引用)等。本篇文章将以一个例子,为了满足不断复杂化的需求,层层递进,逐步演示从值传递到java8行为参数化这个简单到高级的过程,以展示行为参数化的必要与优势。

为什么需要行为参数化?

以前做外包项目的时候,似乎永远不知道客户会在什么时候提出新需求或需求变更,如果没有行为参数化或类似行为参数化的东西,客户的需求小有改动可能会带来代码上较大的变动,或为了一个小的新需求复制粘贴好几个类或方法,但行为参数化对于某些需求的变化或新增可以做到以不变应万变。

第一次尝试:颜色过滤,如此简单

有个农民客户要求筛选出绿色的苹果:

  1. public static List<Apple> filterGreenApples(List<Apple> inventory) {
  2. List<Apple> result = new ArrayList<Apple>();
  3. for(Apple apple: inventory){
  4. if( "green".equals(apple.getColor() ) {
  5. result.add(apple);
  6. }
  7. }
  8. return result;
  9. }

这样的代码没有任何问题,“很好地”实现了农民的需求,但是突然农民说我需要筛选出红色的苹果,怎么办?复制粘贴虽然简单,但农民可能还会提出要筛选深红色的苹果。于是为了防止农民再提出筛选其它颜色的苹果,有了下面的代码:

  1. public static List<Apple> filterApplesByColor(List<Apple> inventory,String color) {
  2. List<Apple> result = new ArrayList<Apple>();
  3. for (Apple apple: inventory){
  4. if ( apple.getColor().equals(color) ) {
  5. result.add(apple);
  6. }
  7. }
  8. return result;
  9. }

农民很高兴:“无论我想要什么颜色的苹果,都能给我选出来了”。

第二次尝试:重量过滤,代码重复

但没过几天,农民又发愁了,没有选出轻或重的苹果这个功能,于是有了下面的过滤方法:

  1. public static List<Apple> filterApplesByWeight(List<Apple> inventory,int weight) {
  2. List<Apple> result = new ArrayList<Apple>();
  3. for (Apple apple: inventory){
  4. if ( apple.getWeight() > weight ){
  5. result.add(apple);
  6. }
  7. }
  8. return result;
  9. }

这里农民是高兴了,因为它的需求实现了,但对于我们程序员来说有了太多的重复代码,违反了DRY软件工程原则。

第三次尝试:颜色与重量合并过滤,去除重复代码,如此糟糕

于是,想办法去除重复的代码,将颜色与重量过滤集中在一个方法中:

  1. public static List<Apple> filterApples(List<Apple> inventory, String color,int weight, boolean flag) {
  2. List<Apple> result = new ArrayList<Apple>();
  3. for (Apple apple: inventory){
  4. //flag标志用于区分是颜色筛选还是重量筛选
  5. if ( (flag && apple.getColor().equals(color)) ||(!flag && apple.getWeight() > weight) ){
  6. result.add(apple);
  7. }
  8. }
  9. return result;
  10. }

这次尝试虽然没有重复的代码了,但方法看起来很糟糕,不易理解,而且无法满足农民可能提出的更多过滤需要,比如大小、形状、产地,更不用说可能出现的组合筛选需求了。因此需要作出改变,既不想每一个过滤需求都写一个对应的过滤方法,又不想写一个巨大而糟糕的方法来实现多个筛选需求,怎么办?

第四次尝试:传递行为,声明过多,有点啰嗦

前面几次尝试,在过滤时传递的是具体的值(值传递),如string类型的颜色、int类型的重量、boolean,代表的只是苹果的一个属性或状态,更糟糕的它们可能会有无数个组合,每个组合都对应了一个新需求,我们是无法单纯地靠值传递来设计出优雅的过滤方法的!

由于每一次筛选都是一个具体的行为,行为决定了过滤的结果,那直接将行为传递至过滤方法呢?

以下代码是筛选行为的封装:

  1. /**
  2. *对筛选标准建立模型
  3. */
  4. public interface ApplePredicate{
  5. //test方法决定了apple是否满足我们的筛选条件
  6. boolean test (Apple apple);
  7. }
  8.  
  9. /**
  10. *代表了选出较重苹果的这一行为
  11. */
  12. public class AppleHeavyWeightPredicate implements ApplePredicate{
  13. public boolean test(Apple apple){
  14. return apple.getWeight() > 150;
  15. }
  16. }
  17.  
  18. /**
  19. *代表了选出绿色苹果这一行为
  20. */
  21. public class AppleGreenColorPredicate implements ApplePredicate{
  22. public boolean test(Apple apple){
  23. return "green".equals(apple.getColor());
  24. }
  25. }

于是筛选苹果的方法变成了以下这样,它需要接受一个代表了筛选行为的对象参数ApplePredicate:

  1. public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){
  2. List<Apple> result = new ArrayList<>();
  3. for(Apple apple: inventory){
  4. //ApplePredicate对象封装了测试苹果的条件
  5. //满足条件即是我们需要选出的结果
  6. if(p.test(apple)){
  7. result.add(apple);
  8. }
  9. }
  10. return result;
  11. }

这样针对不同的过滤需求(行为),我们只需要定义不同的ApplePredicate的实现类,即可使用同一个过滤方法筛选出我们想要的苹果,比如筛选出红色且较重的苹果:

  1. public class AppleRedAndHeavyPredicate implements ApplePredicate{
  2. public boolean test(Apple apple){
  3. return "red".equals(apple.getColor())&& apple.getWeight() > 150;
  4. }
  5. }
  6. List<Apple> redAndHeavyApples =filterApples(inventory, new AppleRedAndHeavyPredicate());

无论筛选需求的组合多么复杂,都只有一个行为参数,至此,filterApples方法已经能够应对不断变化的筛选需求了。但每一个筛选行为都需要定义一个类,是不是太啰嗦了?

第五次尝试:通过匿名类传递行为,依然笨重

使用匿名类省去这些类的声明,会不会简单一点:

  1. List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
  2. public boolean test(Apple apple){
  3. return "red".equals(apple.getColor());
  4. }
  5. });

匿名类虽然省去了大量行为类的声明,但是依然笨重(模板化的代码占了4行,实际的筛选代码却只有一行)且匿名类的使用可能会让人费解。

下面的代码执行时会有什么样的输出呢, 4、 5、 6还是42?

  1. public class MeaningOfThis
  2. {
  3. public final int value = 4;
  4. public void doIt(){
  5. int value = 6;
  6. Runnable r = new Runnable(){
  7. public final int value = 5;
  8. public void run(){
  9. int value = 10;
  10. System.out.println(this.value);
  11. }
  12. };
  13. r.run();
  14. }
  15. public static void main(String...args)
  16. {
  17. MeaningOfThis m = new MeaningOfThis();
  18. m.doIt();
  19. }
  20. }

第六次尝试:使用Lambda表达式传递代码,清晰明了

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

是不是干净了很多?是不是一眼就能看出想要筛选的是什么?

注:Lanbda表达式会在下篇中详细介绍,这篇的主题是行为参数化

以上各种方式实现筛选需求的优与劣总结如下:

第七次尝试:将 List 类型抽象化,行为参数化趋于完美

前方的过滤方法只能过滤Apple,我们可以使用泛型进一步抽象化,使其可以过滤Orange、Banana等任何实体:

  1. public interface Predicate<T>{
  2. boolean test(T t);
  3. }
  4. public static <T> List<T> filter(List<T> list, Predicate<T> p){
  5. List<T> result = new ArrayList<>();
  6. for(T e: list){
  7. if(p.test(e)){
  8. result.add(e);
  9. }
  10. }
  11. return result;
  12. }

使用:

  1. //筛选出红苹果
  2. List<Apple> redApples = filter(inventory, (Apple apple) -> "red".equals(apple.getColor()));
  3. //筛选出偶数
  4. List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);

这样即灵活又简洁的代码,在java8之前想都没想过!由此可见,java8魅力无限,每个java程序员都应该学会使用它。

一言以蔽之,行为参数化使我们的代码能够更好地适应不断变化的要求,很大程度上减轻了我们程序员未来的工作量

猜你喜欢

转载自blog.csdn.net/sdmxdzb/article/details/82740868
今日推荐