Teeda で Service を使う際の方針(その3)

[seasar]Teeda で Service を使う際の方針(その3)です。

たぶん、迷っているってことは、どれも決定的ではないということなはず。6/29にも説明するつもりですが、私の考えは次のとおりです。
ひがやすを blog - [Seasar]Service を使う際の方針

ひがさんがトラックバックで私の疑問に対するblogエントリーを書いてくれました。
なるほど、考え抜かれたシンプルさを感じます。


一見、なんてことの無いように思えるが、次の重複したロジックを親クラスに持っていく発想が特に気に入りました。

同一ユースケース(サブアプリケーション)の複数の画面で使われるロジックは、各画面の共通の親Pageクラスに持たせる。

重複が見られた時に、どこにコードを記述すべきか分かりにくかったり、面倒だったりすると、確信犯的にコピ&ペーストする人が実際には多い。なので、重複対策は、うるさいほど分かりやすくて、簡単にできることが問われる。「できるできない」ではなく、「やり易いやり難い」という視点で捉えることが重要。

これまで、重複がみられると、別種類のクラスに記述することが多かった。別種類のクラスに処理を移す場合は、フレームワークデザインパターン、もしくは、プロジェクト独自のお作法などをよく理解していないと、ついつい尻込みしてしまう。知っている人からすればなんてこと無いことでも、知らなければ障壁はとても高くなる。(特に、プロジェクトに途中参加したプログラマーは辛い。)でも、親クラスならば、ほぼ同じ作法で簡単に書けるので、心理的な抵抗が少ない利点がある。

GUI関連のプログラミングで、継承よりも、委譲の方が優れているということが一般的に知れ渡ったあたりから、極端に「継承=悪」のようなイメージが付きまとい、継承をさける傾向があったと思う。このような風潮があるなかで、適材適所を見極めて、共通部分を継承元の親クラスに置くという原点回帰というか教科書的なやり方を実務の身近な場所で使うのは意義深いと思う。(考えすぎかw)


今後は、業務アプリのプログラミングにおいて、継承の良さが見直される動きが流行るのではないか。この動きは、「不必要なインターフェース」、「不必要なアクセッサ」、「不必要なレイヤー」を排除する動きと合流して、原点回帰のようだが進化したプログラミングスタイルが確立する流れを感じます。

物事は、「チープ」 → 「コンプレックス」 → 「シンプル」の3つの段階を経て成熟するという話を聞いたことがあるのですが、今まさに、シンプルに向かっている段階ですね。


誤解が無いように注意がいる箇所として、重複部分の全ては継承と述べているのではないです。次のように、委譲モデルも併用しています。

複数のユースケースで使われるロジックは、共通のユースケースを抽出し、共通のユースケース名Serviceクラスに持たせる。

なんとなく、このような設計にした方が良いとは思いますが、なぜ、上記のような局面において、継承モデルではなく、委譲モデルを採用したのか理由が気になるところです。

なぜなら、最近、アーキテクチャのことを考えていて、結論だけでなく、そこに至った過程を理解してドキュメント化することの大切さを痛感しているからです。


あとは、決して今のものがベストと思い込んで固執することなく、進化・改善し続けるもんだという視点も大切ですね。


最後に、私のもやもや感を晴らすblogエントリーを書いてくれた ひがさんに 改めて感謝!


以下は、 [Seasar]Teeda で Service を使う際の方針 で提示したサンプルコードをひがさんのblogエントリーで書かれた方針に照らし合わせて書き直したものです。

class HogeRegisterPage extends AbstractHogePage {
  public String doRegister() {
    hogeDao.insert(hogeDxo.convert(this));
    return null;
  }
}

class HogeSearchPage extends AbstractHogePage {

  List<HogeDto> hogeItems;
  
  public String prerender() {    
    List<Hoge> hoges = findHoges();
    hogeItems = hogeDxo.convert(hoges);
    return null;
  }
  
  protected List<Hoge> findHoges() {
    HogeConditionDto hogeConditionDto = new HogeConditionDto();
    ...    // hogeConditionDto.setFuga(fuga) などの要領で検索条件をセット
    return hogeDao.findHoges(hogeConditionDto);
  }
}

// 同一ユースケース(サブアプリケーション)の複数ページで使われる
// ロジックはこのクラスに持たせる
abstract class AbstractHogePage {
  @Binding
  public HogeDao hogeDao;
  
  @Binding
  public HogeDxo hogeDxo;
}