Абстрактная фабрика и примеси в PHP (Часть 2)
Однако, вернемся к нашему случаю. Допустим тут в комнату входит начитанный разработчик (НР), смотрит на код и вспоминает, что по условиям игры все светлые умеют лечить, а все темные становятся невидимыми. Добавим соответствующие методы.
<?php abstract class Infantry { //общие для всей пехоты методы abstract public function Attack(); abstract public function Defend(); } abstract class Archer { abstract public function Shoot(); } abstract class SiegeWeapons { abstract public function Fire(); } class GoodInfantry extends Infantry { //Спецэфическая реализация для доброго пехотинца public function Attack(); public function Defend(); public function Treat(); } class GoodArcher extends Archer{ public function Shoot(); public function Treat(); } class GoodSiegeWeapons extends SiegeWeapons{ public function Fire(); public function Treat(); } class EvilInfantry extends Infantry{ //Спецэфическая реализация для злого пехотинца public function Attack(); public function Defend(); public function Invisible(); } class EvilArcher extends Archer{ public function Shoot(); public function Invisible(); } class EvilSiegeWeapons extends SiegeWeapons{ public function Fire(); public function Invisible(); } abstract class AbstractFactory { abstract public function createInfantry(); abstract public function createArcher(); abstract public function createSiegeWeapons(); } class GoodFactory extends AbstractFactory { /** * @return Infantry */ public function createInfantry() { return new GoodInfantry(); } /** * @return Archer */ public function createArcher() { return new GoodArcher(); } /** * @return SiegeWeapons */ public function createSiegeWeapons() { return new GoodSiegeWeapons(); } } class EvilFactory extends AbstractFactory { /** * @return Infantry */ public function createInfantry() { return new EvilInfantry(); } /** * @return Archer */ public function createArcher() { return new EvilArcher(); } /** * @return SiegeWeapons */ public function createSiegeWeapons() { return new EvilSiegeWeapons(); } }
Код стал не таким красивым по тому, что появились методы, которые приходится копировать. В php нет множественного наследования и приходится выбирать наследоваться белому лучнику от белых или от лучников. Мы априори выбрали второй вариант и теперь думаем, как сделать наш код красивее, а его поддержку удобнее. НР предлагает использовать примеси (trait), которые появились в php 5.4. Вынесем обшие методы в примесь.
<?php abstract class Infantry { //общие для всей пехоты методы abstract public function Attack(); abstract public function Defend(); } abstract class Archer { abstract public function Shoot(); } abstract class SiegeWeapons { abstract public function Fire(); } trait Good { public function Treat(); } trait Evil { public function Invisible(); } class GoodInfantry extends Infantry { use Good; //Спецэфическая реализация для доброго пехотинца public function Attack(); public function Defend(); } class GoodArcher extends Archer{ use Good; public function Shoot(); } class GoodSiegeWeapons extends SiegeWeapons{ use Good; public function Fire(); } class EvilInfantry extends Infantry{ use Evil; //Спецэфическая реализация для злого пехотинца public function Attack(); public function Defend(); } class EvilArcher extends Archer{ use Evil; public function Shoot(); } class EvilSiegeWeapons extends SiegeWeapons{ use Evil; public function Fire(); } abstract class AbstractFactory { abstract public function createInfantry(); abstract public function createArcher(); abstract public function createSiegeWeapons(); } class GoodFactory extends AbstractFactory { /** * @return Infantry */ public function createInfantry() { return new GoodInfantry(); } /** * @return Archer */ public function createArcher() { return new GoodArcher(); } /** * @return SiegeWeapons */ public function createSiegeWeapons() { return new GoodSiegeWeapons(); } } class EvilFactory extends AbstractFactory { /** * @return Infantry */ public function createInfantry() { return new EvilInfantry(); } /** * @return Archer */ public function createArcher() { return new EvilArcher(); } /** * @return SiegeWeapons */ public function createSiegeWeapons() { return new EvilSiegeWeapons(); } }
Теперь, какой бы сложной не была реализация лечения и невидимости, нам придется написать еге всего один раз и править в едином месте. Про абстрактную фабрику и про примеси есть куча литературы, но я ни разу не видел их описание в одной статье. А тем временем, практически каждое использование этого паттерна приводит к необходимости введения общих методов и свойств для групп обьектов, которые мы разделили нашими фабриками.
P.S. Мне очень хотелось бы услышать критику моего изложения общеизвестных вещей и моего варианта использования примесей от коллег. Я открыл для себя паттерны проектирования пару лет назад, но использовать их начал только сейчас.
И если будут у кого-то вопросы, то с радостью попробую ответить.
Не знаю насколько правильно так делать и правильно ли, но можно сделать множественное наследие таким путём:
ChildClass extends Class implements Interface1 ?
Олег, тут план в том, что есть реализация методов и в родительском классе и в примеси. А в твоем варианте в интерфейсе можно только указать обязательство реализовать такой-то метод, но не саму реализацию.
Вот это совсем не тот выбор, который на самом деле стоит. Никакого наследования от белых здесь быть не может. У юнита должен быть владелец, т. е. некоторое свойство ownerFraction -> указатель на игрока. Это дает нам сразу пачку возможностей: добавить в игру третьего игрока, временно менять владельца (помните, как в Dune2000 ордосы могли временно захватывать чужих юнитов?)
Спасибо за комментарий.
Я подразумевал, что владелец, совсем отдельная сущьность. Как, например, в Героях Магии и Меча есть несколько замков, при этом у меня могут быть сразу несколько разных либо несколько одинаковых. Там есть свойства общие для всего замка, а есть свойства, присущие всем лучникам. Или всем, кто летает.
Вить, но ведь даже в таком контексте ты лишил фабрику главного — ее абстракции. Теперь всякий раз, когда мы хотим создать юнит мы должны написать что-то вроде
$factory = null;
if ($fraction->getSide() == ‘light’) {
$factory = new GoodFactory();
} elseif ($fraction->getSide() == ‘dark’ {
$factory = new EvilFactory();
}
$unit = null;
if ($unitType == ‘knight’) {
return $factory->createKnight();
} else ...
Заметь, что если мы заменим фабрику на прямое создание юнита через new, станет только проще. Но ведь фабрика должна повысить уровень абстракции, чтобы мы больше не думали о принадлежности юнита фракции и не думали о влиянии каких-то еще вещей на его особенности. Фабрика должна упрощать.
Некоторый смысл у фабрики появляется сразу же, как только мы перестанем плодить разные фабрики и вспомним об абстракции:
$factory = new UnitFactory();
return $factory->createUnit($unitType, $fraction->getSide());
Теперь нам не надо думать ни о том, влияет ли на что-либо фракция или нет, ни о типе создаваемого юнита. Разработчик в то же время вынужден передать тип фракции и идентификатор создаваемого юнита в фабричный метод. Да и код стал короче — все детали скрыты в самом методе createUnit.
????? ??? ? ????? ??? ?? ?? ???? ????? ?? ???? ??? ????, ??? ?? ?? ??? ?????.
https://audiojungle.net/item/christmas-piano/19056234
????? ??? ? ????? ??? ?? ?? ???? ????? ?? ???? ??? ????, ??? ?? ?? ??? ?????.
https://audiojungle.net/item/christmas-piano/19056234