give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

O/Rマッピングツールに対する誤解をときたい -実装編 Part4-

Part3を読む

ドメインモデルクラス

それではここからドメインモデルを構成する注文クラス、注文明細クラス、書籍クラス、和書クラス、洋書クラスを説明していきます。

注文クラス(BookOrder.cs)
using System;
using System.Collections.Generic;
using System.Text;

namespace Junichi.Ito.NHibernateBookStore
{
    // 1件の注文を表す
    [Serializable]
    public class BookOrder
    {
        private int id;

        private string customerName;

        private DateTime orderDate;

        private IList<BookOrderItem> items;

        // 注文ID
        public virtual int ID
        {
            get { return id; }
            set { id = value; }
        }

        // 注文者
        public virtual string CustomerName
        {
            get { return customerName; }
            set { customerName = value; }
        }

        // 注文日
        public virtual DateTime OrderDate
        {
            get { return orderDate; }
            set { orderDate = value; }
        }	

        // 注文明細
        public virtual IList<BookOrderItem> Items
        {
            get { return items; }
            set { items = value; }
        }
    }
}


このクラスは単純にデータを保持するだけのクラスです。


ただし、細かいところに注目するとプロパティがすべて「virtual」になっています。
実はNHibernateは実行時にProxyクラスを動的生成する関係から、メソッドやプロパティはすべてvirtualにするという制約が付きます。
この制約は他のドメインモデルクラスでも同様です。


また、UI側でASP.NETのSessionやViewStateに格納されることを想定して、Serializable属性を付けている点も全クラス共通です。

注文明細クラス(BookOrderItem.cs)
using System;
using System.Collections.Generic;
using System.Text;

namespace Junichi.Ito.NHibernateBookStore
{
    // 1件の注文明細を表す
    [Serializable]
    public class BookOrderItem
    {
        private int id;

        private DateTime shippingDate;

        private Book book;

        // 注文明細ID
        public virtual int ID
        {
            get { return id; }
            set { id = value; }
        }

        // 発送予定日
        public virtual DateTime ShippingDate
        {
            get { return shippingDate; }
            set { shippingDate = value; }
        }

        // 書籍
        public virtual Book Book
        {
            get { return book; }
            set { book = value; }
        }	
    }
}


こちらも単純にデータを保持するだけのクラスです。
特に説明する点はないと思います。

書籍クラス(Book.cs)
using System;

namespace Junichi.Ito.NHibernateBookStore
{
    // 書籍をあらわす
    [Serializable]
    public abstract class Book
    {
        private int id;

        private string bookName;

        private int stockCount;
        
        // 書籍ID
        public virtual int ID
        {
            get { return id; }
            set { id = value; }
        }

        // 書籍名
        public virtual string BookName
        {
            get { return bookName; }
            set { bookName = value; }
        }

        // 在庫数
        public virtual int StockCount
        {
            get { return stockCount; }
            set { stockCount = value; }
        }

        // 在庫はあるか?(あればtrue)
        public virtual bool HasStock
        {
            get { return this.StockCount > 0; }
        }
        
        // 発送を予約する(戻り値: 発送予定日)
        public virtual DateTime ReserveShipping()
        {
            // 発送予定日を計算する
            DateTime shippingDate = this.CalcShippingDate();

            // 在庫がまだ存在すれば1冊減らす
            if (this.HasStock)
            {
                this.stockCount--;
            }

            // 発送予定日を返す
            return shippingDate;
        }

        // 発送予定日を計算する(計算ロジックはサブクラスで実装)
        public abstract DateTime CalcShippingDate();

        // is演算子の代替手段
        public virtual bool Is(Type type)
        {
            // NHibernateでは実行時にProxyクラスが生成されるため、is演算子がうまく動作しない
            // そのため、このメソッドでインスタンスの型確認を行う
            return this.GetType() == type;
        }        
    }
}


さてここからが重要なクラスですね。
書籍クラスにはデータを保持する以外に独自のプロパティやメソッドを実装しています。
中でも特に重要なのは以下の部分です。

        // 発送を予約する(戻り値: 発送予定日)
        public virtual DateTime ReserveShipping()
        {
            // 発送予定日を計算する
            DateTime shippingDate = this.CalcShippingDate();

            // 在庫がまだ存在すれば1冊減らす
            if (this.HasStock)
            {
                this.stockCount--;
            }

            // 発送予定日を返す
            return shippingDate;
        }

        // 発送予定日を計算する(計算ロジックはサブクラスで実装)
        public abstract DateTime CalcShippingDate();


処理シーケンスのところでも説明したように、「発送を予約する」メソッドでは和書と洋書で発送予定日の算出ルールを切り替える必要があります。
親クラスである書籍クラスでは「発送を予約する」ための共通フローを実装しています。
ただし発送予定日の算出ロジックだけはここで定義せず、サブクラスである和書や洋書に移譲するために「発送予定日を計算する」メソッドを抽象メソッドとしています。


ちなみに本来であれば「CalcShippingDate」はpublicである必要はなくprotectedで良いのですが、protectedにするとNHibernateで実行時エラーが発生したのでpublicとしています。

和書クラス(JapaneseBook.cs)
using System;

namespace Junichi.Ito.NHibernateBookStore
{
    // 和書をあらわす
    [Serializable]
    public class JapaneseBook : Book
    {
        // 発送予定日を計算する
        public override DateTime CalcShippingDate()
        {
            // 在庫があれば当日、なければ5日後に発送する
            DateTime today = DateTime.Today;
            return this.HasStock ? today : today.AddDays(5);
        }
    }
}


先ほど説明した通り、和書クラスでは和書用の発送予定日計算ロジックを実装します。
ソースコメントにもあるように「在庫があれば当日、なければ5日後に発送する」というのがこのプログラムでのビジネスルールです。

洋書クラス(ForeignBook.cs)
using System;

namespace Junichi.Ito.NHibernateBookStore
{
    // 洋書をあらわす
    [Serializable]
    public class ForeignBook : Book
    {
        // 発送予定日を計算する
        public override DateTime CalcShippingDate()
        {
            // 在庫があれば3日後、なければ7日後に発送する
            DateTime today = DateTime.Today;
            return this.HasStock ? today.AddDays(3) : today.AddDays(7);
        }
    }
}


和書クラスと同様に、洋書用の発送予定日計算ロジックを実装します。
ソースコメントにもあるように「在庫があれば3日後、なければ7日後に発送する」というのがこのプログラムでのビジネスルールです。


ドメインモデルクラスの実装コードは以上です。
少し長くなってしまいましたが、書籍クラス、和書クラス、洋書クラスの関係が理解できれば問題ないと思います。


次回はFacadeクラスの実装を見ていきます。


Part5を読む