Builder模式与Java语法

栏目: Java · 发布时间: 5年前

内容简介:Builder模式是在Java中最流行的模式之一。它很简单,有助于保持对象不可变,并且可以使用模式的流畅变体示例:调用方式:

Builder模式是在 Java 中最流行的模式之一。它很简单,有助于保持对象不可变,并且可以使用 Project Lombok的@BuilderImmutables工具 生成,仅举几例。

模式的流畅变体示例:

<b>public</b> <b>class</b> User {

  <b>private</b> <b>final</b> String firstName;

  <b>private</b> <b>final</b> String lastName;

  User(String firstName, String lastName) {
    <b>this</b>.firstName = firstName;
    <b>this</b>.lastName = lastName;
  }

  <b>public</b> <b>static</b> Builder builder() {
      <b>return</b> <b>new</b> Builder();
  }

  <b>public</b> <b>static</b> <b>class</b> Builder {

    String firstName;
    String lastName;

    Builder firstName(String value) {
        <b>this</b>.firstName = value;
        <b>return</b> <b>this</b>;
    }

    Builder lastName(String value) {
        <b>this</b>.lastName = value;
        <b>return</b> <b>this</b>;
    }

    <b>public</b> User build() {
        <b>return</b> <b>new</b> User(firstName, lastName);
    }
  }
}

调用方式:

User.Builder builder = User.builder().firstName(<font>"Sergey"</font><font>).lastName(</font><font>"Egorov"</font><font>);

<b>if</b> (newRules) {
    builder.firstName(</font><font>"Sergei"</font><font>);
}

User user = builder.build();
</font>

解释:

  1. User class是不可变的,一旦我们实例化它就无法更改。
  2. 它的构造函数具有包私有可见性,必须使用构建器来实例化实例User。
  3. Builder的字段不是不可变的,可以在构建实例之前多次更改User。
  4. builder流利并且返回this(类型Builder)并且可以链接。

有什么问题?

继承问题

想象一下,我们想扩展User类:(banq注:其实如果User类是DDD值对象,实际是 final class,不能再被继承了)。

<b>public</b> <b>class</b> RussianUser <b>extends</b> User {
    <b>final</b> String patronymic;

    RussianUser(String firstName, String lastName, String patronymic) {
        <b>super</b>(firstName, lastName);
        <b>this</b>.patronymic = patronymic;
    }

    <b>public</b> <b>static</b> RussianUser.Builder builder() {
        <b>return</b> <b>new</b> RussianUser.Builder();
    }

    <b>public</b> <b>static</b> <b>class</b> Builder <b>extends</b> User.Builder {

        String patronymic;

        <b>public</b> Builder patronymic(String patronymic) {
            <b>this</b>.patronymic = patronymic;
            <b>return</b> <b>this</b>;
        }

        <b>public</b> RussianUser build() {
            <b>return</b> <b>new</b> RussianUser(firstName, lastName, patronymic);
        }
    }
}

调用代码时会出错:

RussianUser me = RussianUser.builder()
    .firstName(<font>"Sergei"</font><font>) </font><font><i>// returns User.Builder :(</i></font><font>
    .patronymic(</font><font>"Valeryevich"</font><font>) </font><font><i>// // Cannot resolve method!出错</i></font><font>
    .lastName(</font><font>"Egorov"</font><font>)
    .build();
</font>

这里的问题是因为firstName有以下定义:

User.Builder firstName(String value) {
        <b>this</b>.value = value;
        <b>return</b> <b>this</b>;
    }

Java的编译器无法检测到this的意思是RussianUser.Builder而不是User.Builder!

我们甚至无法改变顺序:

RussianUser me = RussianUser.builder()
    .patronymic(<font>"Valeryevich"</font><font>)
    .firstName(</font><font>"Sergei"</font><font>)
    .lastName(</font><font>"Egorov"</font><font>)
    .build() </font><font><i>// compilation error! User is not assignable to RussianUser</i></font><font>
    ;
</font>

可能的解决方案: Self typing

解决它的一种方法是添加一个泛型参数User.Builder,指示要返回的类型:

 <b>public</b> <b>static</b> <b>class</b> Builder<SELF <b>extends</b> Builder<SELF>> {

    SELF firstName(String value) {
        <b>this</b>.firstName = value;
        <b>return</b> (SELF) <b>this</b>;
    }

并将其设置为RussianUser.Builder:

<b>public</b> <b>static</b> <b>class</b> Builder <b>extends</b> User.Builder<RussianUser.Builder> {

它现在有效:

RussianUser.builder()
    .firstName(<font>"Sergei"</font><font>) </font><font><i>// returns RussianUser.Builder :)</i></font><font>
    .patronymic(</font><font>"Valeryevich"</font><font>) </font><font><i>// RussianUser.Builder</i></font><font>
    .lastName(</font><font>"Egorov"</font><font>) </font><font><i>// RussianUser.Builder</i></font><font>
    .build(); </font><font><i>// RussianUser</i></font><font>
</font>

它还适用于多级继承:

<b>class</b> A<SELF <b>extends</b> A<SELF>> {

    SELF self() {
        <b>return</b> (SELF) <b>this</b>;
    }
}

<b>class</b> B<SELF <b>extends</b> B<SELF>> <b>extends</b> A<SELF> {}

<b>class</b> C <b>extends</b> B<C> {}

那么,问题解决了吗?好吧,不是真的... 基本类型不能轻易实例化!

因为它使用递归泛型定义,所以我们有一个递归问题!

new A<A<A<A<A<A<A<...>>>>>>>()

但是,它可以解决( 除非你使用Kotlin ):

A a = new A<>();

在这里,我们依赖于Java的原始类型和钻石运算符<>。

但是,正如所提到的,它不适用于其他语言,如Kotlin或Scala,并且一般来说是这是一种黑客方式。

理想的解决方案:使用Java的Self typing

在继续阅读之前,我应该警告你:这个解决方案不存在,至少现在还没有。拥有它会很好,但目前我不知道任何JEP。PS谁知道如何提交JEP?;)

Self typing作为语言功能存在于Swift等语言中。

想象一下以下虚构的Java伪代码示例:

<b>class</b> A {

    @Self
    <b>void</b> withSomething() {
        System.out.println(<font>"something"</font><font>);
    }
}

<b>class</b> B <b>extends</b> A {
    @Self
    <b>void</b> withSomethingElse() {
        System.out.println(</font><font>"something else"</font><font>);
    }
}
</font>

调用:

<b>new</b> B()
    .withSomething() <font><i>// replaced with the receiver instead of void</i></font><font>
    .withSomethingElse();
</font>

如您所见,问题可以在编译器级别解决。事实上,有像 Manifold的@Self 这样 javac编译器插件。

真正的解决方案:想一想

但是,如果不是试图解决返回类型问题,我们...删除类型?

<b>public</b> <b>class</b> User {

  <font><i>// ...</i></font><font>

    <b>public</b> <b>static</b> <b>class</b> Builder {

        String firstName;
        String lastName;

        <b>void</b> firstName(String value) {
            <b>this</b>.firstName = value;
        }

        <b>void</b> lastName(String value) {
            <b>this</b>.lastName = value;
        }

        <b>public</b> User build() {
            <b>return</b> <b>new</b> User(firstName, lastName);
        }
    }
}
<b>public</b> <b>class</b> RussianUser <b>extends</b> User {

    </font><font><i>// ...</i></font><font>

    <b>public</b> <b>static</b> <b>class</b> Builder <b>extends</b> User.Builder {

        String patronymic;

        <b>public</b> <b>void</b> patronymic(String patronymic) {
            <b>this</b>.patronymic = patronymic;
        }

        <b>public</b> RussianUser build() {
            <b>return</b> <b>new</b> RussianUser(firstName, lastName, patronymic);
        }
    }
}
</font>

调用方式:

RussianUser.Builder b = RussianUser.builder();
b.firstName(<font>"Sergei"</font><font>);
b.patronymic(</font><font>"Valeryevich"</font><font>);
b.lastName(</font><font>"Egorov"</font><font>);
RussianUser user = b.build(); </font><font><i>// RussianUser</i></font><font>
</font>

你可能会说,“这不是方便而且冗长,至少在Java中”。我同意,但......这是Builder的问题吗?

还记得我说过这个Builder是可变的吗?那么,为什么不利用它呢!

让我们将以下内容添加到我们的基础构建器中:

<b>public</b> <b>class</b> User {

  <font><i>// ...</i></font><font>

    <b>public</b> <b>static</b> <b>class</b> Builder {
        <b>public</b> Builder() {
            <b>this</b>.configure();
        }

        <b>protected</b> <b>void</b> configure() {}
</font>

并使用我们的构建器作为匿名对象:

RussianUser user = <b>new</b> RussianUser.Builder() {
    @Override
    <b>protected</b> <b>void</b> configure() {
        firstName(<font>"Sergei"</font><font>); </font><font><i>// from User.Builder</i></font><font>
        patronymic(</font><font>"Valeryevich"</font><font>); </font><font><i>// From RussianUser.Builder</i></font><font>
        lastName(</font><font>"Egorov"</font><font>); </font><font><i>// from User.Builder</i></font><font>
    }
}.build();
</font>

继承不再是一个问题,但它仍然有点冗长。

这里是Java的另一个“特性”派上用场: Double brace initialization/双大括号初始化

这里我们使用初始化块来设置字段。Swing / Vaadin人可能认识到这种模式;)

有些人不喜欢它(随意评论为什么,顺便说一句)。我不会在应用程序的性能关键部分使用它,但如果是,比方说,测试,那么这种方法似乎标记了所有检查:

  1. 可以与从Mammoths Age开始的任何Java版本一起使用。
  2. 对其他JVM语言友好。
  3. 简洁。
  4. 语言的本机特性,而不是黑客。

结论

我们已经看到,虽然Java不提供自键型语法,但我们可以通过使用Java的另一个功能来解决问题,而不会破坏替代JVM语言的体验。

虽然一些开发人员似乎认为双大括号初始化是一种反模式,但它实际上似乎对某些用例有其价值。毕竟,这只是匿名类中构造函数定义的糖。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

UNIX网络编程

UNIX网络编程

史蒂文斯、芬纳、鲁道夫 / 杨继张 / 清华大学出版社 / 2006-1 / 98.00元

《UNIX网络编程》(第1卷)(套接口API第3版)第1版和第2版由已故UNIX网络专家W. Richard Stevens博士独自编写。《UNIX网络编程》(第1卷)(套接口API第3版)是3版,由世界著名网络专家Bill Fenner和Andrew M. Rudoff执笔,根据近几年网络技术的发展,对上一版进行全面修订,增添了IPv6的更新过的信息、SCTP协议和密钥管理套接口的内容,删除了X......一起来看看 《UNIX网络编程》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具