【JAVA8】使用Lambda重构面向对象的设计模式

2019-07-03 00:00:00 模式 重构 面向对象

对设计经验的归纳总结被称为设计模式。

设计软件时,如果你愿意,可以复用这些方式方法来解决一些常见问题。这看起来像传统建筑工程师的工作方式,对典型的场景(比如悬挂桥、拱桥等)都定义有可重用的解决方案。例如,

  • 访问者模式常用于分离程序的算法和它的操作对象。
  • 单例模式一般用于限制类的实例化,仅生成一份对象。

策略模式

StrategyMaingithub.com

策略模式代表了解决一类算法的通用解决方案,你可以在运行时选择使用哪种方案

  • 一个代表某个算法的接口(它是策略模式的接口)。
  • 一个或多个该接口的具体实现,它们代表了算法的多种实现(比如实体类Concrete-StrategyA或者ConcreteStrategyB)。
  • 一个或多个使用策略对象的客户。

假设你希望验证输入的内容是否根据标准进行了恰当的格式化(比如只包含小写字母或数字)。你可以从定义一个验证文本(以String的形式表示)的接口入手:

interface ValidationStrategy {
   public 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);
   }
}

使用Lambda表达式

到现在为止,你应该已经意识到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表达式避免了采用策略设计模式时僵化的模板代码。

模板方法

OnlineBankingLambdagithub.com
OnlineBankinggithub.com
OnlineBankingDemo1github.com

如果你需要采用某个算法的框架,同时又希望有一定的灵活度,能对它的某些部分进行改进,那么采用模板方法设计模式是比较通用的方案。换句话说,模板方法模式在你“希望使用这个算法,但是需要对其中的某些行进行改进,才能达到希望的效果”时是非常有用的。

假设你需要编写一个简单的在线银行应用。通常,用户需要输入一个用户账户,之后应用才能从银行的数据库中得到用户的详细信息,最终完成一些让用户满意的操作。

不同分行的在线银行应用让客户满意的方式可能还略有不同,比如给客户的账户发放红利,或者仅仅是少发送一些推广文件。你可能通过下面的抽象类方式来实现在线银行应用:

public void processCustomer(int id){
    Customer c = Database.getCustomerWithId(id);
    makeCustomerHappy(c);
}
abstract void makeCustomerHappy(Customer c);

// dummy Datbase class static private class Database{
    static Customer getCustomerWithId(int id){ return new Customer();}
}

使用Lambda表达式

public static void main(String[] args) {
    new OnlineBankingLambda()
            .processCustomer(1337, (Customer c) -> System.out.println("Hello " + c.getName()));
}

public void processCustomer(int id, Consumer<Customer> makeCustomerHappy){
    Customer c = Database.getCustomerWithId(id);
    makeCustomerHappy.accept(c);
}

// dummy Database class static private class Database{
    static Customer getCustomerWithId(int id){ return new Customer();}
}

Lamba表达式能帮助你解决设计模式与生俱来的设计僵化问题

观察者模式

ObserverLambdagithub.com

观察者模式是一种比较常见的方案,某些事件发生时(比如状态转变),如果一个对象(通常我们称之为主题)需要自动地通知其他多个对象(称为观察者),就会采用该方案。

创建图形用户界面(GUI)程序时,你经常会使用该设计模式。这种情况下,你会在图形用户界面组件(比如按钮)上注册一系列的观察者。如果点击按钮,观察者就会收到通知,并随即执行某个特定的行为。

但是观察者模式并不局限于图形用户界面。比如,观察者设计模式也适用于股票交易的情形,多个券商可能都希望对某一支股票价格(主题)的变动做出响应。

《【JAVA8】使用Lambda重构面向对象的设计模式》
《【JAVA8】使用Lambda重构面向对象的设计模式》

你需要为Twitter这样的应用设计并实现一个定制化的通知系统。想法很简单:好几家报纸机构,比如《纽约时报》《卫报》以及《世界报》都订阅了新闻,他们希望当接收的新闻中包含他们感兴趣的关键字时,能得到特别通知。

观察者

/**  * 观察者模式  */
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);
        }
    }
}

主题

public interface  Subject {
    void registerObserver(Observer o);
    void notifyObservers(String tweet);
}

public 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));
    }

}


public class ObserverTester {

    public static void main(String[] args) {
        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 f = new Feed();

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);
    }
});

f.notifyObservers("The queen said her favourite book is Java 8 in Action!");

是否我们随时随地都可以使用Lambda表达式呢?答案是否定的!

Lambda适配得很好,那是因为需要执行的动作都很简单,因此才能很方便地消除僵化代码。但是,观察者的逻辑有可能十分复杂,它们可能还持有状态,抑或定义了多个方法,诸如此类。在这些情形下,你还是应该继续使用类的方式。

责任链模式

ChainOfResponsibilityMaingithub.com

责任链模式是一种创建处理对象序列(比如操作序列)的通用方案。一个处理对象可能需要在完成一些工作之后,将结果传递给另一个对象,这个对象接着做一些工作,再转交给下一个处理对象,以此类推。

通常,这种模式是通过定义一个代表处理对象的抽象类来实现的,在抽象类中会定义一个字段来记录后续对象。一旦对象完成它的工作,处理对象就会将它的工作转交给它的后继。

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);
}

《【JAVA8】使用Lambda重构面向对象的设计模式》

可以创建两个处理对象,它们的功能是进行一些文 本处理工作。

class HeaderTextProcessing
        extends ProcessingObject<String> {
    public String handleWork(String text) {
        return "From Raoul, Mario and Alan: " + text;
    }
}

class SpellCheckerProcessing
        extends ProcessingObject<String> {
    public String handleWork(String text) {
        return text.replaceAll("labda", "lambda");
    }
}

现在你就可以将这两个处理对象结合起来,构造一个操作序列!

ProcessingObject<String>
       p1=new HeaderTextProcessing();
ProcessingObject<String> 
       p2 = new SpellCheckerProcessing();
p1.setSuccessor(p2);
String result = p1.handle("Aren't labdas really sexy?!!");
System.out.println(result);

使用Lambda表达式

这个模式看起来像是在链接(也即是构造)函数,你需要使用andThen方法对其进行构造。

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);

工厂模式

FactoryMaingithub.com

使用工厂模式,你无需向客户暴露实例化的逻辑就能完成对象的创建。比如,我们假定你为一家银行工作,他们需要一种方式创建不同的金融产品:贷款、期权、股票,等等。

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);
        }
    }
}

这里贷款(Loan)、股票(Stock)和债券(Bond)都是产品(Product)的子类。createProduct方法可以通过附加的逻辑来设置每个创建的产品。但是带来的好处也显而易见,你在创建对象时不用再担心会将构造函数或者配置暴露给客户,这使得客户创建产品时更加简单:

Product p = ProductFactory.createProduct("loan");

使用Lambda表达式

Supplier<Product> loanSupplier = Loan::new;
Loan loan = loanSupplier.get();

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 createProductLambda(String name){
    Supplier<Product> p = map.get(name);
    if(p != null) return p.get();
    throw new RuntimeException("No such product " + name);
}

这是个全新的尝试,它使用Java 8中的新特性达到了传统工厂模式同样的效果。但是,如果工厂方法createProduct需要接收多个传递给产品构造方法的参数,这种方式的扩展性不是很好。你不得不提供不同的函数接口,无法采用之前统一使用一个简单接口的方式。

比如,我们假设你希望保存具有三个参数(两个参数为Integer类型,一个参数为String类型)的构造函数;为了完成这个任务,你需要创建一个特殊的函数接口TriFunction。最终的结果是Map变得更加复杂。

public interface TriFunction<T, U, V, R>{
      R apply(T t, U u, V v);
}

Map<String, TriFunction<Integer, Integer, String, Product>> map = new HashMap<>();

小结

  • Lambda表达式能提升代码的可读性和灵活性。
  • 如果你的代码中使用了匿名类,尽量用Lambda表达式替换它们,但是要注意二者间语义的微妙差别,比如关键字this,以及变量隐藏。
  • 跟Lambda表达式比起来,方法引用的可读性更好 。
  • 尽量使用Stream API替换迭代式的集合处理。
  • Lambda表达式有助于避免使用面向对象设计模式时容易出现的僵化的模板代码,典型的比如策略模式、模板方法、观察者模式、责任链模式,以及工厂模式。
  • 尽量将复杂的Lambda表达式抽象到普通方法中。
  • Lambda表达式会让栈跟踪的分析变得更为复杂。

本文精简自 《JAVA8 IN ACTION》 一书

* 第三部分 高效 Java 8 编程 第8章 重构、测试和调试

end

    原文作者:Leons
    原文地址: https://zhuanlan.zhihu.com/p/67199045
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。

相关文章