Давайте создадим список типов животных:
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);
с тех пор выглядит немного лучше Test
а также Item
зависимости устранены. Все еще пахнет из-за isMammal()
, isReptile()
… это означает, что каждый раз, когда рождается новый тип, Item должен обновляться. Тем не менее, это плохая практика, которую базовый класс знает о своих потомках. Что такое элегантный способ?
Я полагаю, вы не хотите, чтобы конечный метод рос в вертикальном размере. Я предлагаю использовать интерфейсы для анализа 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()
Метод будет использовать только анализ структуры класса. Это значительно уменьшит дальнейший рост размеров.
Дальнейшее чтение:
Используйте интерфейс
Определите интерфейс и убедитесь, что все животные реализуют это.
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());
Я нашел хороший обходной путь. У меня будут однородные списки:
$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;
}
}
что ты думаешь?