Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Design Patterns & Their Types

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Hey friends! Thanks for hanging out and giving me the privilege to guide us through some fun, geeky, but also useful stuff. We're talking design patterns. The idea is simple: The same problems that we face in our code every day have been faced a million times before. And often, a way or "strategy" to solve that problem has already been perfected. These are called "design patterns".

Why Should we Care?

A design pattern is nothing more than a "strategy" for writing code when you encounter a particular problem. If you can start to identify which types of problems are solved by which strategies, you'll walk into situations and immediately know what to do. Learning design patterns gives you:

A) More tools in your developer toolkit when coding and B) A better understanding of core libraries like Symfony, which leverages design patterns a lot.

It'll also make you way more fun at parties... assuming the only people at the party are programmers... because you'll be able to smartly say things like:

Yea, I noticed that you refactored to use the decorator pattern - great idea for extending that class without violating the single responsibility principle.

Dang, we're going to be super popular.

Design Pattern Types

Ok, so there are tons of design patterns. Though... only a small number are likely to be useful to us in the real-world: we just won't ever face the problems that the others solve. These many design patterns fall into three basic groups. You don't need to memorize these... it's just a nice way to think about the three types of problems that design patterns solve.

The first type is called "creational patterns", and these are all about helping instantiate objects. They include the factory pattern, builder pattern, singleton pattern, and others.

The second type is called "structural patterns". These help you organize things when you have a bunch of objects and you need to identify relationships between them. One example of a relationship would be a parent-child relationship, but there are many others. Yea, I know: this one can be a little fuzzy. But we will see one structural pattern in this tutorial: the "decorator pattern".

The third and final type of patterns is called "behavioral patterns", which help solve problems with how objects communicate with each other, as well as assigning responsibilities between objects. That's a fancy way of saying that behavioral patterns help you design classes with specific responsibilities that can then work together... instead of putting all of that code into one giant class. We'll talk about two behavioral patterns: the "strategy pattern" and the "observer pattern".

Get that Project Set up!

Now that we've defined some of what we'll be looking at, it's time to get technical! We're going to use these patterns in a real Symfony project to do real stuff. We'll only cover a few patterns in this tutorial - some of my favorites - but if you finish and want to see more, let us know!

All right, to be the best design-pattern-er that you can be, you should definitely download the course code from this page and code along with me. After you unzip it, you'll find a start/ directory that has the same code that you see here. Pop open this README.md file for all the setup details. Though, this one's pretty easy: you just need run:

composer install

Our app is a simple command-line role-playing game where characters battle each other and level up. RPG's are my favorite type of game - Shining Force for the win!

To play, run:

./bin/console app:game:play

Sweet! We have three character types! Let's be a fighter. We're battling another fighter. Queue epic battle sounds! And... we won! There was 11 rounds of fighting, 94 damage points dealt, 84 damage points received and glory for all!!! We can also battle again. And... woohoo! We're on a roll!

This is a Symfony app, but a very simple Symfony app. It has a command class that sets things up and prints the results. You tell it which character you want to be and it starts the battle.

... lines 1 - 14
class GameCommand extends Command
{
... lines 17 - 23
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->text('Welcome to the game where warriors fight against each other for honor and glory... and 🍕!');
$characters = $this->game->getCharactersList();
$characterChoice = $io->choice('Select your character', $characters);
$playerCharacter = $this->game->createCharacter($characterChoice);
$playerCharacter->setNickname('Player ' . $characterChoice);
$io->writeln('It\'s time for a fight!');
$this->play($io, $playerCharacter);
return Command::SUCCESS;
}
... lines 42 - 96
}

But most of the work is done via the game property, which is this GameApplication class. This takes these two Character objects and it goes through the logic of having them "attack" each other until one of them wins. At the bottom, it also contains the three character types, which are represented by this Character class. You can pass in different stats for your character, like $maxHealth, the $baseDamage that you do, and different $armor levels.

... lines 1 - 6
class GameApplication
{
public function play(Character $player, Character $ai): FightResult
{
$player->rest();
$fightResult = new FightResult();
while (true) {
$fightResult->addRound();
$damage = $player->attack();
if ($damage === 0) {
$fightResult->addExhaustedTurn();
}
$damageDealt = $ai->receiveAttack($damage);
$fightResult->addDamageDealt($damageDealt);
if ($this->didPlayerDie($ai)) {
return $this->finishFightResult($fightResult, $player, $ai);
}
$damageReceived = $player->receiveAttack($ai->attack());
$fightResult->addDamageReceived($damageReceived);
if ($this->didPlayerDie($player)) {
return $this->finishFightResult($fightResult, $ai, $player);
}
}
}
... lines 37 - 68
}

So GameApplication defines the three character types down here... then battles them up above. That's basically it!

... lines 1 - 37
public function createCharacter(string $character): Character
{
return match (strtolower($character)) {
'fighter' => new Character(90, 12, 0.25),
'archer' => new Character(80, 10, 0.15),
'mage' => new Character(70, 8, 0.10),
default => throw new \RuntimeException('Undefined Character'),
};
}
... lines 47 - 70

Next: let's dive into our first pattern - the "strategy pattern" - where we allow some characters to cast magical spells. To make that possible, we're going to need to make the Character class a lot more flexible.

Leave a comment!

2
Login or Register to join the conversation
Abelardo Avatar
Abelardo Avatar Abelardo | posted 28 days ago | edited

Hi there,

At your gameplay, the human player chooses a character from a given list. Ok.
Then, you randomly choose an AI character...but you don't check this election against the human player election.
Both, human and AI, can they choose the same character?

I didn't download the code because I can't pay for it.

Best regards.

Reply

Hey Abelardo,

That's a good question. In this case, the game allows both "players" to choose the same character because there are a few random stuff going on over the battle so that any character can win.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!