Java8中的设计模式(一)

之前在infoq上看到一篇文章:
swift版本原文参考:
http://www.infoq.com/cn/articles/design-patterns-in-swift
于是想着把这篇文章修改为Java8的版本,本文是对原文的Java8版本的修改,所以大部分文章和示例都是采用原文的叙述;再次表达对原文作者的感谢;

设计模式##

设计模式(Design Pattern)是对软件设计中普遍存在的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛等人(Erich Gamma,Richard Helm,Ralph Johnson和John Vlissides这四人提出的。也被称为:Gang of Four,GOF,四人帮)在1990年代从建筑设计领域引入到计算机科学的。 设计模式并不能直接用于完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。
以上描述摘自维基百科。
随着我们所使用的编程语言的演化,我们遇到的问题也确实一直在改变。GOF提出的23种设计模式也许部分过时了。但我们遇到的问题并不会消失,设计模式的概念将一直存在:提炼普遍存在的问题,提出解决方案。这其实是一个抽象过程。现在已有的编程语言都存在表达的局限,即对某类问题抽象层次过低。所以,我们在使用任何编程语言时,都还是会遇到一些普遍存在的却没有被语言本身很好解决的问题。这时,我们就会使用到“设计模式”,即人们总结出来的解决方案:遇到问题A,用方案A;遇到问题B,用方案B。只不过问题会一直变化。现在我们不可以再使用那23个模式来解决问题了,但是我们仍然需要总结出其它模式来解决新的问题。这种情况一直会持续到我们拥有“完美语言”的那一天。但现在看起来,这一天还没有到来的迹象。

Java8中的设计模式##

Java8中提出了函数式编程等新的概念,使得Java8在面对传统的GOF23模式的时候,解决问题的方式已经得到了部分的改善,之前的一些设计模式在Java8面前就已经不再是问题了。但是Java8提出了函数式编程的同时,因为涉及到向后兼容的问题,同样在Java8中可能面对新的代码设计问题,比如接口中的默认方法带来的诸多设计细节,同样也是会引人深思的;
下面主要讨论的是,在Java8中,传统的哪些开发模式被消除了或者以一种更简单的方式简化了。

一,命令模式##

命令模式使用对象封装一系列操作(命令),使得操作可以重复使用,也易于在对象间传递。首先来一个传统Java方式实现的命令模式代码。

public class Light {
  public void on() {
    System.out.println("light turn on");
  }
  public void off() {
    System.out.println("light turn off");
  }
}

interface Command {
  void execute();
}

class FilpUpCommand implements Command {
   private Light light;
   public FilpUpCommand(Light light) {
    this.light = light;
  }
  public void execute() {
    light.on();
  }
}

class FilpDownCommand implements Command {
  private Light light;
  public FilpDownCommand(Light light) {
    this.light = light;
  }
  public void execute() {
    light.off();
  }
}

以上代码中,灯(Light)是命令(Command)的操作对象(Receiver)。我们定义了命令的协议,同时我们实现两个具体的命令操作:FlipUpCommand和FlipDownCommand。它们分别使灯亮,和使灯灭。

class LightSwitch{
  private List<Command> queue=new ArrayList<>();

  public void addCommand(Command cmd){
    queue.add(cmd);
  }

  public void execute(){
    for(Command cmd:queue){
        cmd.execute();
    }
  }
}

class Client{

  public static void pressSwitch(){
    Light lamp=new Light();
    Command flipUpCommand=new FilpUpCommand(lamp);
    Command flipDowomnCmand=new FilpDownCommand(lamp);
    
    LightSwitch lightSwitch = new LightSwitch();
    lightSwitch.addCommand(flipUpCommand);
    lightSwitch.addCommand(flipDowomnCmand);
    lightSwitch.addCommand(flipUpCommand);
    lightSwitch.addCommand(flipDowomnCmand);
    
    lightSwitch.execute();
  }
}

上面的代码首先创建了一个命令执行者LightSwitch,并创建一个客户对象来使用命令;
在函数式编程中,由于存在高阶函数。我们可以直接将一个函数作为参数传给另外一个函数。所以,使用类包裹函数在对象间传递这件事情就显得多余了。以下代码显示如何使用高阶函数达到命令模式相同的效果:

class LightSwitchFP {
  private List<Consumer<Light>> queue = new ArrayList<>();
  public void addCommand(Consumer<Light> cmd) {
    queue.add(cmd);
  }
  public void execute(Light light) {
    for (Consumer<Light> cunsumer : queue) {
        cunsumer.accept(light);
    }
  }
}

class Client {
  public static void pressSwitch() {
    Light lamp = new Light();

    Consumer<Light> flipUp = light -> {light.on();};
    Consumer<Light> flipDown = light -> {light.off();};
    LightSwitchFP lightSwitch = new LightSwitchFP();
    lightSwitch.addCommand(flipUp);
    lightSwitch.addCommand(flipDown);
    lightSwitch.addCommand(flipUp);
    lightSwitch.addCommand(flipDown);

    lightSwitch.execute(lamp);
  }
}

在Java8中,首先我们直接使用Java8提供的Consumer函数接口作为我们的命令接口,因为有了lambda表达式,我们根本无需在单独为具体命令对象创建类型,而通过传入labmda表达式来完成具体命令对象的创建;

二,策略模式##

策略模式定义了一系列算法,将每个算法封装起来,并且使它们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。
下面简单演示一个传统的策略模式实现方案:

interface Strategy {  
    public Integer compute(Integer op1, Integer op2);
}

class Add implements Strategy {
    public Integer compute(Integer op1, Integer op2) {
     return op1 + op2;
    }
}
class Multiply implements Strategy {
    public Integer compute(Integer op1, Integer op2) {
        return op1 * op2;
    }
}
class Context {
    private Strategy strategy;
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }
    public void use(Integer first, Integer second) {
        System.out.println(this.strategy.compute(first, second));
    }
}                     

类似于命令模式,策略模式中的策略对象主要用于封装操作(函数),不同的是策略模式中的策略对象封装的是不同的算法。这些算法实现了相同的接口,在这个例子中,接口是用Strategy协议表示的。我们使用两个实现了Strategy协议的具体类:Add和Multiply分别封装两个简单的算法。Context对象,用于对算法进行配置选择,它有一个Strategy类型的实例变量:strategy。通过配置Context的strategy具体类型,可以使用不同的算法。

然后我们再看看怎么简化策略模式:

public static final BinaryOperator<Integer> add = (op1, op2) -> op1 + op2;
public static final BinaryOperator<Integer> multiply = (op1, op2) -> op1 * op2;

class ContextFP {
    private BinaryOperator<Integer> strategy;

    public ContextFP(BinaryOperator<Integer> strategy) {
        this.strategy = strategy;
    }

    public void use(Integer first, Integer second) {
        System.out.println(strategy.apply(first, second));
    }
}

public static void main(String[] args) {
    StraFP fp = new StraFP();
    ContextFP ctx = fp.new ContextFP(StraFP.add);
    ctx.use(1, 2);
}

在Java8中,我们很自然的使用内建的function interface作为封装算法的载体,这样更为直接自然。例子中,ContextFP的构造器的传参就是函数类型。给予构造器代表不同算法的函数,就配置了不同的算法。

函数也可以作为类的实例变量。这样在类中,直接维护代表算法的函数也成为可能。从类型声明可以看出,ContextFP中的实例变量strategy就是一个函数。

一等函数的概念使得函数获得了更高的地位,使得函数的灵活性大大增加。在很多场景下直接使用函数会是更直接自然的选择。面向对象编程范式,赋予了对象更高的地位。但是,如果给予函数“正常”一些的地位,可以简化不少问题。设计模式中的不少模式存在都是由于函数的使用限制,需要使用在使用类包裹函数。类似的例子还有模版方法模式(Template method)。

上面代码示例不能对策略做很好的封装,下面提供了一个枚举的版本:

public enum StrategyEnum {
    ADD(() -> (x, y) -> x + y), 
    MULTIPLY(() -> (x, y) -> x * y);

    private Supplier<BinaryOperator<Integer>> operation;

    private StrategyEnum(Supplier<BinaryOperator<Integer>> operation) {
        this.operation = operation;
    }

    public BinaryOperator<Integer> get() {
        return operation.get();
    }
}

class ContextFP {
    private StrategyEnum strategy;

    public ContextFP(StrategyEnum strategy) {
        this.strategy = strategy;
    }

    public void use(Integer first, Integer second) {
        System.out.println(strategy.get().apply(first, second));
    }
}
    原文作者:叩丁狼教育
    原文地址: https://www.jianshu.com/p/a666ca454e8f
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞