PHP,根据对象类型运行代码会导致依赖关系



让我们创建一个动物类型列表:

abstract class Item
{
public function run()
{
echo __FUNCTION__.'<br>';
}
}
class Reptile extends Item
{
public function putEgg()
{
echo __FUNCTION__.'<br>';
}
}
class Mammal extends Item
{
public function born()
{
echo __FUNCTION__.'<br>';
}
}
$list = [];
for ($i = 1; $i <= 10; $i++)
{
switch(mt_rand(1,2))
{
case 1 :
$o = new Reptile();
break;
case 2 :
$o = new Mammal();
break;
}
$list[] = $o;
}

现在在其他地方,我想列出它们:

class Test
{
public function dump(array $list)
{
foreach ($list as $o)
{
/**
* @var Item $o
*/
echo '<hr>';
echo get_class($o).':<br>';
$o->run();
if ($o instanceof Mammal)
{
$o->born();
}
if ($o instanceof Reptile)
{
$o->putEgg();
}
}
}
}
(new Test())->dump($list);

现在我的问题是Test类与Item及其所有后代耦合。如果我像这样重构整体:

abstract class Item
{
public function run()
{
echo __FUNCTION__.'<br>';
}
public function isReptile()
{
return $this instanceof Reptile;
}
public function isMammal()
{
return $this instanceof Mammal;
}
}
class Reptile extends Item
{
public function putEgg()
{
echo __FUNCTION__.'<br>';
}
}
class Mammal extends Item
{
public function born()
{
echo __FUNCTION__.'<br>';
}
}
$list = [];
for ($i = 1; $i <= 10; $i++)
{
switch(mt_rand(1,2))
{
case 1 :
$o = new Reptile();
break;
case 2 :
$o = new Mammal();
break;
}
$list[] = $o;
}
//
class Test
{
public function dump(array $list)
{
foreach ($list as $o)
{
/**
* @var Item $o
*/
echo '<hr>';
echo get_class($o).':<br>';
$o->run();
if ($o->isMammal())
{
$o->born();
}
if ($o->isReptile())
{
$o->putEgg();
}
}
}
}
(new Test())->dump($list);

自从现在TestItem依赖项消除以来,看起来更好一点。由于isMammal(),它仍然闻起来,isReptile()...这意味着每次新类型诞生时,项目都应该更新。然而,基类了解其后代是一种不好的做法。什么是优雅的方式?

使用接口

定义一个接口并确保所有动物都实现它。

interface Animal {
public function born();
}

现在所有的动物都必须实现这一点并实现接口中定义的函数。

class Reptile implement Animal {
public function born()
{
return 'new baby reptile';
}
}
class Mammal implement Animal {
public function born()
{
return 'new baby mammal';
}
}
class Test {
public function makeBaby(Animal $animal)
{
echo $animal->born();
}
}
(new Test())->makeBaby(new Reptile());
(new Test())->makeBaby(new Mammal());

我相信,您不希望最终方法以垂直大小增长。我建议使用接口来分析Item结构,而不是它的类。

<?php
/*
* Interfaces first.
* They will allow us to build a "contract" between calling class and
* actual implementations. Also, only interfaces MUST be used in end class.
*/
interface ViviparousInterface
{
public function giveBirth();
}
interface OviparousInterface
{
public function layEgg();
}
interface SpawningInterface
{
public function layCaviar();
}
/*
* Now implemetation classes:
*/
abstract class Item
{
public function run()
{
echo __FUNCTION__ . '<br>';
}
}
class Reptile extends Item implements OviparousInterface
{
public function layEgg()
{
echo __FUNCTION__ . '<br>';
}
}
class Mammal extends Item implements ViviparousInterface
{
public function giveBirth()
{
echo __FUNCTION__ . '<br>';
}
}
class Fish extends Item implements SpawningInterface
{
public function layCaviar()
{
echo __FUNCTION__ . '<br>';
}
}
class ShomethingElse extends Item implements ViviparousInterface
{
public function giveBirth()
{
echo __FUNCTION__ . '<br>';
}
}
/**
* Test class:
*/
class Test
{
public function dump(array $list)
{
foreach ($list as $o)
{
/**
* @var Item $o
*/
echo '<hr>', get_class($o) . ':<br>';
$o->run();
/*
* Here we do not care about actual classes.
* We do know, that if they implement one of the interfaces,
* then they will have required methods.
*/
if ($o instanceof ViviparousInterface) {
$o->giveBirth();
} elseif ($o instanceof OviparousInterface) {
$o->layEgg();
} elseif ($o instanceof SpawningInterface) {
$o->layCaviar();
}
}
}
}
/*
* Test case:
*/
$list = [];
for ($i = 1; $i <= 10; $i++)
{
switch(mt_rand(1, 4))
{
case 1:
$o = new Reptile();
break;
case 2:
$o = new Mammal();
break;
case 3:
$o = new Fish();
break;
case 4:
$o = new ShomethingElse();
break;
}
$list[] = $o;
}
(new Test())->dump($list);

最后,无论您将来有多少实际Item后代,您的Test:dump()方法都将仅使用类结构分析。它将大大减少进一步的规模增长。

延伸阅读:

  1. PHP 中接口的意义何在?
  2. 在PHP中养成七个良好的面向对象习惯

我找到了一个很好的解决方法。我将有均质列表:

$listReptiles = [];
$listMammals = [];
for ($i = 1; $i <= 10; $i++)
{
switch(mt_rand(1,2))
{
case 1 :
$listReptiles[] = new Reptile();
break;
case 2 :
$listMammals[] = new Mammal();
break;
}
}

你觉得怎么样?

最新更新