Facadeパターン

デザインパターン【C#】:Facade 07_一連の手順があるロジックをファサードで吸収する

これまでのレクチャーでは、複雑なサブシステム(Machineプロジェクト)をMachineFacadeというひとつの窓口にまとめ、クライアント側はMachineFacade経由でしか各機能を使えないようにしてきました。今回は、その「窓口」であるFacadeに、単なる機能の中継役以上の役割を持たせます。すなわち、

「必ずこの順番で呼ばないといけない」「呼ぶ前にこのチェックが必要」といった、処理の手順そのものをFacadeの中に吸収してしまう、という使い方です。こうしておけば、Facadeを呼び出す全員が自動的に同じ手順・同じチェックを踏むことになり、使い方が統一されます。

チェックの手順をFacadeに集約する

まずはMachineFacadeにあるCameraTake()メソッドを例に見ていきます。変更前は、次のようにCameraクラスのTake()をそのまま呼び出すだけの実装になっています。

public static void CameraTake()
{
    new Camera().Take();
}

 

図1 変更前のCameraTake()。Boxの内部温度に関するチェックはまだ入っていない

ここに「Box内部の温度が70度を超えていたらカメラは使用できない(撮影前に必ずこのチェックを通す)」というルールを追加します。BoxInternalTemperature()を使って現在の内部温度を取得し、70度を超えていたら例外をthrowするようにします。

public static void CameraTake()
{
    if (BoxInternalTemperature() > 70)
    {
        throw new Exception("高温時はカメラを使用できません!!");
    }
 
    new Camera().Take();
}

 

図2 CameraTake()の先頭に温度チェックを追加した状態

この状態でクライアント側(Form1.cs)からMachineFacade.CameraTake()を呼び出しても、コード自体は今までと変わりません。

FanEntity entity = MachineFacade.FanSpin(4);
MachineFacade.CameraTake();

 

図3 クライアント側のコードは変更なし。呼び出し方はこれまで通り

ポイントは、Cameraクラス自体がinternalとして公開範囲を絞られている(前回のレクチャーで解説したカプセル化)ため、クライアント側からはCameraを直接newすることができず、必ずMachineFacade.CameraTake()を経由するしかない、という点です。つまりこのチェックは

「入れておけば絶対に通る」チェックとして機能します。誰かがうっかりチェックを忘れる、ということが構造的に起こり得ません。

このように、手順やチェックをFacadeの中に書いておくことで、呼び出し側の書き方に依存せず、使い方そのものを統一・強制できるというのが、Facadeパターンのもうひとつの効能です。

複数の処理をひとつの手順としてまとめる

チェックだけでなく、「複数の処理を決まった順番で組み合わせる」というのもFacadeに任せられます。例として、Box内部の温度を測定する際に、事前にファンを止めてから測る、という手順を新しいメソッドとして作成します。

まず、既存のBoxInternalTemperature()をコピーし、ファンを止めてから測るバージョンとしてBoxInternalTemperatureFunStop()という名前で用意します(メソッド名の末尾に「FunStop」を付けています)。

public static int BoxInternalTemperatureFunStop()
{
    FanStop(0);
 
    try
    {
        System.Threading.Thread.Sleep(30000);
        return new Box().GetInternalTemperature();
    }
    finally
    {
        FanStart(0);
    }
}

 

図4 BoxInternalTemperatureFunStop()の完成形。ファンを止めて30秒待ち、温度取得後は必ずファンを再開する

FanStop(0)でファン(fanIdは0番=全ファン扱い)を止めた後、System.Threading.Thread.Sleepで30秒待ってから温度を取得しています。ここで重要なのはtry/finallyの構造です。途中で例外が起きた場合でも、finally内のFanStart(0)は必ず実行されるため、「ファンを止めたまま戻ってこない」という事故を防いでいます。

このように、複数ステップからなる一連の処理を1つのメソッドにまとめてFacade側に持たせておけば、クライアント側は複雑な手順を意識することなく、メソッドを1回呼ぶだけで正しい手順を踏めるようになります。

図5 クライアント側でMachineFacade.と入力すると、BoxInternalTemperatureとBoxInternalTemperatureFunStopがまとまって候補に表示される

似た系統のメソッドの命名規則

同じBoxInternalTemperatureの「仲間」となるメソッドは今後も増える可能性があります。例えば、「毎回30秒待つのは面倒なので、前回ファンを止めて測定した際の値をメモリ上に保持しておき、それを返すだけのメソッド」も欲しくなったとします。まずは仮の実装として、次のようなメソッドを追加してみます。

public static int BoxInternalTemperatureInMemory()
{
    return 0;
}

 

図6 3つ目のバリエーションとして追加したメソッドの仮実装(この時点ではまだ固定値0を返すだけ)

最終的には、FanStop時に測定した値を_fanStopValueというフィールドに保持しておき、それを返す実装にします。ここで注目したいのは、

メソッド名の付け方

です。動画内では、いったん名前の前側(プレフィックス)に修飾語を付けるパターンを試しています。

public static int InMemoryBoxInternalTemperature()
{
    return _fanStopValue;
}

 

しかし、これをクライアント側でMachineFacade.と入力してIntelliSenseの候補を見てみると、アルファベット順に並ぶ都合上、本来「仲間」であるはずのBoxInternalTemperature系のメソッドから離れた場所(FanStart/FanStopの近く)に表示されてしまい、関連性が一目でわからなくなってしまいます。

図7 名前の前側に修飾語を付けてしまうと、IntelliSenseの候補一覧でBoxInternalTemperature系から離れた位置(FunStopBoxInternalTemperature/InMemoryBoxInternalTemperatureなど)に表示されてしまう

そこで、修飾語は名前の

後ろ(サフィックス)

に付けるように統一します。ベースとなるBoxInternalTemperatureに対して、FunStopを付けた版、さらにInMemoryを付けた版、という形で末尾に積み重ねていきます。

private static int _fanStopValue;
 
public static int BoxInternalTemperatureFunStop()
{
    FanStop(0);
 
    try
    {
        System.Threading.Thread.Sleep(30000);
        var result = new Box().GetInternalTemperature();
        _fanStopValue = result;
        return result;
    }
    finally
    {
        FanStart(0);
    }
}
 
public static int BoxInternalTemperatureFunStopInMemory()
{
    return _fanStopValue;
}

 

図8 最終的な命名。ベース名BoxInternalTemperatureの末尾に用途を表す語(FunStop、FunStopInMemory)を積み重ねる形にリネームした

この状態で改めてクライアント側からMachineFacade.と入力すると、BoxInternalTemperature/BoxInternalTemperatureFunStop/BoxInternalTemperatureFunStopInMemoryの3つがIntelliSenseの候補一覧上できれいに並んで表示されるようになります。

図9 サフィックス方式で命名し直した結果、同系統の3メソッドがIntelliSense上で隣り合って表示されるようになった

最近のIntelliSenseは検索性が高く、前方に修飾語を付けても目的のメソッドを見つけること自体は難しくありません。ですが、一覧の並び順という観点では、ベースとなる名前を先頭に置き、バリエーションを表す語を後ろに続けていく方が、「これは同じ系統の類似メソッドである」ということが一目で伝わりやすくなります。

まとめ

今回は、Facadeパターンの役割として次の2点を紹介しました。

・ 「必ずこの順番で使ってほしい」「使う前にこのチェックが必要」といった手順やチェックをFacade側にまとめておくことで、呼び出し側の書き方によらず、使い方を統一・強制できる。

・ 同じ対象に対する複数のバリエーションのメソッドを用意する場合は、ベースとなる名前を先頭に置き、用途を表す語を末尾(サフィックス)に積み重ねていくと、IntelliSenseの候補一覧上でも関連するメソッドがまとまって見やすくなる。

Facadeは単なる「窓口」にとどまらず、こうした運用ルールごと吸収してしまえる場所でもある、というのが今回のポイントです。

C#

■非公開コース「C#14新機能」プレゼント:
非公開コース「C#14新機能」(80分)をご覧になりたい方は
こちらからURLとパスワードを発行していますので、ご覧になってみてください。
非公開コース「C#14新機能」を観る

参考にした書籍

実戦で役立つ C#プログラミングのイディオム/定石&パターン
プログラミングC# 第8版

オブジェクト指向
ジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技
  すばらしい本です。オブジェクト指向が詰まっています。

オブジェクト脳のつくり方―Java・UML・EJBをマスターするための究極の基礎講座
  サンプルコードはjavaですが,最初にオブジェクト指向に目覚めるのに最適な本です。

Head Firstオブジェクト指向分析設計 ―頭とからだで覚えるオブジェクト指向の基本
  マンガみたいな感じで読めて,オブジェクト指向について学べます。  

Clean Code アジャイルソフトウェア達人の技
Clean Architecture 達人に学ぶソフトウェアの構造と設計
Adaptive Code ~ C#実践開発手法
.NETのエンタープライズアプリケーションアーキテクチャ
実装パターン
  廃盤のため異常に高くなりすぎなので,様子を見た方がいい気がします。一万円のボリュームがあるかどうかは微妙です。

オブジェクト指向でなぜつくるのか
レガシーコード改善ガイド
リファクタリング 既存のコードを安全に改善する

デザインパターン
オブジェクト指向における再利用のためのデザインパターン
オブジェクト指向のこころ
Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

ドメイン駆動開発
エリック・エヴァンスのドメイン駆動設計
実践ドメイン駆動設計
ドメイン駆動
現場で役立つシステム設計の原則
エンタープライズアプリケーションアーキテクチャパターン