1

私は、Yelp や FB のチェックイン型システムのようなコードを書くための最も明確な方法を概念化しようとしていますが、チェックアウト コンポーネントも使用します。私はPHPを使用しています。

基本的な要件は次のとおりです: チェックイン: タイプが「建物」の場合は、

  1. UUID で実際の建物を検索して主キーを取得します。チェックインでログに記録し、後でレポートに取り込みます (建物 '123 Main Street' にチェックイン)。
  2. 現在、すでにチェックインされているものはありません
  3. それらは建物から X メートル以内になければなりません
  4. 施設から X メートル以内にない場合は、理由を説明するためにログに記録する例外文字列が必要です。

タイプが「部屋」の場合は、

  1. UUID で実際の部屋を検索して、その主キーを取得します。これは、後でレポートにプルするためにチェックインでログインします (「メイン ストリート 123 -> ルーム 212」にチェックイン)。
  2. 彼らはすでに建物にチェックインする必要があります
  3. 彼らはすでに部屋にチェックインすることはできません

「チェックアウト」フェーズもありますが、これについて言及すると、「私はここにいました」と言うだけの Yelp とはまったく違うことがわかります。チェックインするだけのアクションではありません。その後に「チェックアウト」が続きますが、簡潔にするためにここでは説明しません。私の質問は、上記を満たすコードを構造化するための、よりクリーンなコントローラー、メソッド、およびデザイン パターンの作成についてです。

したがって、リクエストは json を送信し、(Slim) アプリにヒットし、メソッドのコントローラー (この場合は Post) に行きます。GPS 座標、チェックインしている建物/部屋の UUID、ID (認証による)、チェックインの種類、建物または部屋があります。何かのようなもの:

{
    "latitude": "33.333", // these only matter if it is 'building'
    "longitude": "-80.343", // these only matter if it is 'building'
    "buildingUuid": "ff97f741-dba2-415a-a9e0-5a64633e13ad", // could also be 'roomUuid' ...
    "type": "building", // or 'room'
    "exceptionReason": null
}

建物の座標との関係を確認したり、建物の ID を調べたりすることができます。このコードを簡単に、保守しやすく、テストしやすくする方法についてのアイデアを探しています。1 つには、私の switch ステートメント (コード ブロックの far) がどうやって手に負えなくなったかを見ることができますが、これを構造化する他の方法はわかりません。それが質問の核心です

要件なし: (feaux コード)

switch($type) {
    case 'building':
        $model = new \Namespace\Models\Building($this->container);
        break;

    case 'room':
        $model = new \Namespace\Models\Room($this->container);
        break;

    default:
        throw new \InvalidArgumentException("Type {$type} not understood");
}

$model->checkIn(); // polymorphic method that all 'type' classes will have, based on an interface implemented

私は switch ステートメントを避けようとしていますが、ある時点で、をインスタンス化するかを理解するために何かを使用する必要がありますよね? オブジェクトがまだないため、多くの switch ステートメントでポリモーフィズムが役立つことはわかっていますが、ここではポリモーフィズムは役に立ちません。

だから、それは私にはかなり簡単に思えます。明確でないのは、要件の一部をいつ追加したいかです。このタイプの構造を処理する適切な方法は何ですか (これには 2 つのタイプしか含まれません...これは急いで手に負えなくなる可能性があります)。

(フェイクコード)

switch ($type) {
    case 'building':
        $model = new \Namespace\Models\CheckInBuilding($this->container);
        $alreadyInTest = new \Namespace\Models\Buildings\BuildingTestIn($this->container);
        $building = new \Namespace\Models\Buildings\Building($this->getData('buildingUuid'), $this->container);

        if (!$building) {
            throw new Exceptions\FailBadInput('Building not found: ' . $this->getData('buildingUuid'));
        }

        $model->setBuilding($building); // building object used for these properties: ID, coordinates for the lat/long 'nearby' check
        break;

    case 'room':
        $model = new \Namespace\Models\CheckInRoom($this->container);
        $alreadyInTest = new \Namespace\Models\Rooms\RoomTestIn($this->container);
        $room = new \Namespace\Models\Rooms\Room($this->getData('roomUuid'), $this->container);

        if (!$room) {
            throw new Exceptions\FailBadInput('Room not found: ' . $this->getData('roomUuid'));
        }

        $model->setRoom($room); // room object used for these properties: ID
        break;

    default:
        throw new \InvalidArgumentException("Type {$type} not understood");
}

$model->setAlreadyInTest($alreadyInTest);
$model->setData($this->getData());
$model->checkIn();
//
// Now, for 'building|room', this has all the data internal to check:
//   - if they are nearby (but only if in building)
//   - to test if they are already logged in
//   - to log the primary key ID with the request
//

//
// In addition, and not covered here, if they are not in range of the building, they can still check-in, but need to
// type a short reason why they are doing a check-in and not in nearby range ('GPS broken, etc', who knows...). So,
// would I add yet another class, instantiated here so it can be mocked in a test, and passed in empty, so it could
// eventually be used to insert an exception reason (in a different table than the check-in table)?
//

設計パターンに基づいて、別の を追加する必要がある場合は$type、ここでそれを行います。これはかなり良いようです。しかし...これはコントローラにあります...それはswitchステートメントを悪用しています...そして、インスタンス化されて渡されるすべての異なるもののために、脆弱/壊れやすいようです.

私はDICを持っていますが、それが物事をより明確にするかどうかはわかりません.

ここのコードはすっきりしますが、実際のモデルでクラスをインスタンス化したくありません (オブジェクト内に「alreadyInTester」オブジェクトを作成したくないなど)。テスト/モックがはるかに難しくなるようです。

そうは言っても、テストには同じ問題があります。これらのさまざまな要件とそれらをテストする方法に大きく依存しているため、テストはあまり分離されていません。alreadyInTest オブジェクトと building/room オブジェクトをモックして checkIn メソッドを分離し、Building/room を個別にテストすることはできますが、統合テストのようにそれらをすべて一緒にテストするには、非決定的テストのリスクを冒す必要があります。乱雑なアプローチ。

私の最後の考えは次のようなものになるでしょうが、コントローラーを太らせすぎることを心配しています: (feaux code)

switch($type) {
    case 'building':
        $alreadyInTest = new \Namespace\Models\Buildings\BuildingTestIn($this->container);
        if($alreadyInTest->isIn()) {
            throw new \InvalidArgumentException('You are already checked in to a building');
        }

        $building = new \Namespace\Models\Buildings\Building($this->getData('buildingUuid'), $this->container);

        if (!$building) {
            throw new Exceptions\FailBadInput('Building not found: ' . $this->getData('buildingUuid'));
        }

        $model = new \Namespace\Models\Building($this->container);
        break;

    case 'room':
        $alreadyInTest = new \Namespace\Models\Rooms\RoomTestIn($this->container);

        if($alreadyInTest->isIn()) {
            throw new \InvalidArgumentException('You are already checked in to a room');
        }

        $room = new \Namespace\Models\Rooms\Room($this->getData('roomUuid'), $this->container);

        if (!$room) {
            throw new Exceptions\FailBadInput('Room not found: ' . $this->getData('roomUuid'));
        }

        $model = new \Namespace\Models\Room($this->container);
        break;

    default:
        throw new \InvalidArgumentException("Type {$type} not understood");
}

$model->setData($this->getData());
$model->checkIn();

繰り返しますが、これらの 2 つの if/throws (ケースごと) を別の場所で抽象化する必要があるように感じますが、それでこれがより簡単になるとは思えません (また、これは複雑な例ではないことを認めます... まだ)、どちらの例でも、コントローラーはそれほど細くはありません。私には、この最後の例がより明確に感じられます。私にとって重要なのは、何かが追加されるたびに、switch ステートメントにさらに追加することを意味するということです。ポリモーフィック システムの方が優れていると思いますが、要件チェックを実行するために必要な外部クラスが存在するため、とにかく大量のオブジェクトをインスタンス化して渡す必要があり、同じように複雑になります。各チェックイン オブジェクト内でクラスをインスタンス化することは、テスト可能ではありません。Chain of Responsibility または Command パターンが役立つかもしれないと考えていましたが、あまり適切ではないようです。

ぐるぐる回ります。

それで、これらの1つが最善の方法ですか、それとももっとうまくできることがありますか?

4

0 に答える 0