Javaの不変Bean
-
08-07-2019 - |
質問
Java Beanに不変性を提供する可能性について非常に興味があります(Beanとは、メンバーにゲッターとセッターを提供する空のコンストラクターを持つクラスを意味します)。明らかに、これらのクラスは不変ではなく、データ層から値を転送するために使用される場合、これは実際の問題のように見えます。
この問題に対する1つのアプローチは、StackOverflowで「C#の不変オブジェクトパターン」と呼ばれています。オブジェクトが完全に構築されると凍結されます。私は別のアプローチをとっていますが、それについて人々の意見を本当に聞きたいです。
パターンには、ImutableとMutableの2つのクラスが含まれます。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ごとに明らかに大量の定型文があります。
この問題に対する他の人々のアプローチを聞くことに非常に興味があります。 (良い質問を提供しなかったことに対する謝罪。議論するよりも答えることができます:)
解決
委任パターンを使用すると思います-コンストラクタで指定する必要がある単一の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で不変性を強制する必要がある場合、ImmutableDate(d)を追加します。私は頭が良く、デリゲートを委任しなかったことを確認できたかもしれませんが、あなたはそのアイデアを得ます。これにより、クライアントが変更可能なものにキャストしようとする問題を回避できます。これは、JDKがCollections.unmodifiableMap()などで行うのとよく似ています(ただし、これらの場合、ミューテーション関数はまだ実装する必要があり、実行時例外をスローするようにコーディングされています。ミューテーター)。
それでも退屈な定型コードですが、Eclipseのような優れたIDEをマウスを数回クリックするだけで自動生成できるようなものです。
これが多くのドメインオブジェクトに対して実行することになった場合、動的プロキシまたはAOPの使用を検討することをお勧めします。オブジェクトのプロキシを構築し、すべてのgetメソッドを委任し、必要に応じてsetメソッドをトラップまたは無視するのは比較的簡単です。
他のヒント
一部のコメント(必ずしも問題ではない):
- Dateクラス自体は可変であるため、不変を保護するために正しくコピーしていますが、個人的にはコンストラクターでlongに変換し、ゲッターで新しいDate(longValue)を返します。
- getWhateverInstance()メソッドはどちらもキャストを必要とするDateBeanを返しますが、代わりに特定の型を返すようにインターフェースを変更することをお勧めします。
- 必要に応じて、1つの可変クラスと1つの不変クラスの2つのクラスを共有し、共通の(つまり、取得のみ)インターフェースを共有する傾向があることをすべて述べました。前後に多くの変換があると思われる場合は、両方のクラスにコピーコンストラクターを追加します。
- 不変クラスを使用してフィールドを final として宣言し、コンパイラに不変性を強制するようにします。
e.g。
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();
}
}
インターフェイスとキャストを使用して、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