1

我试图概念化为 Yelp 或 FB 签入类型系统之类的代码编写代码的最清晰方法,但也包含签出组件。我正在使用 PHP。

基本要求可能是:入住:如果是“建筑”类型,我需要

  1. 通过它的 UUID 查找实际建筑物以获取它的主键,我们将在签入时登录,以便稍后将它们拉入报告中(签入到建筑物“123 Main Street”)。
  2. 他们目前无法检查任何内容
  3. 他们必须在场地的 X 米范围内
  4. 如果他们不在 X 米范围内,则必须记录一个异常字符串来解释原因。

如果是“房间”类型,我需要

  1. 通过它的 UUID 查找实际房间以获取它的主键,我们将在签入时登录以稍后将它们拉入报告中(签入到“123 Main Street -> Room 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 等。我正在寻找有关如何使此代码简单、可维护和可测试的想法。一方面,您可以看到我的 switch 语句(far 代码块)是如何失控的,但我不知道如何构造它。这就是问题的症结所在

没有任何要求:(功能代码)

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 语句都有帮助,因为我还没有对象。

所以,对我来说,这似乎很简单。不清楚的是何时我想添加一些要求。处理这种类型的结构的正确方法是什么(只涉及两种类型......这可能会很快失控)。

(功能代码)

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,我就在这里做,看起来还不错。但是......这是在一个控制器中......它正在滥用开关状态......而且由于实例化和传入的所有不同的东西,它看起来很脆弱/脆弱。

我有一个 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();

同样,我觉得我应该在别处抽象这两个 if/throws(每个案例),但这似乎并没有让这变得更简单(另外:我承认这不是一个复杂的例子......但是),并且控制器在这两个示例中都没有更瘦。我觉得最后一个例子对我来说更清楚。对我来说,关键在于每次添加某些内容时,都意味着向 switch 语句添加更多内容。我认为多态系统会更好,但是由于需要外部类来进行需求检查,因此无论如何我都必须实例化并传递大量对象,从而使其同样复杂。在每个签入对象中实例化类是不可测试的。我在想也许责任链或命令模式可能有用,但似乎不太合适。

我兜兜转转。

那么,这些是最好的方法之一,还是我可以做得更好?

4

0 回答 0