かとじゅんの技術日誌

技術の話をするところ

Value Objects と Immutable

おつかれさまです。そろそろ、プログラミングに関するエントリも書かなければw

DDDの勉強を開始するにあたって、一番最初にEntitiesとValue Objectsに出会う。
今回は、まず先にValue Objectsと関連が深いImmutableについて、考えてみよう。なぜ、Value ObjectsかというとOODの基礎をなすからだ。基礎が弱いとその上の建造物ももろいものとなってしまう。だからValue Objectsがまず先。なーんだ、ただのJavaBeansなんでしょ、と思うと痛い目にあうよw

値を表すのがValue Objects。説明することが目的のオブジェクトである。ここに説明されているとおり。

● Value Objects(値オブジェクト)パターン

エンティティとは逆に、たとえば「色」や「量」のように、その属性だけが重要で、アイデンティティを考えることに意味のないオブジェクトもある。そうしたオブジェクトは、値オブジェクトに分類する。値オブジェクトとは、事物の性質を表現するものである。値オブジェクトは状態を変更できないもの(immutable)として扱う。エンティティの複雑さに専念できるように、値オブジェクトはシンプルな設計に保つようにする。

(この値オブジェクトは、PofEAAのValue Objectパターンと同じものを表している)

Entity が識別情報で、Value Object が、説明情報という感じが、なんとなくわかりますか?

VALUE OBJECTS

Value Object

たとえば、身近なところでJDBCのデータソースをValue Objectsで表現してみましょう。

public class DataSource {

    public DataSource(String driverClassName, String url, 
    String userName, String userPassword){
        this.driverClassName = driverClassName;
        this.url = url;
        this.userName = userName;
        this.userPassword = userPassword;
    }

    private String driverClassName;
    private String url;
    private String userName;
    private String userPassword;

    // setter, getter 省略

    // Value Objectの場合は、equalsとhashCodeを一般契約に基づいて実装しなければなりません。

}

利用例としては以下。

DataSource ds = new DataSource("org.postgresql.Driver", 
    "jdbc:postgresql:hoge", "hoge", "");
Class.forName(ds.getDriverClassName());
Connection con =
        DriverManager.getConnection(ds.getUrl(),
                                    ds.getUserName(),
                                    ds.getUserPassword());

ただし、setterを持っているのでインスタンスを作成してから、実際に値として利用されるまでに変更される可能性がある。これは意図しないところで状態が変更される可能性があるということだ。
DDDでは、Value ObjectsはできるだけImmutableにすることが推奨されている。つまり、setterなどの状態変更用メソッドを削って変更できない不変オブジェクトにすることを推奨している。

Immutableにするには

そのImmutableについては、Effective Java(第二版)の「項目15 可変性を最小限にする」で以下のような言及がある。

  1. オブジェクトの状態を変更するメソッドを持たないこと
  2. final classであること。拡張させない。
  3. すべてのフィールドがfinalであること。
  4. すべてのフィールドをprivateにすること。
  5. 可変オブジェクトに対する独占的アクセスを保証すること。

1から3番目については同意。(2番目については、コンストラクタをパッケージプラベートにして、自パッケージ内だけ拡張を許す柔軟な方法もある)
4番目については、public finalは要件によっては採用する場合がある。
5番目はちょっとわかりにくい。たとえば、先ほどのDataSourceをListなどで複数持つValue Objectの場合だ。
つまり、このListは可変オブジェクトということになる。

public class DataSources {

    private final List<DataSource> dataSources;

    public DataSources(List<DataSource> dataSources){
        this.dataSources = dataSources;
    }

    public List<DataSource> getDataSources(){
        return this.dataSources;
    }

}

と、当然、DataSourceにはsetterがないが、Listは可変オブジェクトだ。
Listのインスタンスをgetterでそのまま返してしまうと、以下のようにオブジェクトの状態を変更できてしまう。

DataSources dataSources = new DataSources( dsList );
// オブジェクトの状態を変更できてしまう>< つまり独占的アクセスが保証されていない。
dataSources.getDataSources().add( ... );

これを回避するには、以下のような方法がある。
一つ目は、防御的コピー。この場合、Clonableを実装しているならcloneメソッドか、コピーコンストラクタやファクトリメソッドで同じ値を保持した新たなインスタンスを返す。

public class DataSources {
    private final List<DataSource> dataSources;

    public DataSources(List<DataSource> dataSources){
        // ここでも防御的コピーが必要。詳しくはコメント欄をw
        this.dataSources = (List<DataSource>)dataSources.clone();
    }

    public List<DataSource> getDataSources(){
        return (List<DataSource>)dataSources.clone();
    }
}

二つ目は、リードオンリー用のインターフェイスを返す。この例ではIterableを返しているので、要素を取り出すことはできるが、当然 addメソッドなどで状態を変更することはできない。

public class DataSources {
    private final List<DataSource> dataSources;

    public DataSources(List<DataSource> dataSources){
        this.dataSources = (List<DataSource>)dataSources.clone();
    }

    public Iterable<DataSource> getDataSources(){
        return dataSources;
    }
}

効用について

まとめると、Value Objectは、オブジェクトの状態がnewされてgcされるまで一つしかない。本当にシンプルだ。さらに、状態の変更が伴わないので複数のスレッドからのアクセスにも問題ない。つまり、同期化の必要がない。これらの理由で制限なく共有が可能。メリットが多い。これは設計におけるベストプラクティスといっても過言ではないでしょう。

クラスを作るときに安易にsetterを書いてしまいそうになるが、「本当にsetterが必要なの?」と自問自答したほうがいい。Effective Javaでは、「可変にすべきかなり正当な理由がない限り、クラスは不変であるべき」と説いている。さらにいうと、可変としなければならない場合でもその範囲は最小であるべきだ。

あわせて読みたい

Domain-Driven Design: Tackling Complexity in the Heart of Software

Domain-Driven Design: Tackling Complexity in the Heart of Software


Effective Java 第2版 (The Java Series)

Effective Java 第2版 (The Java Series)