ゆーたんのつぶやき

株式会社ノークリサーチにてIT関連のシニアアナリストとして活動しています。

データと振る舞いの分離



オブジェクト指向の特徴の一つに「データと振る舞いを一つにまとめる」
というものがあることは今更言うまでもありません。ですが、Hibernate
を利用する際に使われるDAOパターンなどはデータそのものと、それらを
挿入・更新・削除する振る舞いとが分離された形になっています。


オブジェクト指向の原則に従いデータと振る舞いはあくまで一つに
まとめるという考え方と、場合によっては両者が分離しても良いと
する考え方のどちらの立場を取るのかといったことについては熱い
議論が交わされているようです。


ちょっと前の話になりますが、arclampさんのBlogでは上記のそれぞれの
立場がPoEAAで言うところの「Transaction Script」と「Domain Model」
の考え方に置き換えられる形でディスカッションが交わされていました。


http://www.arclamp.jp/blog/archives/000552.html


大変勉強になるやりとりでしたが、自分なりに「データと振る舞いの分離」
についてここで考えてみたいと思います。


まずは「データと振る舞いを一つにまとめる」ことの有用性について考えて
みたいと思います。単体のオブジェクトにおいて、その振る舞いが明らかに
自身のオブジェクト内で完結するようなものは当然ながら分離すべきではあ
りません。オブジェクトが持っている属性に対する操作などはこれに該当し
ます。この点についてはおそらくは誰もが一致している考えだと思います。


難しいのは「Domain Model」の考え方でビジネスロジックを組む際、どの
オブジェクトにどの操作(責務)を持たせるか?ということです。 ロジック
の複雑度によっては「Transaction Script」で実装する (つまり、一つの
トランザクション処理を単位として、それに対応付けてビジネスロジック
実装していく、当然ながらコードの重複が発生する可能性が高くなる) 方が
楽な場合もあるかも知れません。それでは「Domain Model」のメリットって
何だろう?と考えたとき、ボクは『Strategyパターンなどを活用することで
ビジネスロジックを複雑にし、メンテナンス性を低下させる原因となる分岐
や選択を複数のオブジェクト間の関係に置き換えて実現することができる』
ことではないかと思っています。


具体例で考えてみたいと思います。
例えば、勤怠管理システムを開発したいとします。出退勤時刻に基づいた
勤務時間と給与の計算規則(何時から深夜残業と見なすか、休日出勤時の
手当はどうするか?)といったことは正社員かアルバイト社員かによって
変わって来ます。
「Transaction Script」で考えた場合は、給与情報テーブルへの操作が
ロジック実装の単位になりますので、ビジネスロジックの流れは


switch(社員分類){
  case: 正社員{
正社員の場合の給与情報テーブルへの挿入処理 --- ①
break;
}
  case: アルバイト社員{
アルバイト社員の場合の給与情報テーブルへの挿入処理 --- ②
break;
}
}
みたいな感じになります。(もちろん、もっとエレガントな実装方法も
あると思いますが)
①と②の中で共通するコードもあれば微妙に違うコードもあるでしょう。
もし勤怠ルールが変更になれば、①も②も全てに渡って修正が必要になる
可能性が高くなります。


これを「Domain Model」的に実装すると、登場するオブジェクトとして、
・社員オブジェクト(これ自体は抽象クラスで「正社員」「アルバイト社員」
 など継承されたものが実際には使われる)
・給与支払形態オブジェクト(これ自体は抽象クラスで「月給制」「時給制」
 など「年俸制」継承されたものが実際には使われる)
・勤怠ルールオブジェクト(「勤怠ルールインターフェース」を実装し、上記
 二つの組み合わせに応じた様々な実装が存在する)
といったものが候補に上がってくるかと思います。(社員オブジェクトと給与
支払いオブジェクトを一緒にするという考え方もありますが、「出向社員」
で「年俸制」みたいな組み合わせもあるので分けておきました)
こうすると、上記の給与情報テーブルへの挿入処理のところは

 public void createWageData(int empno, list report){
//社員番号を元に適切な社員オブジェクトの子クラスを返す
Employee emp = Employee.createInstance(empno);
  //該当社員の給与支払形態を取得
WagePattern pattern = emp.getWagePattern();
//勤怠ルールオブジェクトを生成
PaymentRule rule = PaymentRule.getInstance(pattern);
//給与情報テーブル上へのデータ生成
rule.cratePaymentData(emp, report);
 }
みたいな感じになるかと思います。emp,pattern,ruleはいずれも
抽象クラスやインターフェースで、実際に参照しているのはそれらを
継承したクラスということになります。こうやって操作(責務)を分割
して複数のクラスに委譲してやると、「Service Layer」に相当する
部分は上記のようにシンプルになってきますし、新たに「派遣社員
が追加されたり、「フルコミッション制」が追加されたり(そのため
には勤務状況を表すreportについても十分に抽象化しておく必要が
あります)といった場合でも該当するサブクラスを作成し、Factory
に登録しておくだけで他への影響を与えずに済みます。


「オブジェクトにデータと操作を持たせる」ということを先に考えると
難しくなってしまいますが(少なくともボクにとっては^^) 「変更時の
反映が最小限になるように複数のクラスに責務を委譲させ、それらの
クラス間の関係が自然になるよう操作(責務)を各クラスに割り当てる」
と考えた結果として「データと振る舞いが一つのクラスにまとまる」
という状況に自然となってくるのではないかと思っています。


では、Hibernateに代表されるO/Rマッピングで使われるDAOパターンは
どう考えるべきでしょうか?DAOパターンではTransferObjectに当たる
POJOにはデータアクセスのロジックは実装せず、それらはDAO側に実装
されます。


これについては「永続化は横断的関心事である」と捉えるとスッキリと
するのではないかと考えています。横断的関心事というと、通常はログ
書き込みやトランザクションなどAOPとしてPOJOに織り込む(Weaveする)
ものですが、永続化もその一つと考えられないでしょうか?そして通常
の織り込みでは何らかの方法でアスペクトPOJO内に組み込まれますが
DAOパターンの場合はアスペクトを外部のオブジェクトに委譲していると
考えるわけです。(個人的には永続化についても他のアスペクトと同様の
スタイルで実現させるパターンが近いうちに出てくるように思います)
横断的関心事を実現する方法としてAOPのように織り込む方法と、DAO
パターンのように外部に委譲する方法があるということになりますが、
これはオブジェクトが振る舞いを持つことの「変形」であると捉えれば
オブジェクト指向の原則を必ずしも逸脱したものではないと考えられる
のではないかと思っています。


最後にもう一つ、残っている課題があります。それはSOAのように単一の
データ(オブジェクト)が複数のシステムにまたがって利用されるケース
です。例えば、サプライチェーン上のある商品(製品)を考えてみます。
製造段階においてはどの工程まで進んだかというステータスを扱う属性
と振る舞いが必要ですし(get/set[ProductionStatus])、出荷時には
どの倉庫に保管されているかの設定(get/set[WarehouseInfo])、販売時
には価格を設定したりする操作(get/set[Price])といったように場面に
応じて様々な振る舞いが必要になってきます。
これは上記の横断的関心事とはちょっと違っていて、各システムにおける
そのオブジェクトの「役割(ロール)」に応じて振る舞いが決められている
といえる状態かと思います。この場合に全てのロールに対応した振る舞い
を持たせることは現実的ではないので、ロール毎に異なった種々の操作用
のオブジェクトを各システム上に用意しておいて、商品オブジェクトには
全システムに共通する振る舞いのみを持たせて、ロール毎の振る舞いは各
システム毎の操作用オブジェクトに委譲するのが現実的と思われます。


やたらと長くなってしまいましたが、以上まとめると....


【データと振る舞いをまとめた方が良い場合】


 ・自己のオブジェクト内で完結する属性の操作などの場合


  ・ビジネスロジックを複数のクラスにうまく委譲できて、
   「Domain Model」のメリットが生かせるシステムの場合


【データと振る舞いを分離した方が良い場合】


  ・注目している振る舞いが「横断的関心事」に対応する場合
   ※この場合はAOPによって織り込むか、DAOのように外部の
    クラスに委譲することが考えられるが、いずれの場合も
    オブジェクトが振る舞いを持つことの「変形」と捉える


  ・対象となるオブジェクトの持つべき振る舞いがシステムに
   おける「役割」によって変化する場合
   ※これもオブジェクトが持つべき振る舞いが状況によって
    変化し、その振る舞いを外部のクラスに委譲していると
    捉える


こうやって自分なりに整理してみると、「データと振る舞いの分離」
というのは実は分離してしまっているというよりはオブジェクトの
持つべき振る舞いの実現の仕方に「AOPの織り込み」やDAOのような
「外部への委譲」といった様々なバラエティが出てきたということ
なのかなぁ?と感じています。