La classe più utile che ho mai scritto

Almeno sin’oggi:

public class ViewFields
{
public string Name { get; set; }

public XElement XElement
{
get { return new XElement("FieldRef", new XAttribute("Name", Name)); }
}
}

 

Tags:

Primitive Obsession: Replace State-Altering Conditionals with State

Continuiamo con lo smell: Primitive Obsession

  

Problema:

Gestire le condizioni dello stato di un oggetto è complicato e complesso.

Un esempio di logica errata:

while (command != 'e')
{
    Console.WriteLine("\nWhat would you like to do now?");
    Console.Write("Move    Attack    Stop    Run    Panic    CalmDown    Exit the game: ==>");
    string choice;
    do
        choice = Console.ReadLine();
    while (choice == null);
    command = choice[0];

    switch (char.ToLower(command))
    {
        case 'm':
            if (ply.CurrentState == "Stop" || ply.CurrentState == "CalmDown")
                ply.CurrentState = "Move";
            else 
                Console.WriteLine("You need to stop moving first!!!");
            break;
        case 'a':
            if (ply.CurrentState == "Stop" || ply.CurrentState == "CalmDown")
                ply.CurrentState = "Attack";
            else
                Console.WriteLine("You need to stop moving first!!!");
            break;
        case 'p':
            ply.CurrentState = "Panic";
            break;
        case 'e':
            Console.Write("Thank you for playing!");
            break;
        default:
            Console.Write("Error, try again");
            break;
    }
}

 

Motivazione:

La prima motivazione al refactoring di questo codice è quella di controllare l’espansione esponeziale delle condizioni di controllo che si andranno a creare/aggiungere.
Tramite lo State pattern, si creerà una classe che si occuperà di gestire tutti gli stati di transazione dell’oggetto.

Soluzione:

Ecco come si mostrerà il codice di sopra applicando il State:

public class Context
{
    public IState State { get; set; }
    public void Request(char c)
    {
        string result;
        switch (char.ToLower(c))
        {
            case 'm': result = State.Move(this); break;
            case 'a': result = State.Attack(this); break;
            case 's': result = State.Stop(this); break;
            case 'r': result = State.Run(this); break;
            case 'p': result = State.Panic(this); break;
            case 'c': result = State.CalmDown(this); break;
            case 'e':
                result = "Thank you for playing \"The RPC Game\"";
                break;
            default:
                result = "Error, try again";
                break;
        }
        Console.WriteLine(result);
    }
}

public interface IState
{
    string Move(Context context);
    string Attack(Context context);
    string Stop(Context context);
    string Run(Context context);
    string Panic(Context context);
    string CalmDown(Context context);
}

// There are four States
class RestingState : IState
{
    public string Move(Context context)
    {
        context.State = new MovingState();
        return "You start moving";
    }
    public string Attack(Context context)
    {
        context.State = new AttackingState();
        return "You start attacking the darkness";
    }
    public string Stop(Context context)
    {
        return "You are already stopped!";
    }
    public string Run(Context context)
    {
        return "You cannot run unless you are moving";
    }
    public string Panic(Context context)
    {
        context.State = new PanickingState();
        return "You start Panicking and begin seeing things";
    }
    public string CalmDown(Context context)
    {
        return "You are already relaxed";
    }
}

class AttackingState : IState
{
    public string Move(Context context)
    {
        return "You need to stop attacking first";
    }
    public string Attack(Context context)
    {
        return "You attack the darkness for " + (new Random().Next(20) + 1) + " damage";
    }
    public string Stop(Context context)
    {
        context.State = new RestingState();
        return "You are calm down and come to rest";
    }
    public string Run(Context context)
    {
        context.State = new MovingState();
        return "You Run away from the fray";
    }
    public string Panic(Context context)
    {
        context.State = new PanickingState();
        return "You start Panicking and begin seeing things";
    }
    public string CalmDown(Context context)
    {
        context.State = new RestingState();
        return "You fall down and sleep";
    }
}

class PanickingState : IState
{
    public string Move(Context context)
    {
        return "You move around randomly in a blind panic";
    }
    public string Attack(Context context)
    {
        return "You start attacking the darkness, but keep on missing";
    }
    public string Stop(Context context)
    {
        context.State = new MovingState();
        return "You are start relaxing, but keep on moving";
    }
    public string Run(Context context)
    {
        return "You run around in your panic";
    }
    public string Panic(Context context)
    {
        return "You are already in a panic";
    }
    public string CalmDown(Context context)
    {
        context.State = new RestingState();
        return "You relax and calm down";
    }
}

class MovingState : IState
{
    public string Move(Context context)
    {
        return "You move around randomly";
    }
    public string Attack(Context context)
    {
        return "You need to stop moving first";
    }
    public string Stop(Context context)
    {
        context.State = new RestingState();
        return "You stand still in a dark room";
    }
    public string Run(Context context)
    {
        return "You run around in cirles";
    }
    public string Panic(Context context)
    {
        context.State = new PanickingState();
        return "You start Panicking and begin seeing things";
    }
    public string CalmDown(Context context)
    {
        context.State = new RestingState();
        return "You stand still and relax";
    }
}

static class Program
{
    // The user interface
    static void Main()
    {
        // context.s are States
        // Decide on a starting state and hold onto the Context thus established
        Context context = new Context();
        context.State = new RestingState();

        char command = ' ';
        Console.WriteLine("Welcome to \"The State Game\"!");
        Console.WriteLine("You are standing here looking relaxed!");
        while (command != 'e')
        {
            Console.WriteLine("\nWhat would you like to do now?");
            Console.Write("   Move    Attack    Stop    Run    Panic    CalmDown    Exit the game: ==>");
            string choice;
            do
                choice = Console.ReadLine();
            while (choice == null);
            command = choice[0];
            context.Request(command);
        }
    }
}

Benefici e non
+ Rimuove o riduce le logiche di cambio di stato.
+ Semplifica condizioni di stato complesse.
+ Aiuta a semplificare il design centralizzando il cambio di stato in un unico punto dell’applicazione.

- Complica il design se le condizioni di cambio di stato sono alquanto semplici/poche.

E non prendete come scusa: “il mio sistema ormai è troppo evoluto per poterne apportare queste migliorie. E’ troppo tardi.”

Se fosse realmente così non esisterebbe il Refactoring :)

Per questo post ho preso spunto da libro Refactoring To Patterns di Joshua Kerievsky.

Microsoft Enterprise Library 4.1 – October 2008

Credo d’averlo già scritto un paio di volte:

La libreria più utile al mondo!!!

http://msdn.microsoft.com/en-us/library/dd203099.aspx

Tags: