Ecrire une activité composite tout en code n’a rien de très joyeux et une activité composite écrite en mode impératif (C# ou Vb) peut vite devenir moins performante que la même activité écrite de manière déclarative (Xaml). On peut même se retrouver avec des comportements inattendues :

  • Définition de l’activité qui n’est pas mise à jours alors que le code de l’activité a été modifié :(
  • Consommation anormale de  ressources mémoire :(

Pour éviter cela, il suffit de ne pas céder aux tendance du moment :

  • Affectation de l’implémentation dans le constructeur de l’activité sans toucher à la propriété Implementation.
  • Ecriture d’un code d’implémentation imbuvable et impossible à distinguer du reste de l’activité.
  • Ecriture de la méthode Get() de la propriété Implementation sans se soucier du Set().

Ces erreurs coutent chère, très chère. Pour les éviter ceci il suffit d’utiliser un Template de code similaire à celui-ci :

public class CompositeActivity : Activity
{
    // Délégué de l'implémentation
    private Func<Activity> m_Implementation;

    /// <summary>
    /// Implémentation
    /// </summary>
    protected override Func<Activity> Implementation
    {
        get
        {
            if (this.m_Implementation == null)
            {
                this.m_Implementation = this.GetImplementation;
            }
            return this.m_Implementation;
        }
        set
        {
            // Surtout ne rien coder ici, l’implémentation reste interne!
        }
    }

    /// <summary>
    /// Code impératif de l'implémentation
    /// </summary>
    /// <returns></returns>
    private Activity GetImplementation()
    {
        return new Sequence
            {
                Activities =
                {
                    new WriteLine { Text = "Hello" },
                    new WriteLine { Text = "World!" }
                }
            };
    }
}

Décortiquons ce code :

  1. La propriété Implementation est réécrite afin de rendre indisponible la méthode Set(). Logique, la définition de notre activité reste de la responsabilité de notre activité, pas du workflow qui va l’utiliser!
  2. Une variable interne m_Implementation a été déclarer afin d’optimiser les appels à la méthode GetImplementation() afin de ne pas instancier un délégué à chaque appel.
  3. La méthode GetImplemenation() ne se charge que de fournir l’implémentation de l’activité. Comme cela, pas de risque de s’emmêler les pinceaux quand l’activité serra devenue plus conséquente.

Mais pour les acharnés de l’optimisation ou de la qualité de code il est évidant qu’un petit readonly serrait le bienvenu et une petite méthode statique vue que l’on n’utilise pas d’arguments ;)

public class CompositeActivity : Activity
{
    // Délégué de l'implémentation
    private readonly Func<Activity> m_Implementation;

    public CompositeActivity()
    {
        this.m_Implementation = new Func<Activity>(GetImplementation);
    }

    /// <summary>
    /// Implémentation
    /// </summary>
    protected override Func<Activity> Implementation
    {
        get
        {
            return this.m_Implementation;
        }
        set
        {
            // ...
        }
    }

    /// <summary>
    /// Code impératide de l'implémentation
    /// </summary>
    /// <returns></returns>
    private static Activity GetImplementation()
    {
        return new Sequence
            {
                Activities =
                {
                    new WriteLine { Text = "Welcome" },
                    new WriteLine { Text = "in my" },
                    new WriteLine { Text = "World!" }
                }
            };
    }
}

Bien entendu, si votre activité a besoin d’arguments, il faudra modifier la méthode statique.

Par exemple :

public class CompositeActivity : Activity
{
    // Délégué de l'implémentation
    private readonly Func<Activity> m_Implementation;
    public InArgument<String> Text { get; set; }

    public CompositeActivity()
    {
        this.m_Implementation = new Func<Activity>(GetImplementation);
    }

    /// <summary>
    /// Implémentation
    /// </summary>
    protected override Func<Activity> Implementation
    {
        get
        {
            return this.m_Implementation;
        }
        set
        {
            // ...
        }
    }

    /// <summary>
    /// Code impératide de l'implémentation
    /// </summary>
    /// <returns></returns>
    private  Activity GetImplementation()
    {
        return new Sequence
            {
                Activities =
                {
                    new WriteLine { Text = new InArgument<String>(c=> String.Format("Welcome {0}", this.Text.Get(c))) },
                    new WriteLine { Text = "in my" },
                    new WriteLine { Text = "World!" }
                }
            };
    }
}

Et voila, j’espère avoir été claire ;)