Абстрактная фабрика и примеси в 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. Мне очень хотелось бы услышать критику моего изложения общеизвестных вещей и моего варианта использования примесей от коллег. Я открыл для себя паттерны проектирования пару лет назад, но использовать их начал только сейчас.
И если будут у кого-то вопросы, то с радостью попробую ответить.