挑苹果中的行为参数化思想

2020-10-27 00:16发布

草捏对Lambda表达式的了解停留在用IDEA自动替换匿名类的程度,以为Lambda表达式只是把代码缩短了而已,不过就是一个语法糖。所以一直不屑一顾,没系统学习。“不过就是代码短一点嘛,没啥大不了”。但通过学习才知道Lambda表达式不仅仅是把代码换了种表达方式,或许更重要的是背后的思想——行为参数化。

所谓的行为参数化,指的是我们可以通过参数传递的形式去指定代码的行为。是不是很眼熟,学过设计模式的童鞋,基本都是从策略模式开始学起的。策略模式是指面向接口编程,通过使用不同的实现类,改变具体的行为。行为参数化和策略模式的效果类似,只是多了个参数化,通过传递参数来指定行为。

下面草捏给大家讲个关于挑苹果的小故事。

梅梅开始计划每天吃一个苹果,于是吩咐草捏去超市采购。草捏(一个没有生活经验的男人)买苹果的原则:不是烂的就行。

// 不是烂的就行
// isRotten = false
public static List<Apple> filterApple(List<Apple> apples, boolean isRotten) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (apple.getRotten().equals(isRotten)) {
            result.add(apple);
        }
    }
    return result;
}

兴致勃勃的买完回到家。
:“草捏,这苹果里咋还有个绿的,我喜欢吃红苹果,要买红的!!!”
:“啊,知道了,知道了, 买红的,下次买红的。”

// 这次知道了不仅不是烂的,还要买红的
// isRotten = false, color = "red"
public static List<Apple> filterApple(List<Apple> apples, boolean isRotten, boolean color) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (apple.getRotten().equals(isRotten) &&
            apple.getColor().equals(color)) {
            result.add(apple);
        }
    }
    return result;
}

第二次买苹果归来,等待检验。
:“嗯,这次是清一色的红苹果了,可是我喜欢吃小点的苹果,更可爱些,直径应该小于5厘米。”
:“直径...小于...5厘米...”
:“有问题吗?”
:“好的,好的,下次买小于5厘米的。”

// 又要加一个参数,直径小于5厘米
// isRotten = false, color = "red", diameter = 5
public static List<Apple> filterApple(List<Apple> apples, boolean isRotten, boolean color, double diameter) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (apple.getRotten().equals(isRotten) &&
                apple.getColor().equals(color) &&
                (apple.getDiameter() < diameter)) {
            result.add(apple);
        }
    }
    return result;
}

草捏发现,函数的参数已经有4个了,已经很多了,是不是可以考虑改写一下,传递的参数都是挑选苹果的相关标准,然后在函数中根据这些参数来筛选,是不是可以把这些参数抽象成一个结构体,这里抽象成一个Apple类型的变量。

public static List<Apple> filterApple(List<Apple> apples, Apple standard) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (apple.getRotten().equals(standard.isRotten) &&
                apple.getColor().equals(standard.color) &&
                (apple.getDiameter() < standard.diameter)) {
            result.add(apple);
        }
    }
    return result;
}

草捏想这下应该完美了吧,直到第三次回来。
:“草捏,我又想吃大苹果了,下次你买大苹果回来吧。直径大于5厘米的那种。”
这需求变的可真快啊。要大苹果,那就是修改<为>,简单!

public static List<Apple> filterApple(List<Apple> apples, Apple standard) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (apple.getRotten().equals(standard.isRotten) &&
                apple.getColor().equals(standard.color) &&
                // 筛选大苹果
                (apple.getDiameter() > standard.diameter)) {
            result.add(apple);
        }
    }
    return result;
}

但是看着这两个版本的代码,草捏察觉filterAppple中每次变更的是判断苹果是否符合标准的代码,至于遍历apples和根据判断结果加入到result中这部分是不变化的。所以如果能把判断标准的代码抽象出来,那每次修改的影响就会更小(不用改动filterApple方法)。
定义一个判断苹果标准的接口:

// 苹果标准判断
public interface AppleStandardPredicate {
    // 是否符合标准
    boolean isMeetStandard(Apple apple);
}

再来个具体实现类,大苹果标准判断:

public class BigAppleStandardPredicate implements AppleStandardPredicate {
    @Override
    public boolean isMeetStandard(Apple apple) {
        if (apple.getRotten().equals(false) &&
                apple.getColor().equals("red") &&
                (apple.getDiameter() > 5)) {
            return true;
        }
        return false;
    }
}

把filterApple 的参数改为AppleStandardPredicate,这样filterApple将不会因苹果判断逻辑的变化而变化了。

public static List<Apple> filterApple(List<Apple> apples, AppleStandardPredicate predicate) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (predicate.isMeetStandard(apple)) {
            result.add(apple);
        }
    }
    return result;
}

让我们为filterApple传入BigAppleStandardPredicate:

List<Apple> goodApples = filterApple(apples, new BigAppleStandardPredicate());

但这种写法比较繁琐的地方在于需要创建一个实现类,如果用匿名内部类可能会更简洁些。

List<Apple> goodApples = filterApple(apples, new AppleStandardPredicate() {
    @Override
    public boolean isMeetStandard(Apple apple) {
        if (apple.getRotten().equals(false) &&
                apple.getColor().equals("red") &&
                (apple.getDiameter() > 5)) {
            return true;
        }
        return false;
    }
});

嗯,类是少创建了一个,但好像也不是那么简洁,试着用Lambda再稍微简化下。

List<Apple> goodApples = filterApple(apples, apple -> {
    if (apple.getRotten().equals(false) &&
            apple.getColor().equals("red") &&
            (apple.getDiameter() > 5)) {
        return true;
    }
    return false;
});

:“草捏,我不想吃苹果了,我想吃蛇果!标准和之前苹果的一样。”
:“好的。”
这下该怎么改呢?行为还是原来的行为,但是类型换了。那就用泛型吧。
把AppleStandardPredicate改为带泛型的StandardPredicate:

public interface StandardPredicate<T> {
    boolean isMeetStandard(T object);
}

把filterApple改为带泛型的filter:

public static <T> List<T> filter(List<T> objects, StandardPredicate<T> predicate) {
    List<T> result = new ArrayList<>();
    for (T object : objects) {
        if (predicate.isMeetStandard(object)) {
            result.add(object);
        }
    }
    return result;
}

最后在调用filter时,只是修改了下变量名,其他都没改,仍然是原来的逻辑。

List<SnakeApple> goodSnakeApples = filter(snakeApples, snakeApple -> {
    if (snakeApple.getRotten().equals(false) &&
            snakeApple.getColor().equals("red") &&
            (snakeApple.getDiameter() > 5)) {
        return true;
    }
    return false;
});

通过类型抽象化,让StandardPredicate和Filter的适用范围扩大化了,不仅可以用这段代码挑苹果和蛇果,你还能拿着这段代码去买菜!
:”草捏,去买点卷心菜回来~“
:“好的~”

List<Cabbage> goodCabbages = filter(cabbages, cabbage -> cabbage.getColor().equals("green"));

甚至还能帮老婆挑化妆品!
:”草捏,520快到了,口红用完了~“
:“好的~”

List<Lipstick> goodLipsticks = filter(lipsticks, lipstick -> lipstick.getPrice() > 500);

真是妙啊~ 实乃居家生活之必备~

标签: