プレゼンテーション・チューザー

特定のドメインオブジェクトに適した画面を選択する

これは、私が2000年代半ばに行っていたエンタープライズアプリケーションアーキテクチャのさらなる開発の一環です。残念ながら、それ以降他のことに気を取られてしまい、さらに取り組む時間も、近い将来に見込むこともできません。そのため、この資料はほぼドラフトのままであり、再度取り組む時間ができるまで修正や更新は行いません。

画面間のナビゲーションがプレゼンテーションによって指示される場合、プレゼンテーションは表示する画面のシーケンスを直接エンコードできます。これにより、プレゼンテーションは適切なイベントが発生するたびに、開く必要のある画面を常に把握しています。ただし、表示される画面がドメイン内の情報に依存する場合があります。プレゼンテーションは、どの画面を表示するかを知らず、代わりにドメインオブジェクトを何らかのウィンドウに表示する必要があることを知っています。ドメインオブジェクトは分離されたプレゼンテーションに違反するため、画面を選択できません。

プレゼンテーション・チューザーは、特定のドメインオブジェクトに使用する画面を解決します。クライアントは、特定のドメインオブジェクトを表示するために新しい画面を開く必要があることを知っています。クライアントはプレゼンテーション・チューザーに、このドメインオブジェクトに使用する画面を尋ね、返されたウィンドウを開きます。

仕組み

最も単純な形式では、プレゼンテーション・チューザーは、ドメインオブジェクトの型を画面の型でインデックス付けする辞書ルックアップと考えることができます。

Order => OrderWindow
Customer => CustomerWindow
PriorityCustomer => PriorityCustomerWindow
    

この単純なモードでは、適切な画面のルックアップはaPresentationChooser[domainObject.type]のようになります。

この単純なアイデアはプレゼンテーション・チューザーの本質ですが、物事が少し複雑になる可能性のある一般的な領域がいくつかあります。これらのほとんどは、ほとんどのルックアップがドメインオブジェクトの型に完全に基づいているという事実から生じていますが、すべてがそうであるとは限りません。その結果、プレゼンテーション・チューザーの型ルックアップの性質をクライアントに公開する価値がないことが多いため、クライアントにドメインオブジェクトの型を渡すように求めるのではなく、代わりにオブジェクト自体を渡します。これは、オブジェクトはクライアントに自身で合理的に実行できることを要求すべきではないという一般的な原則に従います。また、プレゼンテーション・チューザーが必要に応じて、より高度なルックアップルールを提供することもできます。

プレゼンテーション・チューザーは通常、サービスです。初期化中に、構成モジュールは、画面とドメインオブジェクトの相互マッピングに関する詳細をプレゼンテーション・チューザーで初期化します。次に、通常の実行中に、画面はプレゼンテーション・チューザーを使用して画面を検索します。

いつ使用するか

同じナビゲーションフローに応答して、異なる画面クラス(通常はウィンドウですが、フォーム内の埋め込みパネルの場合もあります)を使用する必要がある場合は、プレゼンテーション・チューザーが必要です。以下で使用する簡単な例は、単一のリストに異なるタイプのオブジェクトがあり、タイプごとに異なる画面が必要な場合です。

多くの場合、この種の状況を処理する最良の方法は、同じタイプの画面を使用し、その画面上の非表示および無効にされたコントロールを使用して、不適切なデータへのアクセスをブロックすることです。タイプ間のバリエーションが小さく、インターフェイスがぎくしゃくすることなく同様の画面を使用できる場合は、これでうまく機能します。画面がロードされると、ドメインオブジェクトを調べて、有効にして表示するコントロールを決定します。

ただし、同じ可変画面を使用すると、基になるドメインオブジェクトのカスタム表示のオプションが少なくなります。場合によっては、類似点によってユーザーが混乱する可能性があります。このような場合は、別の画面を完全に使用する方が適切であり、どの画面を使用するかを決定するのにプレゼンテーション・チューザーが役立ちます。

ここではドメインデータに応じて画面を変化させることに重点を置いていますが、バリエーションには、ユーザー、ユーザーの所属、同じアプリケーションの複数のプロバイダー、またはインタラクションの状態の異なる側面など、全体的なカスタマイズ要素を含めることもできます。これらのカスタマイズはすべて、異なる画面クラスを動的に使用することにつながる可能性があり、それによって、使用する実際の画面クラスを選択する際のインダイレクションを示唆します。このような場合、プレゼンテーション・チューザーは、そのインダイレクションを提供するのに適した方法です。

プレゼンテーション・チューザーアプリケーションコントローラーはどちらも、画面クラスの選択とトリガーナビゲーションを分離します。プレゼンテーション・チューザーは、表示されるドメインオブジェクトが主なバリエーションである場合に最適であり、アプリケーションコントローラーは、アプリケーションの状態が主なバリエーションである場合に適しています。両方が変化する場合は、パターンを組み合わせるのが理にかなっています。

例:単純なルックアップ(C#)

プレゼンテーション・チューザーの最も簡単な例は、ドメインオブジェクトの型のみに基づいた単純なルックアップ機能を備えたものです。音楽録音に関する情報を表示するアプリケーションを考えてみましょう。録音はリストに表示され、どれでも編集できます。ただし、基になる録音は、クラシック録音であるかポピュラー録音であるかに応じて、異なる方法で編集する必要があります。

図1:録音を選択するためのウィンドウ。

これに対応するために、編集ボタンをクリックするためのイベントハンドラーがあります

private void btnEdit_Click(object sender, EventArgs e) {
  edit(Recordings[lstTitles.SelectedIndex]);
}
private void edit(IRecording recording) {
  Chooser.ShowDialog(recording);
}

このパターンの要点は、選択した録音がクラシック録音であるかポピュラー録音であるかに応じて、選択した録音を編集するためのダイアログを表示するようにプレゼンテーション・チューザーに求める編集メソッドです。

この場合、プレゼンテーション・チューザーは実際には単なる辞書ルックアップです。初期化中に、ドメイン型と対応する使用するウィンドウの詳細をプレゼンテーション・チューザーにロードします。

class PresentationChooser...

  protected IDictionary presenters = new Hashtable();
  public virtual void RegisterPresenter(Type domainType, Type presentation) {
    presenters[domainType] = presentation;
  }

この初期化では、ドメイン型とプレゼンテーション型を辞書に単純に格納します。ルックアッププロセスはもう少し複雑です。後でクラシック録音のサブタイプを追加した場合、そのサブタイプの画面を登録していない限り、クラシック録音で編集したいと考えます。

class PresentationChooser...

  public RecordingForm ShowDialog (Object model) {
    Object[] args = {model};
    RecordingForm dialog = (RecordingForm) Activator.CreateInstance(this[model], args);
    dialog.ShowDialog();
    return dialog;
  }
  public virtual Type this [Object obj] {
    get {
      Type result = lookupPresenter(obj.GetType());
      if (result == null) 
        MessageBox.Show("Unable to show form", "Error", MessageBoxButtons.OK,  MessageBoxIcon.Error);
      return result;
    }
  }
  private Type lookupPresenter(Type arg) 
  {
    Type result = (Type)presenters[arg];
    return (null == result) ? lookupPresenter(arg.BaseType) : result;
  }

登録された画面が見つからない場合は、エラーメッセージダイアログを表示するようにこれを記述しました。画面を登録するときに階層の最上位に画面を提供することで、これを表示する必要がないようにできます。

presentationChooser.RegisterPresenter(typeof(ClassicalRecording), typeof(FrmClassicalRecording));
presentationChooser.RegisterPresenter(typeof(PopularRecording), typeof (FrmPopularRecording));
presentationChooser.RegisterPresenter(typeof(Object), typeof (FrmNullPresentation));

例:条件付き(C#)

ここで、上記の単純な例を取り上げ、より動的なチューザーの動作を許可することで、少し洗練されたスピンを与えます。このシナリオでは、ほとんどのクラシック録音は通常のクラシック表示フォームで表示したいのですが、モーツァルトが作曲したものについては特別な表示フォームを使用したいと考えています。(なぜそうしたいのかはわかりませんが、火曜日に説得力のある例を思いつくのはあまり得意ではありません。)

プレゼンテーション・チューザーへのインターフェイスの多くは、より単純なバージョンと同じです。録音のダイアログを表示するコードは、依然としてChooser.showDialog(aRecording)だけです。ただし、プレゼンテーション・チューザーの実装はもう少し複雑です。

引き続き、型でインデックス付けされた辞書を使用して情報を格納しています。ただし、今回は、ドメインオブジェクトごとに複数の画面を使用したいと考えています。クラスを使用して各選択をキャプチャできます。

class DynamicPresentationChooser…

  Type _registeredType;
  Type _presentation;
  ConditionDelegate _condition;
  public delegate bool ConditionDelegate(Object recording); 
  public PresentationChoice(Type registeredType, Type presentation, ConditionDelegate condition)  {
    this._registeredType = registeredType;
    this._presentation = presentation;
    this._condition = condition;
  }

ここでは、C#デリゲートを使用して、クライアントがドメインオブジェクトに対して条件を評価するためのブール関数を渡せるようにしています。新しいルックアップ機能は、リスト内のすべての選択肢を順番にチェックし、指定されたドメインオブジェクトで条件デリゲートがtrueと評価される最初の選択肢を返します。

class PresentationChoice...

  public override Type this[Object obj] {
    get {
      IList list= presenterList(obj.GetType());
      return (null == list) ? typeof(FrmNullPresentation): chooseFromList(list, obj);
    }
  }
  private IList presenterList(Type type) {
    IList result = (IList) presenters[type];
    if (null != result)
      return result;
    else if (typeof (object) != type)
      return presenterList(type.BaseType);
    else
      return new ArrayList();
  }
  private static Type chooseFromList(IList list, object domainObject) {
    foreach (PresentationChoice choice in list) 
      if (choice.Matches(domainObject)) return choice.Presentation; 
    return typeof(FrmNullPresentation);
  }

何も一致しない場合は、Nullオブジェクトを返します。

画面を登録するために、上記の単純なケースとの互換性のあるインターフェイスを維持したいと考えました。そのため、ドメイン型と画面型のみを使用した登録を許可することで、いくつかの便利なメソッドを使用して以前と同じように機能させることができます。

class DynamicPresentationChooser...

  public override void RegisterPresenter(Type domainType, Type presentation) {
    presenters[domainType] = new ArrayList();
    presenterList(domainType).Add (new PresentationChoice (domainType, presentation));
  }

class PresentationChoice...

  public PresentationChoice(Type domainType, Type presentation) : 
    this (domainType, presentation, null){
    _condition = new ConditionDelegate(TrueConditionDelegate);
  }
  public static bool TrueConditionDelegate(Object ignored) {return true;}

このようにして、動的チューザーを、それ以外の場合は単純なチューザーを使用するすべての状況で使用できます。

次に、追加の登録メソッドを使用して、動的なオプションを挿入します。これは、デフォルトの選択肢が既に存在する場合にのみ実行できるように設定しました。これにより、構成インターフェイスは少しぎこちなくなりますが、これにより、構成で問題が発生した場合に結果がすぐに失敗します。

class DynamicPresentationChooser...

  public void RegisterAdditionalPresenter(Type domainType, Type presentation, PresentationChoice.ConditionDelegate condition) {
    Debug.Assert(
      null != presenterList(domainType), 
      String.Format("Must register default choice for {0} first", domainType));
    presenterList(domainType).Insert(0, new PresentationChoice(domainType, presentation, condition));
  }

次に、次のようにクラスを登録できます。

presentationChooser.RegisterPresenter(typeof(ClassicalRecording), typeof(FrmClassicalRecording));
presentationChooser.RegisterPresenter(typeof(PopularRecording), typeof (FrmPopularRecording));
chooser.RegisterAdditionalPresenter(
  typeof(ClassicalRecording), 
  typeof(FrmMozartRecording), 
  new PresentationChoice.ConditionDelegate(MozartCondition));