1、策略模式
策略模式代表了解决一类算法的通用解决方案,可以在运行时选择使用哪种方案。可以将这一模式应用到更广泛的领域,比如使用不同的标准来验证输入的有效性,使用不同的方式来分析或者格式化输入。
策略模式包含三部分内容:
- 一个代表某个算法的接口(它是策略模式的接口)。
- 一个或多个该接口的具体实现,它们代表了算法的多种实现。
- 一个或多个使用策略对象的客户。
假设希望验证输入的内容是否根据标准进行了恰当的格式化(比如只包含小写字母或数字)。
1、1 传统方式
可以从定义一个验证文本(以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"); Validator lowerCaseValidator = new Validator(new IsAllLowerCase ()); boolean b2 = lowerCaseValidator.validate("bbbb"); 复制代码
1、2 JAVA8方式
使用java8后可以看出ValidationStrategy是一个函数接口了而且它还与Predicate具有同样的函数描述。这意味着我们不需要声明新的类来实现不同的策略,通过直接传递Lambda表达式就能达到同样的目的,并且还更简洁:
Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+")); boolean b1 = numericValidator.validate("aaaa"); Validator lowerCaseValidator =new Validator((String s) -> s.matches("\\d+")); boolean b2 = lowerCaseValidator.validate("bbbb"); 复制代码
如上所述,Lambda表达式避免了采用策略设计模式时僵化的模板代码。Lambda表达式实际已经对部分代码(或策略)进行了封装,而这就是创建策略设计模式的初衷。因此,强烈建议对类似的问题,应该尽量使用Lambda表达式来解决。
2、 模板设计模式
如果需要采用某个算法的框架,同时又希望有一定的灵活度,能对它的某些部分进行改进,那么采用模板方法设计模式是比较通用的方案。模板方法模式在“希望使用这个算法,但是需要对其中的某些行进行改进,才能达到希望的效果”时是非常有用的。
假设需要编写一个简单的在线银行应用。通常,用户需要输入一个用户账户,之后应用才能从银行的数据库中得到用户的详细信息,最终完成一些让用户满意的操作。不同分行的在线银行应用让客户满意的方式可能还略有不同,比如给客户的账户发放红利,或者仅仅是少发送一些推广文件。通过下面的抽象类方式来 实现在线银行应用:
2、1 传统方式
public abstract class OnlineBanking { public void processCustomer(int id){ Customer c = Database.getCustomerWithId(id); makeCustomerHappy(c); } abstract void makeCustomerHappy(Customer c); } 复制代码
processCustomer方法搭建了在线银行算法的框架:获取客户提供的ID,然后提供服务让用户满意。不同的支行可以通过继承OnlineBanking类,对该方法提供差异化的实现。
2、2 JAVA8方式
使用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()); 复制代码
这是又一个例子,佐证了Lamba表达式解决设计模式与生俱来的设计僵化问题。
3、 观察者模式
某些事件发生时(比如状态转变),如果一个对象(通常称之为主题)需要自动地通知其他多个对象(称为观察者),就会采用该方案。创建图形用户界面(GUI)程序时,经常会使用该设计模式。这种情况下,会在图形用户界面组件(比如按钮)上注册一系列的观察者。如果点击按钮,观察者就会收到通知,并随即执行某个特定的行为。 但是观察者模式并不局限于图形用户界面。比如,观察者设计模式也适用于股票交易的情形,多个券商可能都希望对某一支股票价格(主题)的变动做出响应。
假设需要为Twitter这样的应用设计并实现一个定制化的通知系统。好几家报纸机构,比如《纽约时报》《卫报》以及《世界报》都订阅了新闻,他们希望当接收的新闻中包含他们感兴趣的关键字时,能得到特别通知.
3、1 传统方式
首先,需要一个观察者接口,它将不同的观察者聚合在一起。它仅有一个名为notify的方法,一旦接收到一条新的新闻,该方法就会被调用:
public interface Observer { void notify(String tweet); } 复制代码
声明不同的观察者(比如,这里是三家不同的报纸机构),依据新闻中不同的关键字分别定义不同的行为:
class NYTimes implements Observer{ public void notify(String tweet) { if(tweet != null && tweet.contains("money")){ System.out.println("Breaking news in NY! " + tweet); } } } class Guardian implements Observer{ public void notify(String tweet) { if(tweet != null && tweet.contains("queen")){ System.out.println("Yet another news in London... " + tweet); } } } class LeMonde implements Observer{ public void notify(String tweet) { if(tweet != null && tweet.contains("wine")){ System.out.println("Today cheese, wine and news! " + tweet); } } } 复制代码
定义一个Subject接口:
interface Subject{ void registerObserver(Observer o); void notifyObservers(String tweet); } 复制代码
Subject使用registerObserver方法可以注册一个新的观察者,使用notifyObservers方法通知它的观察者一个新闻的到来。让我们更进一步,实现Feed类:
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.notify(tweet)); } } 复制代码
Feed类在内部维护了一个观察者列表,一条新闻到达时,它就进行通知:
Feed f = new Feed(); f.registerObserver(new NYTimes()); f.registerObserver(new Guardian()); f.registerObserver(new LeMonde()); f.notifyObservers("The queen said her favourite person is Steven!"); 复制代码
Guardian就会关注这条新闻!
3、2 JAVA8方式
Observer接口的所有实现类都提供了一个方法:notify。新闻到达时,它们都只是对同一段代码封装执行。Lambda表达式的设计初衷就是要消除这样的僵化代码。使用Lambda表达式后,无需显式地实例化三个观察者对象,直接传递Lambda表达式表示需要执行的行为即可:
f.registerObserver((String tweet) -> { if(tweet != null && tweet.contains("money")){ System.out.println("Breaking news in NY! " + tweet); } }); f.registerObserver((String tweet) -> { if(tweet != null && tweet.contains("queen")){ System.out.println("Yet another news in London... " + tweet); } }); 复制代码
那么,是否我们随时随地都可以使用Lambda表达式呢?答案是否定的!前文介绍的例子中,Lambda适配得很好,那是因为需要执行的动作都很简单,因此才能很方便地消除僵化代码。但是,观察者的逻辑有可能十分复杂,它们可能还持有状态,抑或定义了多个方法,诸如此类。在这些情形下,还是应该继续使用类的方式。
4 责任链模式
责任链模式是一种创建处理对象序列(比如操作序列)的通用方案。一个处理对象可能需要在完成一些工作之后,将结果传递给另一个对象,这个对象接着做一些工作,再转交给下一个处理对象,以此类推。通常,这种模式是通过定义一个代表处理对象的抽象类来实现的,在抽象类中会定义一个字段来记录后续对象。一旦对象完成它的工作,处理对象就会将它的工作转交给它的后继。
4、1 传统方式
public abstract class ProcessingObject<T> { protected ProcessingObject<T> successor; public void setSuccessor(ProcessingObject<T> successor){ this.successor = successor; } public T handle(T input){ T r = handleWork(input); if(successor != null){ return successor.handle(r); } return r; } abstract protected T handleWork(T input); } 复制代码
创建两个处理对象,它们的功能是进行一些文本处理工作:
public class HeaderTextProcessing extends ProcessingObject<String> { public String handleWork(String text){ return "There is a person: " + text; } } public class SpellCheckerProcessing extends ProcessingObject<String> { public String handleWork(String text){ return text.replaceAll("Steven", "niu"); } } 复制代码
现在就可以将这两个处理对象结合起来,构造一个操作序列!
ProcessingObject<String> p1 = new HeaderTextProcessing(); ProcessingObject<String> p2 = new SpellCheckerProcessing(); p1.setSuccessor(p2); String result = p1.handle("Steven is niubility persion."); 复制代码
4、2Java8方式
这个模式看起来像是在链接(也即是构造) 函数,可以将处理对象作为函数的一个实例,或者更确切地说作为UnaryOperator-的一个实例。为了链接这些函数,需要使用andThen方法对其进行构造。
UnaryOperator<String> headerProcessing = (String text) -> "There is a persion: " + text; UnaryOperator<String> spellCheckerProcessing = (String text) -> text.replaceAll("Steven", "niu"); Function<String, String> pipeline = headerProcessing.andThen(spellCheckerProcessing); String result = pipeline.apply("Steven is niubility persion"); 复制代码
5、工厂模式
使用工厂模式,无需向客户暴露实例化的逻辑就能完成对象的创建。假定为一家银行工作,他们需要一种方式创建不同的金融产品:贷款、期权、股票,等等。通常,会创建一个工厂类,它包含一个负责实现不同对象的方法
5、1 传统方式
public 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); } } } 复制代码
这里贷款、股票和债券都是产品的子类。createProduct方法可以通过附加的逻辑来设置每个创建的产品。但是带来的好处也显而易 见,在创建对象时不用再担心会将构造函数或者配置暴露给客户,这使得客户创建产品时更加简单:
Product p = ProductFactory.createProduct("loan"); 复制代码
5、2Java8方式
java8后可以像引用方法一样引用构造函数。比如,下面就是一个引用贷款构造函数的示例:
Supplier<Product> loanSupplier = Loan::new; Loan loan = loanSupplier.get(); 复制代码
通过这种方式可以重构之前的代码,创建一个Map,将产品名映射到对应的构造函数:
final static Map<String, Supplier<Product>> map = new HashMap<>(); static { map.put("loan", Loan::new); map.put("stock", Stock::new); map.put("bond", Bond::new); } 复制代码
现在可以像之前使用工厂设计模式那样,利用这个Map来实例化不同的产品。
public static Product createProduct(String name){ Supplier<Product> p = map.get(name); if(p != null){ return p.get(); } throw new IllegalArgumentException("No such product " + name); } 复制代码
6、小结
Lambda表达式有助于避免使用面向对象设计模式时容易出现的僵化的模板代码,函数式编程具体实践了声明式编程(“只需要使用不相互影响的表达式,描述想要做什么,由系统来选择如何实现”)和无副作用计算,这两个思想能帮助更容易地构建和维护系统。