新的语言特性常常让现存的编程模式或设计黯然失色。比如, Java 5
中引入了for-each
循环,由于它的稳健性和简洁性,已经替代了很多显式使用迭代器的情形。Java 7
中推出的菱形操作符(<>
)让大家在创建实例时无需显式使用泛型,一定程度上推动了Java
程序员们采用类型接口(type interface)进行程序设计。
对设计经验的归纳总结被称为设计模式。设计软件时,可以复用这些方式方法来解决一些常见问题。这看起来像传统建筑工程师的工作方式,对典型的场景(比如悬挂桥、拱桥等)都定义有可重用的解决方案。例如,访问者模式常用于分离程序的算法和它的操作对象。单例模式一般用于限制类的实例化,仅生成一份对象。
Lambda
表达式为程序员的工具箱又新添了一件利器。它们为解决传统设计模式所面对的问题提供了新的解决方案,不但如此,采用这些方案往往更高效、更简单。使用Lambda
表达式后,很多现存的略显臃肿的面向对象设计模式能够用更精简的方式实现了。下面会针对五个设计模式展开讨论,分别是:
策略模式代表了解决一类算法的通用解决方案,可以在运行时选择使用哪种方案。例如使用不同的条件(比如苹果的重量,或者颜色)来筛选库存中的苹果。可以将这一模式应用到更广泛的领域,比如使用不同的标准来验证输入的有效性,使用不同的方式来分析或者格式化输入。
策略模式包含三部分内容:
ConcreteStrategyA
或者ConcreteStrategyB
)。String
的形式表示)的接口入手:public interface ValidationStrategy {
boolean execute(String s);
}
其次,定义了该接口的一个或多个具体实现:
public class IsAllLowerCase implements ValidationStrategy {
public boolean execute(String s) {
return s.matches("[a-z]+");
}
}
public class IsNumeric implements ValidationStrategy {
public boolean execute(String s) {
return s.matches("\\d+");
}
}
之后,就可以在程序中使用这些略有差异的验证策略了:
public class Validator {
private final ValidationStrategy strategy;
public Validator(ValidationStrategy v) {
this.strategy = v;
}
public boolean validate(String s) {
return strategy.execute(s);
}
}
Validator numericValidator = new Validator(new IsNumeric());
boolean b1 = numericValidator.validate("aaaa"); //返回false
Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
boolean b2 = lowerCaseValidator.validate("bbbb"); //返回true
使用Lambda表达式
ValidationStrategy
是一个函数接口(除此之外,它还与Predicate
具有同样的函数描述)。这意味着不需要声明新的类来实现不同的策略,通过直接传递Lambda
表达式就能达到同样的目的,并且还更简洁:
Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
boolean b1 = numericValidator.validate("aaaa"); //直接传递Lambda表达式
Validator lowerCaseValidator = new Validator((String s) -> s.matches("\\d+")); //直接传递Lambda表达式
boolean b2 = lowerCaseValidator.validate("bbbb");
Lambda
表达式避免了采用策略设计模式时僵化的模板代码。如果仔细分析一下个中缘由,会发现,Lambda
表达式实际已经对部分代码(或策略)进行了封装,而这就是创建策略设计模式的初衷。因此,建议对类似的问题,应该尽量使用Lambda
表达式来解决。
如果需要采用某个算法的框架,同时又希望有一定的灵活度,能对它的某些部分进行改进,那么采用模板方法设计模式是比较通用的方案。换句话说,模板方法模式在你“希望使用这个算法,但是需要对其中的某些行进行改进,才能达到希望的效果”时是非常有用的。
从一个例子着手,看看这个模式是如何工作的。假设需要编写一个简单的在线银行应用。通常,用户需要输入一个用户账户,之后应用才能从银行的数据库中得到用户的详细信息,最终完成一些让用户满意的操作。不同分行的在线银行应用让客户满意的方式可能还略有不同,比如给客户的账户发放红利,或者仅仅是少发送一些推广文件。可能通过下面的抽象类方式来实现在线银行应用:
abstract class OnlineBanking {
public void processCustomer(int id){
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy(c);
}
abstract void makeCustomerHappy(Customer c);
}
processCustomer
方法搭建了在线银行算法的框架:获取客户提供的ID
,然后提供服务让用户满意。不同的支行可以通过继承OnlineBanking
类,对该方法提供差异化的实现。
使用Lambda表达式
使用Lambda
表达式同样也可以解决这些问题(创建算法框架,让具体的实现插入某些部分)。你想要插入的不同算法组件可以通过Lambda
表达式或者方法引用的方式实现。
这里向processCustomer
方法引入了第二个参数,它是一个Consumer
类型的参数,与前文定义的makeCustomerHappy
的特征保持一致:
public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy.accept(c);
}
现在,可以很方便地通过传递Lambda
表达式,直接插入不同的行为,不再需要继承OnlineBanking
类了:
new OnlineBankingLambda().processCustomer(1337, (Customer c) -> System.out.println("Hello " + c.getName());