VO・DTO・JavaBeansの違い

ずーっと悩んでいましたが、やっとしっくり来ました。

表にまとめてみます。

名称 不変/可変 備考
VO

(ValueObject)

不変

粒度が細かい一つのデータ値を示す事が多い。

Serializable・Comparableを実装する。

セッタを定義する時は新規インスタンスを返す。

例:String、Integer、BigDecimal

DTO

(DataTransferObject)

可変

データの論理的な構造を示し、ゲッタ・セッタを定義する。

セッタには引数検証を実装する時もある。

セッタ・ゲッタ無しでpublicフィールドの場合も。

JavaBeans 可変

DTOにゲッタ・セッタ以外のインスタンスメソッドを

プラスしたもの。

(例えばList内Integerの合計値を返すメソッドとか)

この表から考えると、VO、DTO、JavaBeansはis-a関係にできそう・・・

コード例

例によってJava1.4です。簡単な実装なのでコメントは略。

クラス分けとか命名とかがおかしい、ってのはスルーの方向でw

VO
/**
* 商品を表すクラス。
*
* @author zetta1985
*/
public class ItemVO implements Serializable, Comparable{
    static final long serialVersionUID = 4576493878539834332L;
    private String code;
    private String name;
    private BigDecimal price;
    public ItemVO(String code, String name, BigDecimal price) {
        super();
        if (code == null || code.equals(""))
            throw new IllegalArgumentException("code is invalid value");
        if (name == null || name.equals(""))
            throw new IllegalArgumentException("name is invalid value");
        if (price == null || price.equals(BigDecimal.ZERO))
            throw new IllegalArgumentException("price is invalid value");
        this.code = code;
        this.name = name;
        this.price = price;
    }
    public boolean equals(Object obj) {
        return this.code.equals(((ItemVO)obj).code);
    }
    public int compareTo(Object obj) {
        return this.code.compareTo(((ItemVO)obj).code);
    }
    // ============================================================= getter
    public String getCode() {
        return code;
    }
    public String getName() {
        return name;
    }
    public BigDecimal getPrice() {
        return price;
    }
}



/**
* 顧客を表すクラス。
*
* @author zetta1985
*/
public class CustomerVO implements Serializable, Comparable{
    static final long serialVersionUID = 5473297489560237894L;
    private String code;
    private String name;
    private String phoneNumber;
    public CustomerVO(String code, String name, String phoneNumber) {
        super();
        if (code == null || code.equals(""))
        throw new IllegalArgumentException("code is invalid value");
        if (name == null || name.equals(""))
        throw new IllegalArgumentException("name is invalid value");
        if (phoneNumber == null || phoneNumber.equals(""))
        throw new IllegalArgumentException("phoneNumber is invalid value");
        this.code = code;
        this.name = name;
        this.phoneNumber = phoneNumber;
    }
    public boolean equals(Object obj) {
        return this.code.equals(((CustomerVO)obj).code);
    }
    public int compareTo(Object obj) {
        return this.code.compareTo(((CustomerVO)obj).code);
    }
    // ============================================================= getter
    public String getCode() {
        return code;
    }
    public String getName() {
        return name;
    }
    public String getPhoneNumber() {
        return phoneNumber;
    }
}
DTO
/**
* 注文された商品を表すクラス。
*
* @author zetta1985
*/
public class OrderedItemDTO implements Serializable{
    static final long serialVersionUID = 2197438978497897435L;
    private int count;
    private ItemVO item;
    public OrderedItemDTO(int count, ItemVO item) {
        if (count < 0) throw new IllegalArgumentException("count is invalid value.");
        if (item == null) throw new NullPointerException("item is null.");
        this.count = count;
        this.item = item;
    }
    // ============================================================= getter
    public int getCount() {
        return count;
    }
    public ItemVO getItem() {
        return item;
    }
    // ============================================================= setter
    public void setCount(int count) {
        if (count < 0) throw new IllegalArgumentException("count is invalid value.");
        this.count = count;
    }
}
JavaBean
/**
* 一件の注文を表すクラス。
*
* @author zetta1985
*/
public class OrderBean implements Serializable {
    static final long serialVersionUID = 56479378943728967L;
    private CustomerVO customer;
    private List items;
    public OrderBean(OrderVO order) {
        super();
        this.customer = order.getCustomer();
        this.items = order.getOrderedItems();
        Collections.sort(items);
    }
    public BigDecimal getSumPrice(){
        BigDecimal sum = BigDecimal.ZERO;
        for (Iterator iter = getOrderedItems().iterator(); iter.hasNext();) {
        OrderedItemDTO element = (OrderedItemDTO) iter.next();
        sum = sum.add(element.getItem().getPrice().multiply(new BigDecimal(element.getCount())));
        }
        return sum;
    }
    public boolean addOrderedItem(OrderedItemDTO dto) {
        return items.add(dto);
    }
    public void clearOrderedItems() {
        items.clear();
    }
    public Object removeOrderedItem(int index) {
        return items.remove(index);
    }
    // ============================================================= getter
    public CustomerVO getCustomer() {
        return customer;
    }
    public Object getOrderedItem(int index) {
        return items.get(index);
    }
    public List getOrderedItems() {
        return Collections.unmodifiableList(items);
    }
    // ============================================================= setter
    public void setCustomer(CustomerVO customer) {
        this.customer = customer;
    }
    public Object setOrderedItem(int index, OrderedItemDTO dto) {
        return items.set(index, dto);
    }
}

まとめ

VO・DTO・JavaBeansのどれを使うか、どういう実装にするかは要件によりけりですが、

ちゃんと意図した分け方をする事・統一されたクラス定義をする事が大事だと思います。

例えば、DTOのセッタに引数検証を実装するのか、とか

JavaBeansのインスタンスメソッドにビジネスロジックを実装するのか、とか。

例では通常のクラスですが、VOやDTOをインターフェイスや抽象クラスで定義するのもアリかな、と思います。

VOのゲッターをインターフェイスで定義して、VOを実装したDTOでセッターを含めて実装するなど。

そこら辺のコード例はStrutsで良い例ができたのでもう少ししたら書きたいと思います。

このエントリーをはてなブックマークに追加
comments powered by Disqus