Desencadenar la cadena de responsabilidad
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeAbre GameApplication
. Vamos a configurar la cadena dentro de su constructor, y empezaremos por instanciar todos los manejadores. Escribe $casinoHandler = new CasinoHandler()
, $levelHandler = new LevelHandler()
, y finalmente $onFireHandler = new OnFireHandler()
.
// ... lines 1 - 8 | |
use App\ChainHandler\CasinoHandler; | |
use App\ChainHandler\LevelHandler; | |
use App\ChainHandler\OnFireHandler; | |
// ... lines 12 - 16 | |
class GameApplication | |
{ | |
// ... lines 19 - 26 | |
public function __construct(private readonly CharacterBuilder $characterBuilder) | |
{ | |
// ... lines 29 - 30 | |
$casinoHandler = new CasinoHandler(); | |
$levelHandler = new LevelHandler(); | |
$onFireHandler = new OnFireHandler(); | |
// ... lines 34 - 38 | |
} | |
// ... lines 40 - 219 | |
} |
Aquí es donde podemos decidir el orden o secuencia de la cadena. Si tu aplicación no necesita ejecutar los manejadores en ningún orden concreto, ¡genial! ¡Puedes configurarlo como quieras! Pero en nuestro caso, sabemos que el CasinoHandler
puede afectar al jugador si sacamos un 7
, así que hay que llamarlo primero. Los otros dos controladores pueden ir en cualquier orden, así que empezaremos con el CasinoHandler
.
Escribe $casinoHandler->setNext($levelHandler)
, seguido de LevelHandler
- $levelHandler->setNext($onFireHandler)
- y podemos dejar solo el OnFireHandler
. Será el último de la cadena. Lo último que tenemos que hacer es establecer el CasinoHandler
en una propiedad de esta clase, así que escribe $this->xpBonusHandler = $casinoHandler
, y mantén pulsadas las teclas "Opción" + "Intro" para añadir la propiedad. Y cambia su tipo a XpBonusHandlerInterface
. ¡Perfecto!
// ... lines 1 - 11 | |
use App\ChainHandler\XpBonusHandlerInterface; | |
// ... lines 13 - 16 | |
class GameApplication | |
{ | |
// ... lines 19 - 24 | |
private XpBonusHandlerInterface $xpBonusHandler; | |
// ... line 26 | |
public function __construct(private readonly CharacterBuilder $characterBuilder) | |
{ | |
// ... lines 29 - 34 | |
$casinoHandler->setNext($levelHandler); | |
$levelHandler->setNext($onFireHandler); | |
$this->xpBonusHandler = $casinoHandler; | |
} | |
// ... lines 40 - 219 | |
} |
Vale, esta cadena tiene que activarse cuando termine una batalla, y hay un método muy práctico que podemos utilizar para hacerlo. Busca el método endBattle()
, y justo antes de notificar a los observadores, activa la cadena escribiendo $xpBonus = $this->xpBonusHandler->handle()
, donde el primer argumento es $winner
y el segundo es $fightResultSet->of($winner)
. Por último, añadiremos la XP extra al $winner
con $winner->addXp($xpBonus)
.
// ... lines 1 - 16 | |
class GameApplication | |
{ | |
// ... lines 19 - 100 | |
private function endBattle(FightResultSet $fightResultSet, Character $winner, Character $loser): void | |
{ | |
// ... lines 103 - 109 | |
$xpBonus = $this->xpBonusHandler->handle($winner, $fightResultSet->of($winner)); | |
$winner->addXp($xpBonus); | |
// ... lines 112 - 116 | |
} | |
// ... lines 118 - 222 | |
} |
¡Genial! ¡Vamos a probarlo!
Gira hasta tu terminal y ejecuta:
php bin/console app:game:play
Seré un luchador y atacaré hasta que, con suerte, gane, y... ¡ganamos! Pero, hmm... ¿cómo sabemos qué manejador ha entrado en acción? Bueno, ése es un inconveniente de este patrón de diseño. Es difícil de depurar, ya que se puede llamar a cualquier controlador. Para evitarlo, podemos imprimir un mensaje en cada manejador para que sea obvio.
Depuración de manejadores
Para hacerlo rápidamente, puedes copiar el código de debajo de este vídeo, pegarlo y... ¡listo! ¡Veamos si ha funcionado!
// ... lines 1 - 6 | |
use App\GameApplication; | |
// ... line 8 | |
class LevelHandler implements XpBonusHandlerInterface | |
{ | |
// ... lines 11 - 12 | |
public function handle(Character $player, FightResult $fightResult): int | |
{ | |
if ($player->getLevel() === 1) { | |
GameApplication::$printer->info('You earned extra XP thanks to the Level handler!'); | |
// ... lines 17 - 18 | |
} | |
// ... lines 20 - 25 | |
} | |
// ... lines 27 - 31 | |
} |
// ... lines 1 - 6 | |
use App\GameApplication; | |
// ... line 8 | |
class OnFireHandler implements XpBonusHandlerInterface | |
{ | |
// ... lines 11 - 12 | |
public function handle(Character $player, FightResult $fightResult): int | |
{ | |
if ($fightResult->getWinStreak() >= 3) { | |
GameApplication::$printer->info('You earned extra XP thanks to the OnFire handler!'); | |
// ... lines 17 - 18 | |
} | |
// ... lines 20 - 25 | |
} | |
// ... lines 27 - 31 | |
} |
// ... lines 1 - 7 | |
use App\GameApplication; | |
// ... line 9 | |
class CasinoHandler implements XpBonusHandlerInterface | |
// ... lines 11 - 13 | |
public function handle(Character $player, FightResult $fightResult): int | |
{ | |
// ... lines 16 - 24 | |
if ($dice1 === $dice2) { | |
GameApplication::$printer->info('You earned extra XP thanks to the Casino handler!'); | |
// ... lines 27 - 28 | |
} | |
// ... lines 30 - 35 | |
} | |
// ... lines 37 - 41 | |
} |
De vuelta a tu terminal, ejecuta
php bin/console app:game:play
De nuevo, juega hasta que termine la batalla, y... ¡mira esto! Ahí está nuestro mensaje! El LevelHandler
se activó y nos recompensó con XP extra. ¡Fantástico!
Vale, esto es genial, pero creo que podemos hacerlo mejor. Podemos aprovechar la inyección de dependencias de Symfony para inicializar la cadena. Como extra, refactorizaremos los manejadores para que dejen de comprobar si $next
está establecido aplicando el patrón Objeto Nulo. ¡Vamos a hacerlo!