Matthieu Mezil
L’objet, c’est beau
Le développement orienté objet peut être apparenté, osons le mot, à une forme d’art. Cependant, avant de devenir un « artiste » de l’objet, il y a plusieurs notions à maîtriser. A travers cet article, nous allons en aborder quelques-unes.
Par Matthieu Mezil publié le 27/04/2009 à 07:10, lu 2235 fois, 10 pages
 2 | V0
V0
Ma première démarche va consister à décomposer ma solution en deux couches : une couche UI et une couche Business.
Commençons par la couche Business. Il va falloir que j’expose à mon UI la liste des traitements disponibles. Souhaitant utiliser une liste générique, il me faut un type commun entre tous mes traitements. Dans le cas présent, une interface est le type commun idéal pour tous mes traitements.
De quoi avons-nous besoin dans notre interface ?
D’une méthode qui prenne en paramètre une collection de string (ce qui permet de m’abstraire de la notion de fichier) et qui me retourne une collection de string. Afin de pouvoir gérer tout type de collection il faut utiliser l’interface IEnumerable. L’interface IStringsTransform sera donc la suivante :

public interface IStringsTransform

{

    IEnumerable<string> Do(IEnumerable<string> lines);

}

Les deux traitements souhaités ont en commun le fait qu’ils s’appliquent ligne par ligne. Aussi, il est intéressant de se créer une classe de base pour factoriser ce qui peut l’être. Cette classe sera abstraite car elle ne correspondra pas à un traitement en tant que tel et implémentera l’interface IStringsTransform.

public abstract class PerStringTransformBase : IStringsTransform

{

    public IEnumerable<string> Do(IEnumerable<string> lines)

    {

        foreach (string line in lines)

            yield return Do(line);

    }

 

    protected abstract string Do(string line);

}

Maintenant, il faut coder les classes NumberPerStringTransform et InvertCharsPerStringTransform:

public class NumberPerStringTransform : PerStringTransformBase

{

    private int _cpt = 0;

 

    protected override string Do(string line)

    {

        return string.Concat((++_cpt).ToString("000"), " : ", line);

    }

}

 

public class InvertCharsPerStringTransform : PerStringTransformBase

{

    protected override string Do(string line)

    {

        char[] lineChars = line.ToCharArray();

        Array.Reverse(lineChars);

        return new string(lineChars);

    }

}

Nous aurions pu nous passer de la classe PerStringTransformBase et utiliser LINQ pour simplifier le code de NumberPerStringTransform et de InvertCharsPerStringTransform :

public class NumberPerStringTransform : IStringsTransform

{

    public IEnumerable<string> Do(IEnumerable<string> lines)

    {

        return lines.Select((l, index) => string.Concat((index + 1).ToString("000"), " : ", l));

    }

}

 

public class InvertCharsPerStringTransform : IStringsTransform

{

    public IEnumerable<string> Do(IEnumerable<string> lines)

    {

        return lines.Select(l => new string(l.Reverse().ToArray()));

    }

}

Cependant, pour des besoins pédagogiques (je rappelle que le but de cet article n’est pas de faire du LINQ mais de l’objet), nous allons conserver la classe PerStringTransformBase.
Il faut également coder la gestion des fichiers.
Pour la lecture, le fichier d’entrée pourrait être gros. Il n’est pas question de charger l’ensemble des lignes en mémoires et encore moins de doubler cette taille mémoire pour calculer la collection transformée. Dans ce cas là, l’utilisation du yield return est indispensable pour un code « propre ». Le yield return permet de « streamer » le traitement ce qui va se traduire dans notre cas pas le chargement d’une seule ligne en mémoire simultanément (une par une).

public static class FileUtil

{

    public static IEnumerable<string> GetLines(string filePath)

    {

        using (var sr = new StreamReader(filePath))

        {

            string line;

            while ((line = sr.ReadLine()) != null)

                yield return line;

        }

    }

 

    public static void WriteLines(string filePath, IEnumerable<string> lines)

    {

        using (var sw = new StreamWriter(filePath))

        {

            foreach (string line in lines)

                sw.WriteLine(line);

        }

    }

}

 

public static class FileTransform

{

    public static void Transform(string fileInPath, string fileOutPath, IStringsTransform transform)

    {

        FileUtil.WriteLines(fileOutPath, transform.Do(FileUtil.GetLines(fileInPath)));

    }

}

L’exécution en mode pas en pas peut être déroutante. En effet, la méthode WriteLines sera appelée avant la méthode Do, elle-même appelée avant la méthode GetLines. C’est un autre effect du yield return : le traitement est différé et ne s’exécute qu’à l’utilisation.
En dernier lieu, avant d’aller plus loin, il faut exposer la liste des traitements disponibles :

public static class Transforms

{

    private static List<IStringsTransform> _transforms;

    public static List<IStringsTransform> TransformsAllowed

    {

        get

        {

            if (_transforms == null)

                _transforms = new List<IStringsTransform>()

        {

            new NumberPerStringTransform(),

            new InvertCharsPerStringTransform()

        };

            return _transforms;

        }

    }

}

Maintenant côté UI, nous allons utiliser un ComboBox pour choisir le traitement.
Ce combo va être bindé sur Transforms.TransformsAllowed.
Ceci a un gros avantage : mon combo.SelectedItem sera directement un IStringsTransform.
Cependant, le premier problème identifiable immédiatement après l’exécution est le texte des items du combo.
Le texte utilisé par défaut est le résultat de la méthode ToString. Il est donc intéressant de redéfinir cette méthode pour nos deux traitements. Pour faire plus propre, le texte associé aux traitements sera stocké dans un fichier de ressources ce qui rendra en plus notre application facilement localisable.
Remarque : pour une application web, le ToString ne sera pas utilisé directement car la DropDownList n’a pas le même fonctionnement que la Combo.
Le deuxième problème est le suivant : si on lance deux fois de suite la numérotation des lignes sans relancer l’application, le compteur n’est pas réinitialisé vu qu’on utilise la même instance de NumberPerStringTransform. Ne souhaitant pas systématiquement réinitialiser le traitement (inutile pour l’inversion des caractères), il vaut mieux utiliser dans ce cas une méthode virtuelle ne faisant rien dans PerStringTransformBase plutôt qu’une méthode abstract.

public abstract class PerStringTransformBase : IStringsTransform

{

    public IEnumerable<string> Do(IEnumerable<string> lines)

    {

        Init();

        foreach (string line in lines)

            yield return Do(line);

    }

 

    protected abstract string Do(string line);

 

    protected virtual void Init()

    {

    }

}

 

public class NumberPerStringTransform : PerStringTransformBase

{

    private int _cpt = 0;

 

    protected override string Do(string line)

    {

        return string.Concat((++_cpt).ToString("000"), " : ", line);

    }

 

    protected override void Init()

    {

        base.Init();

        _cpt = 0;

    }

 

    public override string ToString()

    {

        return TransformResources.NumberPerStringTransform;

    }

}

 
» Démarrer une discussion
 
Discussion démarée par killscores le 28/04/2009 à 17:06, 3 commentaire(s).