Skip to content

Decorator Pattern

Aprius edited this page Nov 9, 2024 · 3 revisions

What

Wikipedia's defintion of the decorator pattern:

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other instances of the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality of a class to be extended without being modified. Decorator use can be more efficient than subclassing, because an object's behavior can be augmented without defining an entirely new object.

Usage

Decorator Pattern is excellent for dynamically modifying or enhancing game stats like adding buffs or debuffs to a character’s base stats

1, Define a basic decorator (BaseHealth) and a couple of stat decorators (HealthBuff and HealthDebuff) that will alter the health stat.

using Pancake.Pattern;

public class BaseHealth : IDecorator<float>
{
    private readonly float _baseValue;

    public BaseHealth(float baseValue) { _baseValue = baseValue; }
    public float Operation() { return _baseValue; }
}
using Pancake.Pattern;

public class HealthBuff : IDecorator<float>
{
    private readonly float _buffPercentage;
    private readonly IDecorator<float> _baseStat;

    public HealthBuff(IDecorator<float> baseStat, float buffPercentage)
    {
        _baseStat = baseStat;
        _buffPercentage = buffPercentage;
    }

    public float Operation() { return _baseStat.Operation() * (1 + _buffPercentage); }
}
using Pancake.Pattern;

public class HealthDebuff : IDecorator<float>
{
    private readonly float _debuffPercentage;
    private readonly IDecorator<float> _baseStat;

    public HealthDebuff(IDecorator<float> baseStat, float debuffPercentage)
    {
        _baseStat = baseStat;
        _debuffPercentage = debuffPercentage;
    }

    public float Operation() { return _baseStat.Operation() * (1 - _debuffPercentage); }
}

2, Create a Concrete HealthStat Class

Define a HealthStat class that extends Decorable<float> and provides a default implementation for handling health.

using Pancake.Pattern;

public class HealthStat : Decorable<float>
{
}

3, Now We apply decorators to modify the health stat dynamically using the Decorable<float> class.

using Pancake.Pattern;
using UnityEngine;

public class Character : MonoBehaviour
{
    private void Start()
    {
        // Initialize base health
        IDecorator<float> baseHealth = new BaseHealth(100f);

        // Apply a health buff (e.g., +20%)
        IDecorator<float> healthBuff = new HealthBuff(baseHealth, 0.2f);

        // Apply a health debuff (e.g., -10%) on top of the buff
        IDecorator<float> healthDebuff = new HealthDebuff(healthBuff, 0.1f);

        // Use Decorable to manage the current health operation
        var healthStat = new HealthStat { Decorator = healthDebuff };

        // Get the final health after applying the buff and debuff
        float finalHealth = healthStat.Operation();
        Debug.Log($"Character's final health after buffs and debuffs: {finalHealth}");
    }
}

(100 * (1 + 0.2)) * (1 - 0.1) = 100 * 1.2 * 0.9 = 108

image

Clone this wiki locally