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));
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容