Design Patterns In Action: The Decorator

The Decorator pattern allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. It involves creating a decorator class that wraps the original class and adds new behavior to it.

Here’s how to provide cyberpunk characters special abilities using the decorator pattern:

class Character {
    protected $name;
    protected $health;
    protected $damage;
    protected $description;

    public function __construct($name, $health, $damage) {
        $this->name = $name;
        $this->health = $health;
        $this->damage = $damage;
    }

    public function display() {
        return $this->name . ' (Health: ' . $this->health . ', Damage: ' . $this->damage . ')';
    }
}

class CharacterDecorator {
    protected $character;

    public function __construct(Character $character) {
        $this->character = $character;
    }

    public function display() {
        return $this->character->display();
    }
}

class CyberpunkCharacterDecorator extends CharacterDecorator {
    public function addHackingAbility() {
        $this->character->description .= ' (Hacking ability added)';
    }
}

class RobotCharacterDecorator extends CharacterDecorator {
    public function addRoboticArm() {
        $this->character->description .= ' (Robotic arm added)';
        $this->character->damage += 10;
    }
}

$character = new Character("Neo", 100, 20);
$cyberpunkCharacter = new CyberpunkCharacterDecorator($character);
$cyberpunkCharacter->addHackingAbility();
$robotCharacter = new RobotCharacterDecorator($cyberpunkCharacter);
$robotCharacter->addRoboticArm();

echo $robotCharacter->display();
// output: Neo (Health: 100, Damage: 30) (Hacking ability added) (Robotic arm added)

The CharacterDecorator is the base class which wraps the original Character class and adds behavior to it by extending it with new decorator classes like CyberpunkCharacterDecorator and RobotCharacterDecorator which add specific abilities to the character. Like a true cyberpunk, Neo can hack and has a kick-ass robotic arm!

Let’s go for SOLID:

interface CharacterInterface {
    public function display();
}

class Character implements CharacterInterface {
    protected $name;
    protected $health;
    protected $damage;
    protected $description;

    public function __construct($name, $health, $damage) {
        $this->name = $name;
        $this->health = $health;
        $this->damage = $damage;
    }

    public function display() {
        return $this->name . ' (Health: ' . $this->health . ', Damage: ' . $this->damage . ')';
    }
}

interface CharacterDecoratorInterface {
    public function addAbility();
}

abstract class CharacterDecorator implements CharacterDecoratorInterface, CharacterInterface {
    protected $character;

    public function __construct(CharacterInterface $character) {
        $this->character = $character;
    }

    public function display() {
        return $this->character->display();
    }
}

class CyberpunkCharacterDecorator extends CharacterDecorator {
    public function addAbility() {
        $this->character->description .= ' (Hacking ability added)';
    }
}

class RobotCharacterDecorator extends CharacterDecorator {
    public function addAbility() {
        $this->character->description .= ' (Robotic arm added)';
        $this->character->damage += 10;
    }
}

$character = new Character("Neo", 100, 20);
$cyberpunkCharacter = new CyberpunkCharacterDecorator($character);
$cyberpunkCharacter->addAbility();
$robotCharacter = new RobotCharacterDecorator($cyberpunkCharacter);
$robotCharacter->addAbility();

echo $robotCharacter->display();
// output: Neo (Health: 100, Damage: 30) (Hacking ability added) (Robotic arm added)

Here’s a break-down of how we’re now using SOLID principles:

  1. Single Responsibility Principle (SRP): Each class in this code has a single and well-defined responsibility. The Character class is responsible for managing the properties of a character and displaying their information, the CharacterDecorator class is responsible for wrapping a Character object and adding behavior to it, and the CyberpunkCharacterDecorator and RobotCharacterDecorator classes are responsible for adding specific abilities to the character.
  2. Open-Closed Principle (OCP): The classes in this code are open for extension but closed for modification. The Character class and the CharacterDecorator class are closed for modification, meaning that their behavior cannot be changed once they are implemented. However, new decorator classes can be created to extend the behavior of a Character object, such as the CyberpunkCharacterDecorator and RobotCharacterDecorator classes in this example.
  3. Liskov Substitution Principle (LSP): The CharacterDecorator class and its subclasses can be used interchangeably with the Character class without affecting the correctness of the program. This is because the CharacterDecorator class implements the same interface as the Character class and can be used in any context where a Character object is expected.
  4. Interface Segregation Principle (ISP): The Character and CharacterDecorator classes only have the methods that are necessary for their specific functionality. The Character class only has the methods required to manage its properties and display information, while the CharacterDecorator class only has the methods required to wrap a Character object and add behavior to it.
  5. Dependency Inversion Principle (DIP): The CharacterDecorator class depends on the abstraction of the Character class through the CharacterInterface and not on any concrete implementation of the Character class. This means that the CharacterDecorator class is not tightly coupled to the Character class, making it more flexible and easy to maintain.
Related Posts