举一个简单的例子,看如何优化代码,从而更灵活地适应不断变化的需求。
这是一个优秀的程序需要不断考虑的事情。
这是水果的类代码
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