Builder模式与Java语法

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

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

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

模式的流畅变体示例:

<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语言的体验。

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


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

查看所有标签

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

Code

Code

Charles Petzold / Microsoft Press / 2000-10-21 / USD 29.99

Paperback Edition What do flashlights, the British invasion, black cats, and seesaws have to do with computers? In CODE, they show us the ingenious ways we manipulate language and invent new means of ......一起来看看 《Code》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码