nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Visual Studio and Development Technologies。なお掲載内容は個人の見解であり、所属する企業を代表するものではありません。

アプリケーションオブジェクト ライフサイクルパターン

アプリケーションを構築する際に、登場するオブジェクトの生存期間は多岐にわたります。

  • ユーザーがインターフェースからアクションを起こしたときに、それに伴うインタラクションが完了するまでの間だけ生存するオブジェクト。
  • アプリケーション起動から終了までの間、常に共有されるオブジェクト。

などです。
私はその生存期間は幾つかのパターンに分類することができると考えています。
そしてこれらの多くの要素は、UIのアーキテクチャによらず類似したパターンに則ることができるとも考えています。
極端な話、スマートデバイスのネイティブ実装でも、Windowsアプリでも、Webアプリでも考えるべき事は同じです。

別に特別な事は書いていませんし、恐らく多くの人が似たような事を考え、実際の開発時に採用していることだと思います。
特別な事は書いていないとも言えますが、本エントリーでは、私の考えるオブジェクトのライフサイクルパターンと、その留意点について再整理してみました。

なお本エントリーのライフサイクルの名称は完全に(もしくは一部は)オレオレ用語で一般的な概念とは限りませんので、取り扱いにご注意ください。

ライフサイクルパターン一覧

アプリケーション内で取り扱う、オブジェクトの多くは(もしくは全ては)以下の何れかに分類することができます。

  • リクエスト
  • ページ
  • ユースケース
  • セッション
  • アプリケーション

これらのライフサイクルはリクエストが最も短く、下に行くほど長くなります。
そしてライフサイクルの選択は、必要な範囲で可能な限り短いものを選ぶべきでしょう。
これは、純粋にコード上の変数のスコープが狭い方が良い事と同義です。

リクエスト

ユーザーもしくは何らかのイベントに基づいた振る舞いを実装するために必要なオブジェクトのライフサイクルです。
たとえば、「ボタンが押された、何らかの処理をして画面に結果を表示する」といったスコープを指します。

多くは次のいずれかに閉じて利用するべきです。

  • メソッドの引数
  • メソッドのローカル変数

逆に、次のようなコードはアンチパターンです。

public class UserManager
{
    public void RegisterUser(string userName, string password)
    {
        CheckPassword();
        // 以降登録処理
    }

    private string password;

    private bool CheckPassword()
    {
        if (8 < password.Length)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

前述のケースでは処理の実行にメソッド呼び出し時に必要な値をクラスのフィールドとして保持しています。
これは幾つかの問題を引き起こす可能性があります。

  • RegisterUserがマルチスレッドで実行される可能性がある場合、タイミングによりパスワードチェックが正しく動作しない
  • 不要となったタイミングで再初期化されておらず、意図しないタイミングで利用すべきではない値が利用される

他にもあるかもしれません。
これは直接引数として渡すべきで、フィールドへ保管すべきではないでしょう。

public void RegisterUser(string userName, string password)
{
    CheckPassword(password)
    // 以降登録処理
}

private static bool CheckPassword(string password)
{
    if (8 < password.Length)
    {
        return true;
    }
    else
    {
        return false;
    }
}

なお、例えばローカル変数上で構造化されたオブジェクトを利用し、そのオブジェクトのメンバとして値を保持していると言った場合は問題としません。

ページ

ここではページと表現していますが、Windowやダイアログも同様です。
その画面内の状態を表すオブジェクトのライフサイクルです。

たとえば、MVVMパターンの場合、Viewと1:1に対応するトップレベルのViewModelと、そのViewModelに保持・管理されているメンバーが該当します。
トップレベルのViewModelは、対応するViewとライフサイクルを合わせる必要があるでしょう。
そうしないと
「前の操作で入力した値が、次の処理時に不正に表示される」
という状態が起こりえます。

とはいえ、それほど難しい話ではないでしょう。
DIコンテナでViewModelを管理しているような場合は、シングルトンになっていない事だけ注意すれば良いでしょう。

ユースケース

さてライフサイクルの話をする前に、ユースケースの定義が必要かもしれませんね。
とはいえ、UMLのユースケース図やユースケース駆動開発などで言われている一般的なユースケースと等価です。
ここではユースケースを、「ユーザーに対して価値を提供するアクションのシーケンスの集合」として定義しましょう。
砕いていうと「一つの目的を達成するのに、複数画面必要になることあるよね?」それです。

アプリケーションでは一つのユースケースを、複数のViewの組み合わせによって実現する事もあるでしょう。
ユースケースライフサイクルとは、ユースケースが開始してから完了するまでに保持するものを指します。

これは、UIのアーキテクチャによって実現方法に注意が必要です。

たとえばクライアントアプリケーションで、DIコンテナを利用しているような場合、ページ単位のオブジェクト(MVVMではViewModel)にユースケースを表すオブジェクトをインジェクションすることで実現する事になるでしょう。
この時、ユースケースオブジェクトは、画面をまたがって再利用される必要があるため、コンテナのライフサイクルとしてはシングルトンを採用するケースがままあります。
そのこと自体は問題ありませんが、ユースケース完了時にどのようにユースケースを破棄するか、十分に検討する必要があるでしょう。
ユースケースオブジェクトのフィールドを全てクリアする方法でも構わないのですが、フィールドが追加された時にクリアし忘れると、前の作業で利用した値が、次の作業で表示されてしまうといった不具合を招きがちです。
過去にこのケースのバグで何度煮え湯を飲まされたか分かりません。
コンテナからいったんインスタンスを削除してしまい、必要となったら再作成するのが理想的でしょう。
具体的な実装はコンテナによって異なりますので、別の機会に紹介したいと思います。

さて、Webアプリケーションの最近の潮流は良く分かりません。
太古の昔は、値自体はSessionにいれておいて、Sessionから値を出し入れするFacadeをユースケース毎に作り、ユースケース完了時にSessionからまとめて削除するのもFacadeに任せていましたが。。。
今はもっとスマートな方法があるかも知れませんね。ご存知の方教えてください。

セッション

ユーザーがアプリケーションを利用開始してから終了するまで維持されるライフサイクルです。
Webアプリケーションの場合のHTTP Sessionとほぼ等価でしょう。

  • ログインユーザー情報
  • ユースケースを跨って再利用されるキャッシュ情報(マスターとか)

などが該当するでしょう。
クライアントアプリケーションの場合、DIコンテナにシングルトンとして突っ込めばいいんじゃないですかね(適当。いや適切な意味で適当)

大がかりなWebの場合、セッションレプリケーションとか今はどうやってるんでしょう?良く分かりません。

アプリケーション

アプリケーションまたはシステムが起動してから終了するまで維持されるライフサイクルです。
これは単一のユーザーが利用するクライアントアプリケーションの場合、実質的にセッションと同等となるケースも多いでしょう。
Webアプリケーションの場合、ユーザー間を跨って、システムで共通に維持されるオブジェクトです。
たとえば、DBへの接続情報のような設定値が該当するでしょう。

設定値クラスを作って(.NETの場合、大抵はプラットフォームで提供されていますが)、それを利用しても良いのですが、直接利用するのは避けて、インターフェースベースでDIできるようにした方が良いケースも多いでしょう。
主にテスタビリティを確保するためです。
設定値の変更に基づいてアプリケーションが適切に振舞うかどうか?を自動テストしようとした場合、設定ファイルを直接読み書きしているレイヤーのクラスを使うとテストが困難になることが多いでしょう。

とりあえず今回は以上です。
ご意見・ご感想をお待ちしています。