juillet
2009
Apprenons le Design pattern Factory par un exemple.
Prenons un code développé sans Design pattern :
class Program
{
static void Main(string[] args)
{
Program p = new Program();p.RunShell();
}public void RunShell()
{
bool finished = false;while (!finished)
{
Console.Write(« > »);
String line = Console.ReadLine();
String[] lineArgs = line.Split(new char[] { ‘ ‘, ‘\t’ });switch (lineArgs[0])
{
case « exit »:
Console.WriteLine(« Bye bye »);
finished = true;
break;
case « help »:
Console.WriteLine(« Help : »);
Console.WriteLine(« ——« );
Console.WriteLine(« copy: copy a file »);
Console.WriteLine(« exit: exit the program »);
// …
// …
// …
break;
case « copy »:
if (lineArgs.Length == 3)
Console.WriteLine(String.Format(« copy {0} {1} », lineArgs[1], lineArgs[2]));
else
Console.WriteLine(« Invalid arguments: copy <source> <destination> »);
break;
// …
// …
// …
default:
Console.WriteLine(« Unknow command »);
break;
}}
}
}
Le principal problème de ce code, c’est que la class Program est le héro du code, c’est elle qui fait tout, et qu’au fur et a mesure de l’ajout de nouvelles commandes celle-ci va grossir, grossir, grossir … jusqu’à devenir inmaintenable. Il faudra réfactorer le code, voir le réécrire, et plus on attend et plus se sera compliqué.
Pour faire un découpage simple, efficace et donc éviter le héro nous pouvons utiliser le design pattern Factory pour cet exemple.
Interface de commandes
public interface ICommand
{
void Init(String[] args);
bool Execute(ref bool isFinished);String Name
{
get;
}
}
Implémentation de la commande Copy
public class Copy : ICommand
{
String[] m_args;#region ICommand Membres
public void Init(String[] args)
{
m_args = args;
}public bool Execute(ref bool isFinished)
{
if (m_args.Length != 3)
{
Console.WriteLine(« Invalid arguments: copy <source> <destination> »);
return false;}
Console.WriteLine(String.Format(« copy {0} {1} », m_args[1], m_args[2]));
return true;
}public string Name
{
get { return « copy »; }
}#endregion
}
Implémentation de la commande Exit
public class Exit : ICommand
{
#region ICommand Membrespublic void Init(String[] args)
{
}public bool Execute(ref bool isFinished)
{
Console.WriteLine(« Bye bye »);
isFinished = true;return true;
}public string Name
{
get { return « exit »; }
}#endregion
}
Implémentation de la commande Help
public class Help : ICommand
{
#region ICommand Membrespublic void Init(String[] args)
{
}public bool Execute(ref bool isFinished)
{
Console.WriteLine(« Help : »);
Console.WriteLine(« ——« );
Console.WriteLine(« copy: copy a file »);
Console.WriteLine(« exit: exit the program »);
// …
// …
// …return true;
}public string Name
{
get { return « help »; }
}#endregion
}
Implémentation de la Factory
class CommandFactory
{
private static List<ICommand> m_commands = new List<ICommand>
(
new ICommand[]
{
new Exit(),
new Copy(),
new Help()
}
);public static ICommand GetCommand(String line)
{
String[] tab = line.Split(new char[] { ‘ ‘, ‘\t’ });return GetCommand(tab);
}public static ICommand GetCommand(String[] args)
{
foreach (ICommand command in m_commands)
{
if (command.Name == args[0])
{
ICommand cmd = (ICommand)Activator.CreateInstance(command.GetType());
cmd.Init(args);return cmd;
}
}return null;
}
}
La class Program
class Program
{
static void Main(string[] args)
{
Program p = new Program();p.RunShell();
}private void RunShell()
{
bool finished = false;while (!finished)
{
Console.Write(« > »);
String line = Console.ReadLine();ICommand cmd = CommandFactory.GetCommand(line);
if (cmd != null)
cmd.Execute(ref finished);
else
Console.WriteLine(« Unknow command »);
}
}
}
Malgrè son nom, le héro est un anti pattern
J’avais effectivement pensée à cette explication. Le problème est qu’avec cette méthode la classe d’implémentation est obligé d’avoir un constructeur sans argument pour que cela soit faisable.
De plus même si dans le contexte de ton exemple cela importe peu, un design pattern est fait pour être massivement réutilisable non ? Dans le problème de la performance, devient un vrai problème
Une solution à ce problème est peut etre de passer par une « Factory » pour les commandes non ?
Avec tes précisions, c’est plus clair.
Si je devais faire cela en Java et si je ne me servais pas des instances de commandes créées initialement, j’aurai tendance à stocker les commandes dans une table de hachage (nom de la commande : classe de la commande) :
Map
Avec tes précisions, c’est plus clair.
Si je devais faire cela en Java et si je ne me servais pas des instances de commandes créées initialement, j’aurai tendance à stocker les commandes dans une table de hachage (nom de la commande -> classe de la commande) :
Map
il est vrai que l’utilisation de Activator.CreateInstance est discutable, mais je suis partie dans l’idée que le fait de rappeler l’instance de la liste de commande(m_command) peut être source d’erreur.
Je m’explique, si dans une commande, on utilise des propriétés privées, il faudra les remettrent a zéro dans la methode Init, ou créer une methode Reset dans icommand qui sera appeler avant le init.
Le développeur peut très facilement oublié de remettre une propriété privée a zéro.
Je pense que se genre d’erreur est très difficilement décelable même avec des TUs, c’est pour cela que j’ai choisi de créer une nouvelle instance a chaque fois.
De plus, la methode Activator.CreateInstance a beau être lente, dans le contexte du programme cela importe peu, l’utilisateur ne va pas taper 1000 commandes a la seconde
Juste une petite question : Pourquoi passer par un Activator.CreateInstance (qui utilise la Reflexion et on le sais tous maintenant, la Reflexion c’est mal !(r) et surtout c’est lent) alors que de toute façon tu as une instance de ICommand dans ta liste de commande m_commands et qu’en plus tu itères dessus ! J’ai raté un épisode ou il te suffirait de faire un command.Init(args) ?