Php 使用新兴的业务需求条件清晰地构建可测试的模块化代码
我正在尝试概念化为Yelp或FB签入类型系统编写代码的最清晰的方法,但是 还有一个签出组件。我正在使用PHP 基本要求可能是: 登记入住: 如果是“building”类型,我需要Php 使用新兴的业务需求条件清晰地构建可测试的模块化代码,php,design-patterns,slim,command-pattern,chain-of-responsibility,Php,Design Patterns,Slim,Command Pattern,Chain Of Responsibility,我正在尝试概念化为Yelp或FB签入类型系统编写代码的最清晰的方法,但是 还有一个签出组件。我正在使用PHP 基本要求可能是: 登记入住: 如果是“building”类型,我需要 通过UUID查找实际建筑,以获取其主键,我们将使用签入登录该主键,以便稍后将其拉入报告 (入住“大街123号”大楼) 当前无法将其签入任何内容 他们必须在房屋X米范围内 若他们不在房屋X米范围内,则必须记录异常字符串以解释原因 如果是“房间”类型,我需要 通过UUID查找实际房间以获取其主键 我们将使用签入登录,以便稍
{
"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代码块)已经失控了,但我不知道如何构造它。这就是问题所在
问题的关键
无任何要求:(feaux代码)
我试图避免使用switch语句,但在某些情况下,我必须使用一些东西来确定要实例化什么,
正确的?多态性在这里对我没有帮助,因为我知道它对许多switch语句有帮助,因为我还不知道
有一个目标
所以,对我来说,这似乎很简单。不清楚的是,我什么时候想添加一些需求。是什么
处理这种类型结构的正确方法(只涉及两种类型…这可能在短期内失控)
快点)
(feaux代码)
基于设计模式,如果我需要添加另一个$type
,我就在这里添加,这看起来相当不错。但是
这是一个控制器。。。它在滥用开关状态。。。因为所有的不同,它看起来很脆
实例化并传入的对象
我有DIC,但我不知道它能让事情变得更清楚
虽然它会清除这里的代码,但我不想在实际模型中实例化任何类(比如,我不想
在对象中创建'AlreadyTester'对象),因为我希望它是可测试的,这样做看起来像
将使测试/模拟变得更加困难
话虽如此,测试也有同样的问题。在这些不同的需求和如何测试上有太多的依赖
他们认为,这些测试并不是非常孤立的。我可以模拟AlreadyTest和building/room对象来隔离
签入方法,并单独测试建筑/房间,但要同时测试它们,就像在集成测试中一样,现在我
冒不确定性测试的风险,因为我发现这是一种混乱的方法
我最后的想法是这样的,但我担心控制器太胖了:(feaux代码)
再一次,我觉得我应该把这两个if/throws(每个案例)抽象到其他地方,但这似乎并不能让我做到这一点
再简单一点(同时:我承认这不是一个复杂的例子……现在),控制器也不是更瘦
在这两个例子中。我觉得最后一个例子对我来说更清楚。对我来说,问题的关键是,每次都会发生一些事情
添加,这将意味着向switch语句添加更多内容。我认为多态系统会更好,但因为
需要外部类来进行需求检查,我必须实例化并传递大量的
对象,使其同样复杂。在每个签入对象内实例化类不是可测试的。我
我在想也许责任链或命令模式可能有用,但似乎不太合适
我周而复始
那么,其中一种方法是最好的,还是我可以做得更好?如果您开始将签入视为一个对象,您可以使用策略模式来表示每种不同类型的签入。然后你可以使用工厂为给定类型返回正确的签入对象;此外,为了在一个地方建造对象,我强烈考虑了工厂;但我现在所做的就是把switch语句移到那里(当然,它可能永远只需要在那个地方)。自从我写这篇文章以来,我一直在考虑的是DIC基于这个:这有助于我理解在一个实例中使用DI的情况是有多个级别的实例化。我使用粉刺,但使用的方式要简单得多(只返回一个“新”语句,而不是像工厂那样创建一个)。我的目标可能很高,但我以前曾与这种类型的结构作过斗争;想要传入一个用于检查某些内容的对象(如'alreadyCheck
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 ($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)?
//
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();