繁体   English   中英

在Java中创建不可变类的可能性

[英]Possibilities of creating immutable class in Java

在Java中创建不可变bean的可能性有多大? 例如,我有不可变类Person 什么是创建实例和填充私有字段的好方法。 公共构造函数对我来说似乎并不好,因为当类在其他应用程序中增长时会出现很多输入参数。 谢谢你的任何建议。

public class Person {

private String firstName;
private String lastName;
private List<Address> addresses;
private List<Phone> phones;

public List<Address> getAddresses() {
    return Collections.unmodifiableList(addresses);
}

public String getFirstName() {
    return firstName;
}

public String getLastName() {
    return lastName;
}

public List<Phone> getPhones() {
    return Collections.unmodifiableList(phones);
}
}

编辑 :更准确地指定问题。

您可以使用构建器模式

public class PersonBuilder {
  private String firstName;
  // and others...

  public PersonBuilder() {
    // no arguments necessary for the builder
  }

  public PersonBuilder firstName(String firstName) {
    this.firstName = firstName;
    return this;
  }

  public Person build() {
    // here (or in the Person constructor) you could validate the data
    return new Person(firstName, ...);
  }
}

然后你可以像这样使用它:

Person p = new PersonBuilder.firstName("Foo").build();

乍一看,它可能看起来比具有大量参数的简单构造函数更复杂(并且可能是),但是有一些显着的优点:

  • 您无需指定要保留默认值的值
  • 您可以扩展Person类和构建器,而无需声明多个构造函数或需要重写创建Person每个代码:只需向构建器添加方法,如果有人不调用它们,则无关紧要。
  • 您可以传递构建器对象以允许不同的代码片段来设置Person不同参数。
  • 您可以使用构建器创建多个类似的Person对象,这对单元测试很有用,例如:

     PersonBuilder builder = new PersonBuilder().firstName("Foo").addAddress(new Address(...)); Person fooBar = builder.lastName("Bar").build(); Person fooBaz = builder.lastName("Baz").build(); assertFalse(fooBar.equals(fooBaz)); 

您应该看一下构建器模式

一个好的解决方案是让你的字段final ,将你的构造函数添加为private并在你的代码中使用Builders 在我们的项目中,我们将Builder模式与验证框架结合起来,这样一旦创建了一个对象,我们就确定它是不可变的和有效的。

这是一个简单的例子:

public class Person {

public static class Builder {

    private String firstName;
    private String lastName;
    private final List<String> addresses = new ArrayList<String>();
    private final List<String> phones = new ArrayList<String>();

    public Person create() {
        return new Person(firstName, lastName, addresses, phones);
    }

    public Builder setFirstName(String firstName) {
        this.firstName = firstName;
        return this;
    }

    public Builder setLastName(String lastName) {
        this.lastName = lastName;
        return this;
    }

    public Builder addAddresse(String adr) {
        if (adr != null) {
            addresses.add(adr);
        }
        return this;
    }

    public Builder addPhone(String phone) {
        if (phone != null) {
            phones.add(phone);
        }
        return this;
    }
}

// ************************ end of static declarations **********************

private final String firstName;
private final String lastName;
private final List<String> addresses;
private final List<String> phones;

private Person(String firstName, String lastName, List<String> addresses, List<String> phones) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.addresses = addresses;
    this.phones = phones;
}

public List<String> getAddresses() {
    return Collections.unmodifiableList(addresses);
}

public String getFirstName() {
    return firstName;
}

public String getLastName() {
    return lastName;
}

public List<String> getPhones() {
    return Collections.unmodifiableList(phones);
}
}

在我的示例中,您可以看到Builder中的所有setter都返回Builder实例,以便您可以轻松地链接setter调用。 这非常有用。

你可以看一下Joshua Bloch提出的Builder模式。

正如我之前所说,结合验证框架(参见http://www.hibernate.org/subprojects/validator.html ),这非常强大。

带接口。 做这个:

public interface Person {
    String getFirstName();
    String getLastName();

    // [...]
}

你的实施:

// PersonImpl is package private, in the same package as the Factory
class PersonImpl {

    String getFirstName();
    void setFirstName(String s);
    String getLastName();
    void setLastName(String s);

    // [...]
}

// The factory is the only authority to create PersonImpl
public class Factory {

    public static Person createPerson() {
        PersonImpl result = new PersonImpl();

        // [ do initialisation here ]

        return result;
    }
}

永远不要将实现暴露给你希望Person不可变的地方。

尽管如此,在构造函数中初始化是实现不变性的最简单和最安全的方法,因为这是在不可变类中使用final字段的唯一方法(这是标准习惯用法,并且具有有益效果,特别是如果您的类在多线程环境中使用)。 如果你班上有很多属性,这可能表明它试图做太多。 考虑将其划分为较小的类,或将相关属性组提取到复合​​属性类中。

使用Builder(带有私有构造函数)是可能的,但是它仍然需要一种方法来设置正在构建的对象的属性。 因此,您将回到构造函数参数与访问私有成员的原始困境。 在后一种情况下,你不能声明正在构建的对象的属性为final ,恕我直言是一个很好的减号。 在前一种情况下,您仍然可以首先使用相同长的构造函数参数列表。 刚才有很多额外的样板代码。

对所有实例变量使用final字段。 如果您愿意,可以创建构造函数并选择不公开setter,例如,

public class Person {
   private final String firstName;
   ....

   public Person(String firstName, ... ) {
       this.firstName = firstName;
   }
}

您可以通过创建只读接口然后将实现变为可变bean来实现“不可变”bean。 传递接口不允许变异,但是当你构造对象并实现它时,你可以做各种各样的bean-y事情:

public interface Person {
   String getFirstName();
   String getLastName();
   // ... other immutable methods ...
}

public class MutablePerson implements Person {
   // ... mutable functions, state goes here ...
}

使用工厂模式:

  • 让Person成为只有“get”-functions的接口
  • 使用适当的API创建PersonFactory以构建Person对象
  • PersonFactory创建一个实现Person-interface的对象并返回它
  1. final fields.
  2. 通过声明为final public class Person使类成为“最终”类
  3. 不要使用setXXX()方法来设置值,因为它会改变变量的状态。 但是允许getXXX()方法。
  4. 使用私有构造函数,以便您可以使用构造函数本身设置字段。

遵循上面的Immutable类指南。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM