得益于函数接口,我们可以改造设计模式(不限于此):
- 策略模式
- 模板模式
- 观察者模式
- 责任链模式
- 工厂模式
策略模式
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
例:
public interface Strategy { public int doOperation(int num1, int num2); } public class OperationAdd implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 + num2; } } public class OperationSubstract implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 - num2; } } public class Context { private Strategy strategy; public Context(Strategy strategy){ this.strategy = strategy; } public int executeStrategy(int num1, int num2){ return strategy.doOperation(num1, num2); } } public class StrategyPatternDemo { public static void main(String[] args) { Context context = new Context(new OperationAdd()); System.out.println("10 + 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationSubstract()); System.out.println("10 - 5 = " + context.executeStrategy(10, 5)); } }
在“了不起”系列的第一课就学过谓词Predicate就可以用Lambda形式来简写,因为Predicate就是方法接口。
其实不仅仅是Predicate这种返回Boolean的方法可以使用Lambda,所有的参数和返回形式都可以使用Lambda。完整的例子:
public class StrategyMain { public static void main(String[] args) { // 老派策略1,参数为String,返回Boolean Validator v1 = new Validator(new IsNumeric()); System.out.println(v1.validate("aaaa")); Validator v2 = new Validator(new IsAllLowerCase()); System.out.println(v2.validate("bbbb")); // 老派策略2,参数为两个int,返回int Context context = new Context(new OperationAdd()); System.out.println("10 + 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationSubstract()); System.out.println("10 - 5 = " + context.executeStrategy(10, 5)); // lambda改造的形式1,参数为String,返回Boolean Validator v3 = new Validator((String s) -> s.matches("\d+")); System.out.println(v3.validate("aaaa")); Validator v4 = new Validator((String s) -> s.matches("[a-z]+")); System.out.println(v4.validate("bbbb")); // lambda改造的形式2,参数为两个int,返回int Context operationContext1 = new Context((int a, int b) -> a + b); System.out.println(operationContext1.executeStrategy(10, 5)); Context operationContext2 = new Context((int a, int b) -> a - b); System.out.println(operationContext2.executeStrategy(10, 5)); } interface ValidationStrategy { public boolean execute(String s); } static private class IsAllLowerCase implements ValidationStrategy { public boolean execute(String s) { return s.matches("[a-z]+"); } } static private class IsNumeric implements ValidationStrategy { public boolean execute(String s) { return s.matches("\d+"); } } static private class Validator { private final ValidationStrategy strategy; public Validator(ValidationStrategy v) { this.strategy = v; } public boolean validate(String s) { return strategy.execute(s); } } interface Strategy { public int doOperation(int num1, int num2); } static private class OperationAdd implements Strategy { public int doOperation(int num1, int num2) { return num1 + num2; } } static private class OperationSubstract implements Strategy { public int doOperation(int num1, int num2) { return num1 - num2; } } static private class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public int executeStrategy(int num1, int num2) { return strategy.doOperation(num1, num2); } } }
结果:
false true 10 + 5 = 15 10 - 5 = 5 false true 15 5
模板模式
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
模板模式的关键在于子类灵活处理业务代码,比如StrartPlay等方法,还比如一个银行,需要获取客户的资料,根据情况,然后灵活的使用客户满意的操作,每个分行的操作都不同,如果是传统方式,就是每个分行建立模板子类。用Lambda就省去了一堆的分行子类。
import java.util.function.Consumer; public class OnlineBankingLambda { public static void main(String[] args) { new OnlineBankingLambda().processCustomer(1337, (Customer c) -> System.out.println("Hello!")); } public void processCustomer(int id, Consumer<Customer> makeCustomerHappy){ Customer c = Database.getCustomerWithId(id); makeCustomerHappy.accept(c); } // dummy Customer class static private class Customer {} // dummy Database class static private class Database{ static Customer getCustomerWithId(int id){ return new Customer();} } }
注意,(Customer c) -> System.out.println("Hello!") 就是Consumer<Customer>的一个实例,如果看过“了不起”系列的第一课,我们知道Consumer也是方法接口,他和谓词的区别是谓词返回Boolean,消费者无返回值:void accept(T t);
如此一来,System.out.println("Hello!")就可以使用了(没有返回),不仅仅是Consumer,任何方法接口都可以用在Lambda,所以用一行代码就省了一个子类。
观察者模式
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
观察者模式相当常用,比如发布-订阅就是观察者模式。
需要观察的资源类,最重要的2个方法是,注册观察者和提醒观察者。
interface Observer{ void inform(String tweet); } interface Subject{ void registerObserver(Observer o); void notifyObservers(String tweet); } static private class NYTimes implements Observer{ @Override public void inform(String tweet) { if(tweet != null && tweet.contains("money")){ System.out.println("Breaking news in NY!" + tweet); } } } static private class Guardian implements Observer{ @Override public void inform(String tweet) { if(tweet != null && tweet.contains("queen")){ System.out.println("Yet another news in London... " + tweet); } } } static private class LeMonde implements Observer{ @Override public void inform(String tweet) { if(tweet != null && tweet.contains("wine")){ System.out.println("Today cheese, wine and news! " + tweet); } } } static private class Feed implements Subject{ private final List<Observer> observers = new ArrayList<>(); public void registerObserver(Observer o) { this.observers.add(o); } public void notifyObservers(String tweet) { observers.forEach(o -> o.inform(tweet)); } }
使用
Feed f = new Feed(); f.registerObserver(new NYTimes()); f.registerObserver(new Guardian()); f.registerObserver(new LeMonde()); f.notifyObservers("The queen said her favourite book is Java 8 in Action!");
用Lambda替换
Feed feedLambda = new Feed(); feedLambda.registerObserver((String tweet) -> { if(tweet != null && tweet.contains("money")){ System.out.println("Breaking news in NY! " + tweet); } }); feedLambda.registerObserver((String tweet) -> { if(tweet != null && tweet.contains("queen")){ System.out.println("Yet another news in London... " + tweet); } }); feedLambda.notifyObservers("Money money money, give me money!");
需要灵活对待Lambda,并不是什么情况都要用其替代,因为如果观察者的逻辑比较复杂,用{}括起来比较大,或者还依赖状态,或者定义了很多依赖方法,这种情况下,还是传统的观察者模式更加清晰易维护。
责任链模式
优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。
使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。
责任链的核心是一个对象处理完工作后,还要传递给下一个(Next)对象接着处理,并且依次类推,传递下去。
责任链也是需要定义子类,而Lambda使用Function + andThen 把顺序串联起来,完成同样的功能,例子:
import java.util.function.Function; import java.util.function.UnaryOperator; public class ChainOfResponsibilityMain { public static void main(String[] args) { ProcessingObject<String> p1 = new HeaderTextProcessing(); ProcessingObject<String> p2 = new SpellCheckerProcessing(); p1.setNextProcessor(p2); String result1 = p1.handle("Aren't labdas really sexy?!!"); System.out.println(result1); UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text; UnaryOperator<String> spellCheckerProcessing = (String text) -> text.replaceAll("labda", "lambda"); Function<String, String> pipeline = headerProcessing.andThen(spellCheckerProcessing); String result2 = pipeline.apply("Aren't labdas really sexy?!!"); System.out.println(result2); } static private abstract class ProcessingObject<T> { protected ProcessingObject<T> nextProcessor; public void setNextProcessor(ProcessingObject<T> nextProcessor) { this.nextProcessor = nextProcessor; } public T handle(T input) { T r = handleWork(input); if (nextProcessor != null) { return nextProcessor.handle(r); } return r; } abstract protected T handleWork(T input); } static private class HeaderTextProcessing extends ProcessingObject<String> { public String handleWork(String text) { return "From Raoul, Mario and Alan: " + text; } } static private class SpellCheckerProcessing extends ProcessingObject<String> { public String handleWork(String text) { return text.replaceAll("labda", "lambda"); } } }
工厂模式
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
使用场景: 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
工厂模式也是非常常用的模式,比如我们在银行工作,我们需要使用一个接口创建不同的金融产品:贷款、期权、基金、股票等。
static private class ProductFactory { public static Product createProduct(String name){ switch(name){ case "loan": return new Loan(); case "stock": return new Stock(); case "bond": return new Bond(); default: throw new RuntimeException("No such product " + name); } }
创建方法:
Product p1 = ProductFactory.createProduct("loan");
还是使用方法接口Supplier,来实现Lambda的方式:
@FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; public class FactoryMain { public static void main(String[] args) { Product p1 = ProductFactory.createProduct("loan"); Supplier<Product> loanSupplier = Loan::new; Product p2 = loanSupplier.get(); Product p3 = ProductFactory.createProductLambda("loan"); } static private class ProductFactory { public static Product createProduct(String name){ switch(name){ case "loan": return new Loan(); case "stock": return new Stock(); case "bond": return new Bond(); default: throw new RuntimeException("No such product " + name); } } public static Product createProductLambda(String name){ Supplier<Product> p = map.get(name); if(p != null) return p.get(); throw new RuntimeException("No such product " + name); } } static private interface Product {} static private class Loan implements Product { public Loan() { super(); System.out.println("Loan...Created " ); }} static private class Stock implements Product { public Stock() { super(); System.out.println("Stock...Created " ); }} static private class Bond implements Product { public Bond() { super(); System.out.println("Bond...Created " ); }} final static private Map<String, Supplier<Product>> map = new HashMap<>(); static { map.put("loan", Loan::new); map.put("stock", Stock::new); map.put("bond", Bond::new); } }
这种工厂方法有点局限性,如果有多个参数时,就不好用了,这时不能使用Supplier,需要使用一个特殊的函数接口TriFunction,来进行柯里化:
@PublicEvolving @FunctionalInterface public interface TriFunction<S, T, U, R> { /** * Applies this function to the given arguments. * * @param s the first function argument * @param t the second function argument * @param u the third function argument * @return the function result */ R apply(S s, T t, U u); }
比如:
TriFunction<Integer,Integer,Integer, Integer> triFunction = (x,y,z) -> (x+y)*z; System.out.println(triFunction.apply(4,5,6)); //54
随着函数在Java中变成一等公民,自然而然会产生柯里化。柯里化的链式调用的确用起来很爽。柯里化也可以延迟加载一个函数。
除此以外,柯里化在很多时候简化了函数式编程的复杂性,使编程更加优雅。当然,在团队中使用的话,也需要充分考虑到团队中其他成员是否接受。
另外,还可以用BiFunction,好处是可以使用andThen,进行串联操作,这就是有些责任链+工厂的感觉了。
@FunctionalInterface public interface BiFunction<T, U, R> { R apply(T t, U u); default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t, U u) -> after.apply(apply(t, u)); } }
例如:
public static int compute4(int a, int b, BiFunction<Integer, Integer, Integer> biFunction,Function<Integer, Integer> function) { return biFunction.andThen(function).apply(a, b); } //25 System.out.println(FactoryMain.compute4(2, 3, (v1, v2) -> v1 + v2, v1 -> v1 * v1));