美文网首页
策略模式与行为参数化

策略模式与行为参数化

作者: tingshuo123 | 来源:发表于2018-09-29 23:30 被阅读25次

    考虑这样一个场景,有一堆苹果,我们需要把绿色的苹果给筛选出来。

    public class Apple {
        
        private String color;
        private int weight;
        // getter setter...
    }
    

    很简单的一个需求,我们可以马上写出下面这样这样的代码

    public class FilterApple {
        
        
        // 筛选绿色的苹果
        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;
        }
    }
    

    像上面那样写的话看起来没有问题,但是仔细想想,需求是一直会发生变化如果需要筛选红色的苹果,怎么办?

    如果向选择多种颜色的苹果呢(红色,绿色,浅绿色等)?

    当然新建一个方法,复制上面的方法体后更改if的匹配条件可以解决,这样代码复用率会很低,而且不优雅。一个良好的原则是在编写类似的代码后,就尝试将其抽象化。

    一种做法是给方法添加一个颜色参数(或者颜色的列表),这样就可以灵活的适应变化了.

    改进后的代码:

        // 根据颜色筛选苹果
        public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) {
            
            List<Apple> result = new ArrayList<>();
            
            for (Apple apple : result) {
                if (apple.getColor().equals(color)) {
                    result.add(apple);
                }
            }
            
            return result;
        }
    

    又来了一个新需求,需要把大的苹果挑选出来(大于150克),有了上次的经验,这次我们肯定会想到添加一个 重量参数 来适应变化。

        // 根据重量筛选
        public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
            
            List<Apple> result = new ArrayList<>();
            
            for (Apple apple : result) {
                if (apple.getWeight() > weight) {
                    result.add(apple);
                }
            }
            
            return result;
        }
    

    通过上面代码是可以解决现有的需求,但是我们会发现上面筛选重量跟筛选颜色两个方法相似度很高。
    这显然违反了 DRY(Don't Reperat Yourself)的软件工程原则。
    还有如果你想改变遍历的方式提升性能,那就需要改变所有的实现方法,这样不仅麻烦,也违反了开闭原则

    违反开闭原则问题我们待会再解决,我们先把代码冗余问题先解决了,我们想办法把他给写成一个方法,接收两个筛选条件参数,跟一个标志

    于是有了下面代码:

        public static List<Apple> filterApples(List<Apple> inventory, String color, int weight, 
                boolean flag) {
            
            List<Apple> result = new ArrayList<>();
            // 很笨拙的筛选方式
            for (Apple apple : result) {
                if (flag) 
                    if (apple.getColor().equals(color)) result.add(apple);
                else
                    if (apple.getWeight() > weight) result.add(apple);
            }
            
            return result;
        }
    

    嗯,代码冗余的问题是解决了,但是这样的解决方法再差不过了
    比如 true 和 false 是什么意思?
    如果需求又改变要筛选其他属性怎么办?比如 品种、产地
    或者多种属性组合,比如重的绿色的苹果你又该怎么办?
    像前面的思路写的话的,无疑会有大量的重复filter方法,或者一个非常复杂的方法,参数列表会非常长。

    想起来前几天才学了策略模式,我们可以用策略模式来很好的解决这个问题

    首先我们先定义一个选择标准(策略)的接口

    public interface ApplePredicate {
        /*
         * 你可以通过不同的实现类,封装不同的筛选算法(策略),然后再运行时选择一个筛选算法。
         * 这样有新需求时,你只需要再添加一个筛选规则实现ApplePredicate接口即可,不用改变原来代码
         */
        public boolean filter(Apple apple);
    }
    

    现在我们可以用 ApplePredicate 的不同实现,代表多个不同的选择标准(策略),如下

    筛选绿色苹果:

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

    筛选重(大于150kg)的苹果:

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

    接下来我们只需要让 filterApples方法接受ApplePredicate对象,让ApplePredicate对象对Apple` 做条件测试。

    通过策略模式写的筛选函数:

        public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
            
            List<Apple> result = new ArrayList<>();
            
            for (Apple apple : result) {
                if (p.filter(apple)) {
                    result.add(apple);
                }
            }
            
            return result;
        }
    

    这段代码比之前的代码要好多了,不通的筛选条件我们只需要创建不同的ApplePredicate对象可以了.

    现在我们的代码可以应对任何涉及苹果筛选条件需求的变更了.

    简单来说,行为参数化就是,让方法接受多种行为(策略),并再内部使用,以完成不通过的行为。

    像上面的 filterApples方法的行为取决于通过 ApplePredicate 对象传递的代码,这样我们把 filterApples 的行为给参数化了。

    现在 filterApples 方法的行为取决 ApplePredicate 对象传递的代码。


    现在似乎看起来一切的完美了,想想上面的我们又是创建接口,又是实现接口的,但是我们需要的仅仅是一个方法,由于 filterApples 只接受对象,在JDK 1.8 之前这样做也是没办法的。

    JDK1.8的出现该让我们能写出更加简洁的代码,JDK1.8推出的一个新特性 Lambda 表达式,通过 Lambda 表达式我们可以直接将 apple.getWeight() > 150 这样的表达式传递给 filterApple方法

    用 Lambda 表达式重写 为下面的像样子

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

    这代码比之前的看起来要干净多了。


    其实我们还可以更进一步的抽象,目前,filterApples方法还只适用于 Apple。 我们还可以将 List 类型抽象化,从而让它可以适用于更多的场景,如筛选梨子、橘子、西瓜。

    public interface Predicate<T> {
        
        boolean test(T t);
    }
    
    public class FruitFilter {
        
        public static <T> List<T> filter(List<T> list, Predicate<T> p) {
            
            List<T> result = new ArrayList<>();
            
            for (T e : result) {
                if (p.test(e)) {
                    result.add(e);
                }
            }
            
            return result;
        }
    }
    
    public class PredicateTest {
        
        // 筛选苹果
        @Test
        public void test() {
            
            List<Orange> inventory = new ArrayList<>();
            
            List<Orange> list = FruitFilter.filter(inventory,
                    (Orange orange) -> "red".equals(orange.getColor()));
        }
        
        // 筛选橘子
        @Test
        public void test02() {
            
            List<Apple> inventory = new ArrayList<>();
            
            List<Apple> list = FruitFilter.filter(inventory,
                    (Apple apple) -> "red".equals(apple.getColor()));
        }
    }
    

    相关文章

      网友评论

          本文标题:策略模式与行为参数化

          本文链接:https://www.haomeiwen.com/subject/vncioftx.html