如何应对不断变化的需求
软件工程中,用户的需求肯定会变,如何应对不断变化的需求,理想状态下,应该把工作量降到最少,此外,类似的新功能实现还应该很简单,而且易于长期维护。
行为参数话就是可以帮助你处理频繁变更需求的一种软件开发模式:拿出一个代码块,准备好不去执行它,这个代码块以后可以被程序的其它部分调用,意味着可以推迟这块代码的执行,例如,可以将代码块作为参数传递给另一个方法,稍后再去执行它。
- 一个例子:农场库存一堆苹果
1.筛选绿苹果,第一个解决方案
public static List<Apple> filterGreenApples(List<Apple> appleList){
List<Apple> result = new ArrayList<Apple>();
for (Apple apple : appleList){
if("green".equals(apple.getColor())){
result.add(apple);
}
}
return result;
}
农民改主意了,还想要筛选红苹果,该怎么做?简单的解决办法就是复制这个方法,把名字改成filterRedApples,更改if匹配条件,然而,农民想要筛选多种颜色:浅绿色,暗红色,黄色等,复制的方法应付不了。
2.把颜色作为参数, 灵活适应变化
public static List<Apple> filterApplesByColor(List<Apple> appleList, String color){
List<Apple> result = new ArrayList<>();
for(Apple apple: appleList){
if(apple.getColor().equals(color)){
result.add(apple);
}
}
return result;
}
太简单了!
农民伯伯又跑来对你说,要是能区分,轻的苹果和重的苹果就太好了。
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight){
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory){
if(apple.getWeight() > weight){
result.add(apple);
}
}
return result;
}
不错的解决方案,但是,复制了大部分的代码来实现遍历库存,对每个苹果应用筛选条件,打破了DRY的软件开发原则,如果有要改变筛选的遍历方式来提升性能呢?那就得修改所有方法的实现.
3.第三次尝试:对能想到的每个属性做筛选
public static List<Apple> filterApple(List<Apple> appleList, String color, int weight, boolean flag){
List<Apple> result = new ArrayList<Apple>();
for(Apple apple: appleList){
/**十分笨拙的选择颜色或重量的方法*/
if((flag && apple.getWeight() > weight) ||
(!flag && apple.getColor().equals(color))){
result.add(apple);
}
}
return result;
}
- 首先flag含义不清晰
- 还是不能很好的应对变化的需求:大小,形状,产地等,又怎么办。
4.行为参数化
- 一种可能的解决方案是选择标准建模:考虑的是根据苹果的某些属性来返回一个boolean值,称只为谓词(即一个返回boolean值的函数)
定义一个接口对选择标准建模:
interface ApplePredicate{
public boolean test(Apple a);
}
现在可以用ApplePredicate的多个实现代表不同的选择标准
//选择重的
static class AppleWeightPredicate implements ApplePredicate{
public boolean test(Apple apple){
return apple.getWeight() > 150;
}
}
//选出绿的
static class AppleColorPredicate implements ApplePredicate{
public boolean test(Apple apple){
return "green".equals(apple.getColor());
}
}
选择苹果的不同策略.png
- 刚刚做的这些和“策略设计模式”相关
让方法接受多种行为(或战略)作为参数,并在内部使用,来完成不同的行为,这就是行为参数化。根据抽象条件筛选
public static List<Apple> filterApple(List<Apple> inventory, ApplePredicate p){
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
if(p.test(apple)){
result.add(apple);
}
}
return result;
}
- 灵活多了,易读易用
filterApples方法的行为取决于通过ApplePredcate对象传递的代码,换句话说,filterApples方法的行为参数化了。
行为参数化的好处在于,看可以把迭代要筛选的逻辑和对集合中每个元素应用的行为区分开来,这样可以重复使用一个方法,给它不同的行为来达到不同的目的。
但是上述行为的传递是通过行为封装在对象中实现的,这样不得不声明好几个实现谓词接口的类,啰嗦且费时。
5.匿名类
List<Apple> redApples2 = filterApple(inventory, new ApplePredicate() {
public boolean test(Apple a){
return a.getColor().equals("red");
}
});
- 笨重,占用代码空间
- 经典的Java谜题
6.使用Lambda表达式
List<Apple> redApples4 = filter(inventory,
(Apple apple) -> "red".equals(apple.getColor()));
行为参数化.png
7.将List类型抽象化
//引入泛型T
public static <T> List<T> filter(List<T> list, Predicate<T> p){
List<T> result = new ArrayList<>();
for(T e : list){
if(p.test(e)){
result.add(e);
}
}
return result;
}
-
现在可以把filter 用在香蕉,桔子,Integer,Sting 列表上了
-
行为参数化例子
//例子 排序
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
//线程执行代码块
Thread t = new Thread(() -> System.out.println("Hello"));
//GUI事件处理
button.setOnAction((ActionEvent event) ->label.setText("Sent!"));
Lambda 表达式
-
lambda
简洁的表示一个行为或传递代码:没有名称,有参数列表,函数主体,返回类型,可能还有个可以抛出的异常列表
Lambda表达式.png
基本语法:
(parameters) ->expression
或
(parameters) -> {statements;} -
哪里及如何使用
1.函数式接口
只定义一个抽象方法的接口,Predicate,Comparator, Runnable等
用函数式接口做什么呢,Lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体实现的实例),用匿名类也可以完成同样的事情,只是略笨拙。
2.函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名我们将这种抽象方法叫作
函数描述符,例如:
Runnable接口可以看作一个什么也不接受什么也不返回(void)的函数的签名,因为它只有一个叫作run的抽象方法,这个方法什么也不接受,什么也不返回(void),() -> void代表了参数列表为空,且返回void的函数 -
付诸实践:环绕执行模式
1.记得行为参数化
2.使用函数式接口传递行为
3.执行一个行为
4.传递Lambda -
使用函数式接口
java.util.function包中引入了几个新的函数式接口
1.Predicate
java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(stringList, nonEmptyStringPredicate);
2.Consumer
java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void),你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口
forEach(Arrays.asList(1,3,4,5), (Integer i) -> System.out.println(i));
3.Function
java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)
List<Integer> l = map(Arrays.asList("lambda", "Eric", "stream"), (String s) -> s.length());
4.原始类型特性化
Java类型要么是引用类型(比如Byte、 Integer、 Object、 List) ,要么是原始类型(比如int、 double、 byte、 char)。但是泛型(比如Consumer<T>中的T)只能绑定到引用类型。
IntPredicate 等等等 避免使用Lambda时自动装箱,拆箱操作
-
函数式接口 类型检查
Lambda的类型是从使用Lambda的上下文推断出来的
1-Lambda-类型检查.png
同一个Lambda可用于多个不同的函数式接口
- 类型推断
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
-
使用局部变量
局部变量必须显式声明为final,或事实上是final。
1.第一,实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此, Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。
2.这一限制不鼓励你使用改变外部变量的典型命令式编程模式,这种模式会阻碍很容易做到的并行处理。 -
方法引用
方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法,方法引用就是让你根据已有的方法实现来创建Lambda表达式
等效方法引用.png
如何构建方法引用
1-Lambda-方法引用.png构造函数引用
不将构造函数实例化却能够引用它,这个功能有一些有趣的应用
static Map<String, Function<Integer, Fruit>> map = new HashMap<>();
static{
map.put("apple", Apple::new);
map.put("orange", Orange::new);
}
public static Fruit getMeFruit(String fruit, Integer weight){
return map.get(fruit).apply(weight);
}
- Lambda复合
Java 8的好几个函数式接口都有为方便而设计的方法。具体而言,许多函数式接口,比如用于传递Lambda表达式的Comparator、Function和Predicate都提供了允许你进行复合的方法,这意味着你可以把多个简单的Lambda复合成复杂的表达式。比如,可以让两个谓词之间做一个or操作,组合成一个更大的谓词。而且,你还可以让一个函数的结果成为另一个函数的输入。
1.比较器复合
Comparator<Apple> c = comparing(Apple::getWeight);
/**逆序*/
inventory.sort(comparing(Apple::getWeight).reversed());
/**比较器链*/
inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getColor));
2.谓词复合
Predicate<Apple> redApple = a -> "red".equals(a.getColor());
Predicate<Apple> notRedApple = redApple.negate();
Predicate<Apple> heavyApple = a -> a.getWeight() > 100;
Predicate<Apple> redAndHeavyApple = redApple.and(heavyApple);
3.函数复合
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
//g(f(x))
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1);
//f(g(x))
Function<Integer, Integer> h1 = f.compose(g);
int result2 = h1.apply(1);
- 数学中的类似思想-积分
网友评论