画面とドメインオブジェクトの設計を連動させる

関心事を分けて整理する

画面アプリケーションのコードが複雑になる要因は次の2つ。

  • 画面そのものが複雑
  • 画面の表示ロジックと業務ロジックが分離できていない

次の方針で関心事を整理すれば、画面アプリケーションの複雑さを改善し、変更を楽で安全にできる。

  • さまざまな表示項目やボタンを詰め込んだ何でもできる汎用画面ではなく、用途ごとのシンプルな画面に分ける(タスクベースのユーザーインタフェース)
  • 画面周りのロジックから業務ロジックを分離する

利用者の関心事に焦点を当てると、画面デザインとドメインオブジェクトの設計は連動する。この連動がうまく行けば行くほど、ソフトウェアの変更が楽で安全になる。

複雑な画面は異なる関心事が混ざっている

たとえば、注文画面は以下のような関心事の組み合わせである。

  • 注文者を特定する情報(氏名や顧客番号)
  • 注文した商品と個数
  • 決済方法
  • 配送手段と配送先
  • 連絡方法

ここで注文に必要なデータをすべて持つOrderオブジェクトと、注文データ内容を確認するすべてのロジックを持つOrderService#register()などによって注文機能を実装することが考えられるが、こうした肥大化したクラスやメソッドは変更が大変である。

小さな関心事に分けて考える

注文に関するドメインオブジェクトと注文登録に関するメソッドが次のように分けて考えることができる。
実際の注文登録はこれらを組み合わせてOrderクラスとregisterメソッドを作ればよい。

対象 ドメインオブジェクト 登録メソッド
注文者 Customer register(Customer $customer)
注文内容 Items register(Items $items)
決済方法 PaymentMethod register(PaymentMethod $method)
配送手段 DeliverySpecification register(DeliverySpecification $specification)
連絡先 ContactTo register(ContactTo $contact_to)
注文の確定 Order submit(Order $order)

このようにドメインオブジェクトと登録メソッドを小さく分けることにより変更箇所の特定や変更の影響の範囲を狭い範囲に閉じ込めやすくなる。

画面とドメインオブジェクトを連動させる

画面はドメインオブジェクトを視覚的に表現したもの。その表現方法としては、

  • ドメインオブジェクトをそのまま画面の表示にも使う
  • 画面用のオブジェクトを別途用意する
    • 画面表示用のデータを保持
    • データを表示用に加工するロジックをまとめる

の2通りがあるが、画面の関心事とドメインオブジェクトの関心事は一致していることが基本なので、ドメインオブジェクトをそのまま使うことを考えるべき。ただし、

  • 画面はさまざまな関心事が複合していて、ドメインオブジェクトの構造や粒度と整合しにくい時がある
  • 画面の表示だけに関わる判断や加工のロジックをドメインオブジェクトに持ち込みたくない

といったことが実際には起こる。そうした食い違いが起きているのであれば、ドメインオブジェクト側の設計を改善すべきかもしれない。

ドメインオブジェクトに書くべきロジック

ビューに書くべきこととドメインオブジェクトに書くべきことを整理する考え方は次の3つ。

  • 論理的な情報構造はドメインオブジェクトで表現する
  • 場合ごとの表示の違いをドメインオブジェクトで出し分ける
  • HTMLのclass属性をドメインオブジェクトから出力する
論理的な情報構造をドメインオブジェクトで表現する

ビューの記述は次の2つに分かれる。

  • 物理的なビュー:HTMLタグや改行コードを使って視覚的に表現する
  • 論理的なビュー:「段落がいくつあるか」「最も長い段落の文字数のカウント」などの「構造」を表現する

このうち、ドメインオブジェクトでは論理的なビューを実現するためのロジックを持つべきであり、逆にHTMLタグを使ったり、段落の先頭を全角一文字で字下げするといった物理的な手段を持ってはいけない

場合ごとの表示の違いをドメインオブジェクトで出し分ける

画面表示でif文を使っている場合はその条件判断をドメインオブジェクトに移動できないか検討する。

<?php

class Items
{
    private $items;

    public function found()
    {
        if(empty($items)) {
            return '見つかりませんでした';
        }
        return "{count($items))件見つかりました";
    }
}

次のようにドメインオブジェクトで実装することで、ビュー側にif文の条件判断が不要になる。条件分岐を増やすときもドメインオブジェクトだけが変更の対象になる。
情報の文字列表現は利用者の関心事そのものなので、ドメインオブジェクトに記述することはむしろ自然。

HTMLのclass属性をドメインオブジェクトから出力する

ドメインオブジェクト側にclass属性を返すメソッドを用意すれば、画面の表示ロジックからif文をなくすことができる。

<p class="<?php $mail->readStatus(); ?>">

画面(視覚表現)とソフトウェア(論理構造)を関係付ける

画面とオブジェクトの項目や順番に不一致がある場合、ドメインオブジェクトが利用者の関心事を適切に表現できていない可能性が高い。つまり変更が難しくなる。

項目の並び順とドメインオブジェクトのフィールドの並び順

書籍の一覧画面で次のように項目が並んでおり、

  • 書名
  • 価格
  • 発行年月日
  • 著者
  • 本の種類
<?php

class Book
{
    private int $id;
    private BookNumber $number;
    private Title $title;
    private Author $author;
    private Publisher $publisher;
    private BookType $type;
    private Price $unit_price;
    private LocalDate $published;
    private LocalDate $registered;
}

このようなドメインオブジェクトが定義されているとすると、このクラスのフィールドと一覧画面の項目は内容も順番も一致していない。利用者の関心事よりもデータベースのテーブル構造に引きずられた内容になっている。
一覧画面の関心事をそのまま表現すれば、ドメインオブジェクトは次のようになる。

<?php

class BookSummery
{
    private Title $title;
    private Price $unit_price;
    private LocalDate $published;
    private Author $author;
    private BookType $type;
}

これならば画面との対応が明確なため、変更の要求への対応箇所が直感的にわかる。

画面項目のグルーピング

画面デザインの4原則

  • 近接:関連のある情報は近づける、関係のない情報は離す
  • 整列:同じ意味のものは同じラインに揃える、意味が異なれば異なるラインに揃える
  • 対比:意味の重みの違いを文字の大きさや色の違いで区別する
  • 反復:同じ意味は同じパターンで視覚化する
近接

近接したグループはドメインオブジェクトの単位と一致するはず。関連のある情報ごとにドメインオブジェクトを作成し、関係のない情報は別のオブジェクトに分ける。デザイン上空白などで分離してある複数の情報が1つのドメインオブジェクトにまとまっているのは利用者の関心事の理解として問題があるといえる。

整列

特にインデントはグルーピングの良い手がかりになる。画面上でインデントされているということは意味として異なるということ。インデントされた部分を別のオブジェクトとしてくくりだすことで関心事の構造を適切に表現できるはず。

対比

小さなフォントや薄めのグレーなど画面上で弱く表現している情報は別のクラスを作ってその中に隠蔽することを検討する。

反復

反復で表現された情報は同じ型のオブジェクトとして表現する。1つのクラスの別々のオブジェクトということもあるし、インターフェース宣言で同一の型として扱っている複数のクラスのオブジェクトということもありうる。