主要记录行为参数化知识,行为是指Java类中的方法,将行为参数化是指我们将具体的对数据操作的业务逻辑进行抽取将其抽象为参数,然后通过形参的方式传递给方法,让方法具有简洁性以及高可读性,高扩展性
正文内容:
一、行为参数化
- 在之前的认知概念里面,笔者对于参数的理解就是:在Java方法将要运行时,方法需要外部提供数据,然后对数据进行处理,此时的这个需求数据就是参数。这种数据类型的参数,可以是具体的类,也可以是具体的类属性。从Java类角度分析,Java类包括成员变量,成员方法以及其他内容,此时的参数就是Java类中的成员变量;但是随着业务逻辑的复杂化,需要我们处理的数据可能也是需要提前处理好才可以,简单地说就是为了让代码更加贴合实际生活,更加简洁,让代码具有更高的扩展性与可读性,我们需要对代码进行优化处理
- 现在引入行为参数化概念,行为是指Java类中的方法,将行为参数化是指我们将具体的对数据操作的业务逻辑进行抽取将其抽象为参数,然后通过形参的方式传递给方法,让方法具有简洁性以及高可读性,高扩展性
- 行为参数化与策略模式类似,都是可以帮助开发者处理频繁需求变更的软件开发模式
二、具体的业务场景
- 有个应用程序是帮助农民了解自己的库存。这位农民可能想要一个查找库存中所有绿色苹果的功能。但到了第二天,他可能会告诉你:其实我还需要找出重量超过150克的苹果。过了两天,农民又跑过来告诉你:要是可以找到既是绿色,重量又超过150克的苹果那就太棒了。你要如何应对这样的不断变化的需求?理想状态下应该将自己的工作量降到最小。此时,类似的新功能实现起来还应该很简单,而且易于长期维护
1.初试牛刀:筛选绿苹果
具体的代码如下所示:
public class Apple {
private Integer weight;
private Color color;
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
enum Color {
RED, GREEN
}
public class Filter {
public static List<Apple> filterGreenApple(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (GREEN.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
}
我们现在使用枚举类把苹果的颜色进行描述,然后将苹果抽象化为Apple类,包含颜色与重量的属性,最后构造过滤类,在过滤类里面给出具体的过滤绿色苹果的方法filterGreenApple;如果现在需求变了,农民需要过滤红色苹果,那么我们就需要将filterGreenApple方法复制一份,将方法名改为filterRedApple并且将里面的GREEN颜色改为RED;这个样子确实解决了问题,但是很明显,重复代码很多,并不是一个好的解决方法;一个好的解决方法是我们编写类似代码后,尽量对其进行抽象化
2.再展身手:把颜色作为参数
在上一版代码中,我们复制filterGreenApple方法完成对红色苹果的过滤,重复了filterGreenApple中大量代码,怎样避免这种问题的发生呢?好的解决方式是将颜色进行参数化,把颜色作为参数传递给过滤的方法,这样子就灵活适应变化了
- 在Filter类里面添加方法filterApple
具体代码如下:
public static List<Apple> filterApple(List<Apple> inventory,Color color) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (color.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
现在解决了颜色筛选的问题,但是农民又跑过来告诉你:要是可以区分苹果的重量就太好了,重的苹果一般是重量大于150克
对于我们来说,这不是问题,我们只需要重新写一个过滤苹果的方法就可以了,我们现在复制filterApple方法,然后把形参color改为weight,然后把筛选条件改为weight大于150就完成了
具体代码如下所示:
public static List<Apple> filterApple(List<Apple> inventory, Integer weight) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
- 现在虽然解决了问题,但是代码的重复度太高了,这违反了DRY(Don’t Repeat Yourself不要重复自己)软件工程原则
-我们可以将重量和颜色都作为参数传递给方法,然后给一个标记,根据标记去判断是通过颜色还是重量过滤苹果
3.第三次尝试:对你能想到的每个属性做筛选
我们将所有的苹果属性结合起来,如下所示:
public static List<Apple> filterApple(List<Apple> inventory,
Color color, Integer weight, boolean flag) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if ((flag && color.equals(apple.getColor())) ||
(!flag && apple.getWeight() > weight)) {
result.add(apple);
}
}
return result;
}
现在的问题确实已经解决了,但是这个解决方案再差不过。首先客户端看上去糟糕透了,true和false是什么意思?此外,这个解决方案无法应对变化的需求,如果这位农民要求你对苹果的不同属性进行筛选,比如,大小,产地,形状等,该怎样处理?或者农民要求组合属性,做更复杂的查询,比如绿色的重苹果,又该怎么办?如今的情况,我们需要一种更好的解决方式来告诉过滤方法
- 现在就需要使用行为参数化的方式实现这种灵活性
4.行为参数化
一种可能的解决方式是对我们的选择标准进行建模:考虑的是苹果,需要根据Apple的某些属性(比如它是绿色的吗?重量超过150克吗?)来返回一个boolean值。我们把它称为谓词(即一个返回boolean值的函数)。让我们定义一个接口来对选择标准建模:
public interface ApplePredicate {
boolean test(Apple apple);
}
选择苹果的不同策略,可以将这些标准看作filter方法的不同行为。刚才做的这些和策略设计模式相关,它让我们定义一族算法,把它们封装起来(称为策略),然后再运行的时候选择一个算法。在则这里,算法族就是ApplePredicate,不同的策略就是AppleGreenColorPredicate和AppleHeavyWeightPredicate
public class AppleGreenColorPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return GREEN.equals(apple.getColor());
}
}
public class AppleHeavyWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
但是,该怎么利用ApplePredicate的不同实现呢?我们需要filterApple方法接受ApplePredicate对象,对Apple做条件测试。这就是行为参数化:让方法接受多种行为(策略)作为参数,并在内部使用,来完成不同行为
- 这在软件工程中有很大好处:现在我们把filterApple方法迭代集合的逻辑与你要应用到集合中每个元素的行为区分开了
具体代码如下:
public class Filter {
public static List<Apple> filterApple(List<Apple> inventory,ApplePredicate predicate){
ArrayList<Apple> result = new ArrayList<>();
for (Apple apple:inventory){
if(predicate.test(apple)){
result.add(apple);
}
}
return result;
}
}
在这个例子中,唯一重要的代码是test方法的实现,正是它定义了filterApple方法的新行为。但是现在有一个遗憾,由于filterApple方法只接受对象,所以我们必须把代码包裹在ApplePredicate对象里。这种做法类似于内联“传递代码”,因为我们通过实现一个test方法来传递布尔表达式值。再JDK8之后我们可以使用Lambda进一步简化代码,这样子我们就无须定义多个ApplePredicate实现类了,从而去掉不必要的代码
具体代码如下:
public class Main {
public static void main(String[] args) {
List<Apple> result1 = Filter.filterApple(new ArrayList<Apple>(), (apple) -> GREEN.equals(apple.getColor()));
List<Apple> result2 = Filter.filterApple(new ArrayList<Apple>(), (apple) -> apple.getWeight() > 150);
}
}