コラム

Facade ファサードパターンで窓口をつくる

※この記事の注意事項
・できるだけ初心者の方が理解しやすいように、細かい説明は省略しております。
・最後まで読み進められるように、多少ユーモアを交えて表現していることがあります。

========================================

Facade ファサードパターンで窓口をつくる

Facade ファサードパターンとは、シンプルな窓口をユーザーに提供することです。

処理の大まかな分類で、
・DBに登録するなどデータそのものを操作する処理
・ビジネスロジックなどの組み合わせや計算などの複雑な処理
があると思いますが、

このビジネスロジック部分にユーザーをアクセスさせようと思うと、
ソースコード上のどこに入れればいいのかパッと見わからなかったり、
ソースコードに修正が入った場合は、バグや事故のもとになりやすいです。

そこでユーザーがアクセスするインターフェイスをつくって、そこにアクセスさせます。
これによりユーザー側の記述がシンプルになり、
カプセル化された向こう側のコードがわからなくても動作させることができます。

今回考えたサンプルは、簡単なカートの仕組みを考えました。

・お店側の販売する商品クラス
・注文を行うクラス
・複数の注文をまとめるカートクラス

これに加えて、以下の2つのクラスをつくりました。
・商品を操作するクラス
シングルトンパターンでインタンスを一つに限定する
販売中の商品一覧を取得する
注文が入った場合、在庫の引き当てを行う

・注文を操作するクラス
注文された商品一覧の表示を行う

そして最後に、
・ユーザーがアクセスするクラス
を作成しました。

余談ですが、今回サンプルをつくっていて思ったことは、変数名などのネーミングセンスによって、ソースコードの読みやすさがかなり変わるということです。

機能が似ているので、変数名も似たようなものになりがちですが、パッと見て理解できるように変数名を付けたいものです。そのためにはスコープの理解は必須ですね。

コードは以下になります。

/**
* 商品情報クラス
*/
class Item
{
    private $id;
    private $name;
    private $price;

    public function __construct($id, $name, $price)
    {
        $this->id = $id;
        $this->name = $name;
        $this->price = $price;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getPrice()
    {
        return $this->price;
    }
}

/**
* 注文する商品クラス
*/
class Order
{
    private $item;
    private $amount;

    public function __construct(Item $item, $amount)
    {
        $this->item = $item;
        $this->amount = $amount;
    }

    public function getItem()
    {
        return $this->item;
    }

    public function getAmount()
    {
        return $this->amount;
    }
}

/**
* 注文する商品を格納するクラス
*/
class Cart
{
    private $cart;

    public function __construct()
    {
        $this->cart = [];
    }

    public function addItem(Order $order)
    {
        $this->cart[$order->getItem()->getId()] = $order;
    }

    public function getCart()
    {
        return $this->cart;
    }
}

/**
* 商品を操作するクラス
*/
class ItemOperation
{
    private static $instance;
    private $items;

    private function __construct()
    {
        //DBアクセスして商品一覧を取得したつもり
        $items = [
            [
                'id' => 1,
                'name' => 'フットサルシューズ',
                'price' => 5000,
            ],
            [
                'id' => 2,
                'name' => 'プラクティスシャツ',
                'price' => 2000,
            ],
            [
                'id' => 3,
                'name' => 'プラクティスパンツ',
                'price' => 2700,
            ],
        ];

        //DBから取得した値をセットする
        foreach ($items as $key => $value) {
            $item = new Item($value['id'], $value['name'], $value['price']);
            $this->items[$item->getId()] = $item;
        }
    }

    //シングルトンパターン
    public static function getInstance()
    {
        if (!isset(self::$instance)) {
            self::$instance = new ItemOperation();
        }
        return self::$instance;
    }

    //IDを指定して商品を取得する
    public function getItemById($item_id)
    {
        if (array_key_exists($item_id, $this->items)) {
            return $this->items[$item_id];
        } else {
            return NULL;
        }
    }

    //商品在庫の引き当て
    public function setStock(Order $order)
    {
        //DBアクセスして在庫情報を更新する
        # Code...

        echo $order->getItem()->getName() . "の在庫" . $order->getAmount() . "個を引き当てしました。<br>\n";
    }

    //シングルトンなのでクローンを禁止する
    public final function __clone()
    {
        throw new RuntimeExceotion ('Do Not Clone!' . get_class($this));
    }
}

/**
* 注文を操作するクラス
*/
class OrderOperation
{
    //注文情報の表示
    public static function viewOrder(Cart $cart)
    {
        echo '<table border="1">';
        echo '<tr>';
        echo '<td>ID</td>';
        echo '<td>商品名</td>';
        echo '<td>単価</td>';
        echo '<td>注文個数</td>';
        echo '</tr>';

        foreach ($cart->getCart() as $order) {
            echo '<tr>';
            echo '<td>' . $order->getItem()->getId() . '</td>';
            echo '<td>' . $order->getItem()->getName() . '</td>';
            echo '<td>' . $order->getItem()->getPrice() . '</td>';
            echo '<td>' . $order->getAmount() . '</td>';
            echo '</tr>';
        }

        echo '</table>';
    }
}

/**
* クライアントから注文処理を行うAPI
*/
class OrderApi
{
    public static function order(Cart $cart)
    {
        //商品操作のインスタンス作成
        $ItemOperation = ItemOperation::getInstance();

        //商品在庫の引き当て
        foreach ($cart->getCart() as $order) {
            $ItemOperation->setStock($order);
        }

        //注文情報の表示
        OrderOperation::viewOrder($cart);
    }
}

商品情報クラス Item と注文する商品クラス Order はとてもシンプルです。
さらに注文する商品を格納するクラスとして Cart クラスがあるのですが、関係は以下のようになります。

一つの Order の中には一種類の Item が入っていて、注文個数も入っている。
複数の Order は Cart の中に入っている。

商品を操作する時は ItemOperation クラスを使用し、
注文を操作する時は OrderOperation クラスを使用する。

ユーザー用のインターフェイスとして OrderApi クラスを使用する。

このユーザー用の OrderApi クラスを用意したことで、
ユーザー側の記述はシンプルなものになりました。
ユーザー側の記述は以下になります。

//商品操作のインスタンス作成
$ItemOperation = ItemOperation::getInstance();

//注文する商品の追加
$cart = new Cart();
$cart->addItem( new Order($ItemOperation->getItemById(1), 10) );
$cart->addItem( new Order($ItemOperation->getItemById(2), 15) );
$cart->addItem( new Order($ItemOperation->getItemById(3), 20) );

//注文処理
OrderApi::order($cart);

DB処理などははぶいてしまっていますが、
商品を追加するごとに在庫引き当てのメッセージが表示されます。
そして注文処理で注文一覧を表示しています。

あとはこれらをフォームにしたり、
フォームで受け取った値をメールで送信したりなどを行うことになります。

ポイントは、ユーザーがビジネスロジックである ItemOperation や OrderOperation に直接アクセスするのではなく、OrderApi にアクセスするようになっていることです。

これによってソースコードがカプセル化されシンプルな操作を行うことができるようになっています。

========================================
▼PHPのオブジェクト指向とデザインパターン記事一覧

関連記事

コメント

  • トラックバックは利用できません。

  • コメント (0)

  1. この記事へのコメントはありません。