清楚地构建可测试的模块化代码,其中包含新兴的业务需求条件

时间:2016-09-18 23:06:05

标签: php design-patterns slim command-pattern chain-of-responsibility

我试图概念化为Yelp或FB签到类型系统编写代码的最清晰的方法,但是 带有退房组件。我使用的是PHP。

基本要求可能是:  报到:   如果它是类型'建立',我需要

  1. 通过它的UUID查找实际建筑物以获取它的主键,我们将在办理登机手续时将其记录下来,然后将其输入报告 (签到建筑物' 123 Main Street')。
  2. 他们目前无法检查任何内容
  3. 他们必须在场地的X米范围内
  4. 如果他们没有X米的前提,必须有一个例外字符串来记录解释原因。
  5. 如果是' room',我需要

    1. 通过它的UUID查找实际房间以获取它的主键, 我们将登录办理登机手续,然后将其拉入报告 (登记到' 123 Main Street - > Room 212')。
    2. 他们需要进入建筑物
    3. 他们无法进入房间
    4. 退房'阶段也是如此,我提到你可以理解它并不像你说的那样真的像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语句(远程代码块)已经失控,但我不知道如何构建它。 那是 问题的关键

      没有任何要求:(操作代码)

      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 ($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,但我不知道它让事情更清楚。

      虽然它会清除这里的代码,但我不想在实际模型中实例化任何类(比如,我不想要 创建'已经在测试者''对象内部的对象)因为我希望这是可测试的,并且这样做 会使测试/模拟变得更加困难。

      话虽如此,测试也有同样的问题。这些不同的要求和如何测试有很多 他们,测试不是很孤立。我可以模拟alreadyInTest和建筑/房间对象来隔离 checkIn方法,并单独测试建筑物/房间,但要一起测试它们,就像在集成测试中一样,现在我 风险非确定性测试,因为我觉得这是一个混乱的方法。

      我的最后一个想法是这样的,但我担心控制器过多增肥:(操作代码)

      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语句中添加更多内容。我认为多态系统会更好,但从那以后 会有外部类需要进行需求检查,我必须实例化并传递大量的内容 反正的对象,使它同样复杂。在每个签入对象中实例化类不是可测试的。一世 我认为也许责任链或命令模式可能是有用的,但似乎并不是真的适合。

      我四处转转。

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

0 个答案:

没有答案