Dictionary invece di switch o if statements v2

.NET C# dictionary delegates

Nell’articolo precedente ho parlato di come, a mio parere, sia possibile migliorare la qualità del codice memorizzando chiavi/valori in un dictionary invece di usare switch o istruzioni condizionali. E se invece avessimo della logica di business da implementare per recuperare i punti vita per ogni tipo di Pokemon? Non preoccuparti, i dictionary e l’uso di delegati predefiniti come Action e Func lo fanno per noi.

Ho aggiunto altri due enum per evitare la presenza di numeri magici, che aumenterebbero il rumore nella code base.

public enum FightingGroundType : int
{
    GROUND = 1,
    AIR = 2,
    WATER = 3
}

public enum HitType : int
{
    SUPER_WEAK = 1,
    WEAK = 2,
    EFFECTIVE = 3,
    SUPER_EFFECTIVE = 4
}

Immaginiamo di implementare la logica di business all’interno di un manager che sarà incaricato di recuperare i punti vita in base al Pokemon e al tipo di terreno.

private static int GetHitPointForWaterType(FightingGroundType groundType)
{
    // Fai qualcosa e restituisci il valore
    return (int)(groundType == FightingGroundType.GROUND ? HitType.WEAK : HitType.SUPER_EFFECTIVE);
}

private static int GetHitPointForElectricType(FightingGroundType groundType)
{
    // Fai qualcosa e restituisci il valore
    return (int)(groundType == FightingGroundType.GROUND ? HitType.WEAK : HitType.EFFECTIVE);
}

private static int GetHitPointForGrassType(FightingGroundType groundType)
{
    // Fai qualcosa e restituisci il valore
    return (int)(groundType == FightingGroundType.AIR ? HitType.SUPER_WEAK : HitType.EFFECTIVE);
}

private static int GetHitPointForNormalType(FightingGroundType groundType)
{
    // Fai qualcosa e restituisci il valore
    return (int)HitType.WEAK;
}

[...]

Ora possiamo confrontare di nuovo i diversi stili di implementazione.

If statement

public int GetHitPoint(PokemonType pokemonType, FightingGroundType groundType)
{
    var point = 0;

    if (pokemonType == PokemonType.NORMAL)
    {
        point = GetHitPointForNormalType(groundType);
    }
    else if (pokemonType == PokemonType.GRASS)
    {
        point = GetHitPointForGrassType(groundType);
    }
    else if (pokemonType == PokemonType.ELECTRIC)
    {
        point = GetHitPointForElectricType(groundType);
    }
    else if (pokemonType == PokemonType.WATER)
    {
        point = GetHitPointForWaterType(groundType);
    }
    [...]

    return point;
}

Switch statement

public int GetHitPoint(PokemonType pokemonType, FightingGroundType groundType)
{
    var point = 0;

    switch (pokemonType)
    {
        case PokemonType.NORMAL:
            point = GetHitPointForNormalType(groundType);
            break;
        case PokemonType.GRASS:
            point = GetHitPointForGrassType(groundType);
            break;
        case PokemonType.ELECTRIC:
            point = GetHitPointForElectricType(groundType);
            break;
        case PokemonType.WATER:
            point = GetHitPointForWaterType(groundType);
            break;
        [...]
    }

    return point;
}

Struttura Dictionary

Quindi, considero di creare una mappatura tra il tipo di Pokemon disponibile e le funzioni, trasformando il blocco switch o if-else in un dictionary di delegati. La chiave è il tipo dell’istruzione switch o dell’istruzione if-else condizionale; il valore può essere un Action (se le funzioni sono void) o Func (se le funzioni restituiscono valori); in questo caso uso Func per fornire alcuni parametri di input e restituire un valore

private readonly Dictionary<PokemonType, Func<FightingGroundType, int>> _basePointMapDelegates = new Dictionary<PokemonType, Func<FightingGroundType, int>>
{
    { PokemonType.NORMAL, GetHitPointForNormalType },
    { PokemonType.GRASS, GetHitPointForGrassType },
    { PokemonType.ELECTRIC, GetHitPointForElectricType },
    { PokemonType.WATER, GetHitPointForWaterType },
    [...]
};

public int GetHitPoint(PokemonType pokemonType, FightingGroundType groundType)
{
    return _basePointMapDelegates.ContainsKey(pokemonType) ? _basePointMapDelegates[pokemonType].Invoke(groundType) : 0;
}

Conclusione

L’idea generale è ridurre l’uso degli switch/if statements per controllare il flusso; li uso spesso ma, tuttavia, ci sono alcune circostanze in cui le cose potrebbero essere fatte meglio usando un dictionary o qualsiasi altra collezione adatta per raggiungere l’obiettivo.

Github repo: https://github.com/apulito/dictionary-instead-statements-v2

Buona giornata!

Alberto