从代码的改进,看参数行为化与Lambda

2020-03-14  

举一个简单的例子,看如何优化代码,从而更灵活地适应不断变化的需求。

这是一个优秀的程序需要不断考虑的事情。

 

这是水果的类代码

package constxiong.interview;

/**
 * 水果
 * @author ConstXiong
 */
class Fruit {
	private String type;
	private String color;
	private double weight;
	
	public Fruit(String type, String color, double weight) {
		this.type = type;
		this.color = color;
		this.weight = weight;
	}
	
	public String getColor() {
		return this.color;
	}
	
	public double getWeight() {
		return this.weight;
	}
	
	public String getType() {
		return this.type;
	}

	@Override
	public String toString() {
		return String.format("{type:%s,color:%s,weight:%s}", type, color, weight);
	}
}

 

 

需求 1、从水果堆中,筛选出所有的苹果

package constxiong.interview;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 测试优化代码
 * @author ConstXiong
 */
public class TestImproveCode {

	public static void main(String[] args) {
		List<Fruit> list = Arrays.asList(new Fruit("苹果", "红色", 1.1),
				new Fruit("苹果", "绿色", 1.7),
				new Fruit("香蕉", "黄色", 0.8),
				new Fruit("橘子", "橙色", 0.7));
		
		System.out.println(filterFruit(list));
	}
	
	/**
	 * 过滤水果
	 * @param fruit
	 * @return
	 */
	static List<Fruit> filterFruit(List<Fruit> fruit) {
		List<Fruit> result = new ArrayList<Fruit>();
		for (Fruit f : fruit) {
			if ("苹果".equals(f.getType())) {
				result.add(f);
			}
		}
		return result;
	}
}


打印结果

[{type:苹果,color:红色,weight:1.1}, {type:苹果,color:绿色,weight:1.7}]

 

 

需求 2、这次从水果堆里,筛选出香蕉

思考:这次你想到,给 filterFruit 方法添加一个水果的种类 type 参数,进行过滤,以适应下次可以满足过滤出其他种类的水果

package constxiong.interview;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 测试优化代码
 * @author ConstXiong
 */
public class TestImproveCode {

	public static void main(String[] args) {
		List<Fruit> list = Arrays.asList(new Fruit("苹果", "红色", 1.1),
				new Fruit("苹果", "绿色", 1.7),
				new Fruit("香蕉", "黄色", 0.8),
				new Fruit("橘子", "橙色", 0.7));
		
		System.out.println(filterFruit(list, "香蕉"));
	}
	
	/**
	 * 过滤水果
	 * @param fruit
	 * @param type
	 * @return
	 */
	static List<Fruit> filterFruit(List<Fruit> fruit, String type) {
		List<Fruit> result = new ArrayList<Fruit>();
		for (Fruit f : fruit) {
			if (type.equals(f.getType())) {
				result.add(f);
			}
		}
		return result;
	}
}

打印结果

[{type:香蕉,color:黄色,weight:0.8}]

 

 

需求 3、过滤重量大于 1 的水果

思考:可能你已想到这个需求变动,早就添加了方法 filterFruit(List<Fruit> fruit, double weight) 

package constxiong.interview;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 测试优化代码
 * @author ConstXiong
 */
public class TestImproveCode {

	public static void main(String[] args) {
		List<Fruit> list = Arrays.asList(new Fruit("苹果", "红色", 1.1),
				new Fruit("苹果", "绿色", 1.7),
				new Fruit("香蕉", "黄色", 0.8),
				new Fruit("橘子", "橙色", 0.7));
		
		System.out.println(filterFruit(list, 1));
	}
	
	/**
	 * 根据类型过滤水果
	 * @param fruit
	 * @param type
	 * @return
	 */
	static List<Fruit> filterFruit(List<Fruit> fruit, String type) {
		List<Fruit> result = new ArrayList<Fruit>();
		for (Fruit f : fruit) {
			if (type.equals(f.getType())) {
				result.add(f);
			}
		}
		return result;
	}
	/**
	 * 根绝重量过滤水果
	 * @param fruit
	 * @param type
	 * @return
	 */
	static List<Fruit> filterFruit(List<Fruit> fruit, double weight) {
		List<Fruit> result = new ArrayList<Fruit>();
		for (Fruit f : fruit) {
			if (f.getWeight() > weight) {
				result.add(f);
			}
		}
		return result;
	}
}

 

但你有没有发现,这个代码中的两个 filterFruit 方法,除了第二个参数和对参数的判断不同,其他地方都是相同,这违反了 Don’t Repeat Yourself 的软件设计原则

于是继续优化代码,把 filterFruit 方法的参数进行合并

package constxiong.interview;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 测试优化代码
 * @author ConstXiong
 */
public class TestImproveCode {

	public static void main(String[] args) {
		List<Fruit> list = Arrays.asList(new Fruit("苹果", "红色", 1.1),
				new Fruit("苹果", "绿色", 1.7),
				new Fruit("香蕉", "黄色", 0.8),
				new Fruit("橘子", "橙色", 0.7));
		
		System.out.println(filterFruit(list, null, 1.0));
	}
	
	/**
	 * 过滤水果
	 * @param fruit
	 * @param type
	 * @return
	 */
	static List<Fruit> filterFruit(List<Fruit> fruit, String type, Double weight) {
		List<Fruit> result = new ArrayList<Fruit>();
		for (Fruit f : fruit) {
			if (type != null && weight != null) {
				if (type.equals(f.getType()) && f.getWeight() > weight) {
					result.add(f);
				}
			} else if (type == null && weight != null) {
				if (f.getWeight() > weight) {
					result.add(f);
				}
			} else if (type != null && weight == null) {
				if (type.equals(f.getType())) {
					result.add(f);
				}
			}
		}
		return result;
	}
}

 

这样的改动,其实很糟糕,代码多了很多 if-else 判断,理解起来更困难了,同时不能更好的满足各种组合的需求。比如再希望通过颜色过滤水果怎么办?

 

这时候就需要引入行为参数化,让方法接受多种行为作为参数,并在内部使用,来完成不同的行为。

新增一个接口 FruitFilter

package constxiong.interview;

/**
 * 水果过滤接口
 * @author ConstXiong
 */
public interface FruitFilter {

	/**
	 * 根据是否满足条件过滤水果
	 * @param fruit
	 * @return
	 */
	boolean filter(Fruit fruit);
	
}

现在就可以新增多个 FruitFilter 实现,满足各种需求的组合

package constxiong.interview;

/**
 * 根据类型过滤水果实现类
 * @author ConstXiong
 */
public class FruitTypeFilter implements FruitFilter{

	public boolean filter(Fruit fruit) {
		return "苹果".equals(fruit.getType());
	}
	
}
package constxiong.interview;

/**
 * 根据重量过滤水果实现类
 * @author ConstXiong
 */
public class FruitWeightFilter implements FruitFilter {

	public boolean filter(Fruit fruit)  {
		return fruit.getWeight() > 1;
	}
	
}

可以通过这样的方式,把一簇算法规则封装为一系列策略,在运行时选择算法。

 

再对 filterFruit 方法进行优化

package constxiong.interview;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 测试优化代码
 * @author ConstXiong
 */
public class TestImproveCode {

	public static void main(String[] args) {
		List<Fruit> list = Arrays.asList(new Fruit("苹果", "红色", 1.1),
				new Fruit("苹果", "绿色", 1.7),
				new Fruit("香蕉", "黄色", 0.8),
				new Fruit("橘子", "橙色", 0.7));
		
		System.out.println(filterFruit(list, new FruitTypeFilter()));
	}
	
	/**
	 * 过滤水果
	 * @param fruit
	 * @param type
	 * @return
	 */
	static List<Fruit> filterFruit(List<Fruit> fruit, FruitFilter filter) {
		List<Fruit> result = new ArrayList<Fruit>();
		for (Fruit f : fruit) {
			if (filter.filter(f)) {
				result.add(f);
			}
		}
		return result;
	}
}

 

也可以不用提前定义 FruitFilter 的实现类,使用匿名类,在匿名类中实现需求逻辑

package constxiong.interview;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 测试优化代码
 * @author ConstXiong
 */
public class TestImproveCode {

	public static void main(String[] args) {
		List<Fruit> list = Arrays.asList(new Fruit("苹果", "红色", 1.1),
				new Fruit("苹果", "绿色", 1.7),
				new Fruit("香蕉", "黄色", 0.8),
				new Fruit("橘子", "橙色", 0.7));
		
		System.out.println(filterFruit(list, new FruitFilter() {
			@Override
			public boolean filter(Fruit fruit) {
				return"苹果".equals(fruit.getType());
			}
		}));
	}
	
	/**
	 * 过滤水果
	 * @param fruit
	 * @param type
	 * @return
	 */
	static List<Fruit> filterFruit(List<Fruit> fruit, FruitFilter filter) {
		List<Fruit> result = new ArrayList<Fruit>();
		for (Fruit f : fruit) {
			if (filter.filter(f)) {
				result.add(f);
			}
		}
		return result;
	}
}

 

感觉优化到这里,应该挺让人满意了,但是匿名类的语法容易让人对变量的使用产生歧义,每次都要 new 出对象。

这次就需要使用 Java 8 中 Lambda 表达式代替匿名类,让代码更简洁,一眼就能看出你想要代码表达的意图。

package constxiong.interview;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 测试优化代码
 * @author ConstXiong
 */
public class TestImproveCode {

	public static void main(String[] args) {
		List<Fruit> list = Arrays.asList(new Fruit("苹果", "红色", 1.1),
				new Fruit("苹果", "绿色", 1.7),
				new Fruit("香蕉", "黄色", 0.8),
				new Fruit("橘子", "橙色", 0.7));
		
		System.out.println(filterFruit(list, (Fruit f) -> "苹果".equals(f.getType())));
	}
	
	/**
	 * 过滤水果
	 * @param fruit
	 * @param type
	 * @return
	 */
	static List<Fruit> filterFruit(List<Fruit> fruit, FruitFilter filter) {
		List<Fruit> result = new ArrayList<Fruit>();
		for (Fruit f : fruit) {
			if (filter.filter(f)) {
				result.add(f);
			}
		}
		return result;
	}
}

 

通过修改

filterFruit(list, (Fruit f) -> "苹果".equals(f.getType()))

中的 Lambda 表达式即可修改过滤水果的逻辑,比如希望通过重量过滤

filterFruit(list, (Fruit f) -> f.getWeight() > 1)

 

代码比之前干净很多,更像是它在陈述问题本身。

到此好像可以结束了,但是我们并不满足于此,继续...

 

把 List 抽象化,修改 FruitFilter 接口和 TestImproveCode 类的 filterFruit 方法

package constxiong.interview;

/**
 * 水果过滤接口
 * @author ConstXiong
 */
public interface FruitFilter<T> {

	/**
	 * 根据是否满足条件过滤水果
	 * @param fruit
	 * @return
	 */
	boolean filter(T t);
	
}
    /**
	 * 过滤水果
	 * @param <T>
	 * @param fruit
	 * @param type
	 * @return
	 */
	static <T> List<T> filterFruit(List<T> list, FruitFilter<T> filter) {
		List<T> result = new ArrayList<T>();
		for (T e : list) {
			if (filter.filter(e)) {
				result.add(e);
			}
		}
		return result;
	}

这样这个方法就适用于所有的类型了。

 

这就是 JDK 1.8 中新增 java.util.function.Predicate 接口的用途之一。

看到这里,你是不是理解到了 JDK 的设计师,为了能让你的代码更加简洁的良苦用心了?

 

ConstXiong 备案号:苏ICP备16009629号-3