旧瓶装新酒,常用设计模式的Java8实现方式

简介: 新的语言特性常常让现存的编程模式或设计黯然失色。比如Java 5中引入了for-each循环,由于它的稳健性和简洁性,已经替代了很多显式使用迭代器的情形。Java7中推出的菱形操作符(<>)在创建实例时无需显式使用泛型,一定程度上推动了Java程序员们采用类型接口(type interface)进行程序设计。对设计经验的归纳总结被称为设计模式。设计软件时,如果愿意,可以复用这些方式方法来解决一些常见问题。这看起来像传统建筑工程师的工作方式,对典型的场景都定义有可重用的解决方案。例如,访问者模式常用于分离程序的算法和它的操作对象。单例模式一般用于限制类的实例化,仅生成一份对象。

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表达式有助于避免使用面向对象设计模式时容易出现的僵化的模板代码,函数式编程具体实践了声明式编程(“只需要使用不相互影响的表达式,描述想要做什么,由系统来选择如何实现”)和无副作用计算,这两个思想能帮助更容易地构建和维护系统。


目录
相关文章
|
7月前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
915 157
|
7月前
|
设计模式 Java Spring
Java 设计模式之责任链模式:优雅处理请求的艺术
责任链模式通过构建处理者链,使请求沿链传递直至被处理,实现发送者与接收者的解耦。适用于审批流程、日志处理等多级处理场景,提升系统灵活性与可扩展性。
723 2
|
9月前
|
设计模式 缓存 Java
Java设计模式(二):观察者模式与装饰器模式
本文深入讲解观察者模式与装饰器模式的核心概念及实现方式,涵盖从基础理论到实战应用的全面内容。观察者模式实现对象间松耦合通信,适用于事件通知机制;装饰器模式通过组合方式动态扩展对象功能,避免子类爆炸。文章通过Java示例展示两者在GUI、IO流、Web中间件等场景的应用,并提供常见陷阱与面试高频问题解析,助你写出灵活、可维护的代码。
|
7月前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
1724 35
|
7月前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
539 8
|
12月前
|
设计模式 缓存 安全
【高薪程序员必看】万字长文拆解Java并发编程!(8):设计模式-享元模式设计指南
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的经典对象复用设计模式-享元模式,废话不多说让我们直接开始。
238 0
|
9月前
|
设计模式 安全 Java
Java设计模式(一):单例模式与工厂模式
本文详解单例模式与工厂模式的核心实现及应用,涵盖饿汉式、懒汉式、双重检查锁、工厂方法、抽象工厂等设计模式,并结合数据库连接池与支付系统实战案例,助你掌握设计模式精髓,提升代码专业性与可维护性。
|
9月前
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。
|
设计模式 Java 数据安全/隐私保护
Java 设计模式:装饰者模式(Decorator Pattern)
装饰者模式属于结构型设计模式,允许通过动态包装对象的方式为对象添加新功能,提供比继承更灵活的扩展方式。该模式通过组合替代继承,遵循开闭原则(对扩展开放,对修改关闭)。
|
设计模式 消息中间件 搜索推荐
Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
【11月更文挑战第17天】观察者模式是一种行为设计模式,定义了一对多的依赖关系,使多个观察者对象能直接监听并响应某一主题对象的状态变化。本文介绍了观察者模式的基本概念、商业系统中的应用实例,如优衣库事件中各相关方的动态响应,以及模式的优势和实际系统设计中的应用建议,包括事件驱动架构和消息队列的使用。
307 6