5

我很好奇为 java bean 提供不变性的可能性(这里的 bean 是指具有为成员提供 getter 和 setter 的空构造函数的类)。显然,这些类不是不可变的,它们用于从数据层传输值,这似乎是一个真正的问题。

StackOverflow 中提到了一种解决此问题的方法,称为“C# 中的不可变对象模式”,其中对象在完全构建后被冻结。我有另一种方法,真的很想听听人们对此的看法。

该模式涉及两个类 Immutable 和 Mutable,其中 Mutable 和 Immutable 都实现了一个提供非可变 bean 方法的接口。

例如

public interface DateBean {
    public Date getDate();
    public DateBean getImmutableInstance();
    public DateBean getMutableInstance();
}

public class ImmutableDate implements DateBean {
    private Date date;

ImmutableDate(Date date) {
    this.date = new Date(date.getTime());
}

public Date getDate() {
    return new Date(date.getTime());
}

    public DateBean getImmutableInstance() {
        return this;
    }

    public DateBean getMutableInstance() {
        MutableDate dateBean = new MutableDate();
        dateBean.setDate(getDate());
        return dateBean;
    }
}

public class MutableDate implements DateBean {
    private Date date;

public Date getDate() {
    return date;
}

public void setDate(Date date) {
    this.date = date;
}

public DateBean getImmutableInstance() {
    return new ImmutableDate(this.date);
}

    public DateBean getMutableInstance() {
        MutableDate dateBean = new MutableDate();
        dateBean.setDate(getDate());
        return dateBean;
    }
}

这种方法允许使用反射(通过通常的约定)构建 bean,并且还允许我们在最近的机会转换为不可变的变体。不幸的是,每个 bean 显然有大量的样板。

我很想听听其他人对这个问题的看法。(我很抱歉没有提供一个好的问题,可以回答而不是讨论:)

4

3 回答 3

3

一些评论(不一定有问题):

  1. Date 类本身是可变的,因此您可以正确复制它以保护不变性,但我个人更喜欢在构造函数中转换为 long 并在 getter 中返回一个新的 Date(longValue)。
  2. 您的两个 getWhateverInstance() 方法都返回 DateBean ,这将需要强制转换,更改接口以返回特定类型可能是一个想法。
  3. 说了这么多,我倾向于只拥有两个类,一个是可变的,一个是不可变的,如果合适的话,共享一个通用(即仅获取)接口。如果你认为会有很多来回转换,那么为这两个类添加一个复制构造函数。
  4. 我更喜欢不可变类将字段声明为final以使编译器也强制执行不可变性。

例如

public interface DateBean {
    public Date getDate();
}

public class ImmutableDate implements DateBean {
    private final long date;

    ImmutableDate(long date) {
       this.date = date;
    }

    ImmutableDate(Date date) {
       this(date.getTime());
    }

    ImmutableDate(DateBean bean) {
       this(bean.getDate());
    }

    public Date getDate() {
         return new Date(date);
    }
}


public class MutableDate implements DateBean {
    private long date;

    MutableDate() {}

    MutableDate(long date) {
       this.date = date;
    }

    MutableDate(Date date) {
       this(date.getTime());
    }

    MutableDate(DateBean bean) {
       this(bean.getDate());
    }

    public Date getDate() {
        return new Date(date);
    }

    public void setDate(Date date) {
        this.date = date.getTime();
    }

}
于 2008-11-20T23:27:57.827 回答
2

我想我会使用委托模式 - 使用必须在构造函数中指定的单个 DateBean 成员创建一个 ImmutableDate 类:

public class ImmutableDate implements DateBean
{
   private DateBean delegate;

   public ImmutableDate(DateBean d)
   {
      this.delegate = d;
   }

   public Date getDate()
   {
      return delegate.getDate();
   }
}

如果我需要强制 DateBean d 具有不变性,我只需在其上使用 new ImmutableDate(d) 即可。我本可以很聪明,并确保我没有委派代表,但你明白了。这避免了客户端试图将其转换为可变的问题。这很像 JDK 对 Collections.unmodifiableMap() 等所做的。(然而,在这些情况下,突变函数仍然必须实现,并且被编码为抛出运行时异常。如果你有一个没有突变体)。

再一次,它是乏味的样板代码,但它是像 Eclipse 这样的优秀 IDE 只需单击几下鼠标即可自动为您生成的东西。

如果这是您最终对很多域对象所做的事情,您可能需要考虑使用动态代理,甚至可能是 AOP。然后为任何对象构建代理,委托所有 get 方法,并根据需要捕获或忽略 set 方法,这将相对容易。

于 2008-11-21T21:31:47.000 回答
1

我使用接口和强制转换来控制 bean 的可变性。我认为没有充分的理由使用 和 之类的方法使我的域对象复杂getImmutableInstance()getMutableInstance()

为什么不直接使用继承和抽象呢?例如

public interface User{

  long getId();

  String getName();

  int getAge();

}

public interface MutableUser extends User{

  void setName(String name);

  void setAge(int age);

}

以下是代码的客户端将执行的操作:

public void validateUser(User user){
  if(user.getName() == null) ...
}

public void updateUserAge(MutableUser user, int age){
  user.setAge(age);
}

它回答了你的问题吗?

yc

于 2008-11-20T23:29:38.867 回答