Quelque chose qui revient beaucoup dans mon travail actuel est qu'il y a un processus généralisé qui doit se produire, mais ensuite la partie étrange de ce processus doit se produire légèrement différemment selon la valeur d'une certaine variable, et je ne suis pas Je suis sûr que c'est la façon la plus élégante de gérer cela.
Je vais utiliser l'exemple que nous avons habituellement, qui fait les choses légèrement différemment selon le pays avec lequel nous traitons.
J'ai donc une classe, appelons-la Processor
:
public class Processor
{
public string Process(string country, string text)
{
text.Capitalise();
text.RemovePunctuation();
text.Replace("é", "e");
var split = text.Split(",");
string.Join("|", split);
}
}
Sauf que seules certaines de ces actions doivent se produire pour certains pays. Par exemple, seuls 6 pays nécessitent l'étape de capitalisation. Le personnage sur lequel diviser peut changer selon le pays. Le remplacement de l'accentuation 'e'
peut uniquement être requis en fonction du pays.
Évidemment, vous pouvez le résoudre en faisant quelque chose comme ceci:
public string Process(string country, string text)
{
if (country == "USA" || country == "GBR")
{
text.Capitalise();
}
if (country == "DEU")
{
text.RemovePunctuation();
}
if (country != "FRA")
{
text.Replace("é", "e");
}
var separator = DetermineSeparator(country);
var split = text.Split(separator);
string.Join("|", split);
}
Mais lorsque vous traitez avec tous les pays possibles dans le monde, cela devient très lourd. Et peu importe, les if
instructions rendent la logique plus difficile à lire (du moins, si vous imaginez une méthode plus complexe que l'exemple), et la complexité cyclomatique commence à monter assez rapidement.
Donc en ce moment je fais en quelque sorte quelque chose comme ça:
public class Processor
{
CountrySpecificHandlerFactory handlerFactory;
public Processor(CountrySpecificHandlerFactory handlerFactory)
{
this.handlerFactory = handlerFactory;
}
public string Process(string country, string text)
{
var handlers = this.handlerFactory.CreateHandlers(country);
handlers.Capitalier.Capitalise(text);
handlers.PunctuationHandler.RemovePunctuation(text);
handlers.SpecialCharacterHandler.ReplaceSpecialCharacters(text);
var separator = handlers.SeparatorHandler.DetermineSeparator();
var split = text.Split(separator);
string.Join("|", split);
}
}
Gestionnaires:
public class CountrySpecificHandlerFactory
{
private static IDictionary<string, ICapitaliser> capitaliserDictionary
= new Dictionary<string, ICapitaliser>
{
{ "USA", new Capitaliser() },
{ "GBR", new Capitaliser() },
{ "FRA", new ThingThatDoesNotCapitaliseButImplementsICapitaliser() },
{ "DEU", new ThingThatDoesNotCapitaliseButImplementsICapitaliser() },
};
// Imagine the other dictionaries like this...
public CreateHandlers(string country)
{
return new CountrySpecificHandlers
{
Capitaliser = capitaliserDictionary[country],
PunctuationHanlder = punctuationDictionary[country],
// etc...
};
}
}
public class CountrySpecificHandlers
{
public ICapitaliser Capitaliser { get; private set; }
public IPunctuationHanlder PunctuationHanlder { get; private set; }
public ISpecialCharacterHandler SpecialCharacterHandler { get; private set; }
public ISeparatorHandler SeparatorHandler { get; private set; }
}
Et je ne suis pas vraiment sûr d’aimer. La logique est encore quelque peu obscurcie par toute la création d'usine et vous ne pouvez pas simplement regarder la méthode d'origine et voir ce qui se passe lorsqu'un processus "GBR" est exécuté, par exemple. Vous finissez également par créer beaucoup de classes (dans des exemples plus complexes que celui-ci) dans le style GbrPunctuationHandler
, UsaPunctuationHandler
etc ... ce qui signifie que vous devez regarder plusieurs classes différentes pour comprendre toutes les actions possibles qui pourraient se produire pendant la ponctuation manipulation. Évidemment, je ne veux pas d'une classe géante avec un milliard d' if
instructions, mais 20 classes avec une logique légèrement différente me semblent également maladroites.
Fondamentalement, je pense que je me suis retrouvé dans une sorte de nœud OOP et je ne sais pas très bien comment le démêler. Je me demandais s'il y avait un modèle qui pourrait aider avec ce type de processus?
PreProcess
fonctionnalité, qui pourraient être mises en œuvre différemment selon certains pays,DetermineSeparator
peuvent être là pour tous, et unPostProcess
. Tous peuvent êtreprotected virtual void
avec une implémentation par défaut, et vous pouvez alors avoir unProcessors
pays spécifiqueif (country == "DEU")
vous vérifierif (config.ShouldRemovePunctuation)
.country
une chaîne plutôt qu'une instance d'une classe qui modélise ces options?Réponses:
Je suggère d'encapsuler toutes les options dans une seule classe:
et passez-le dans la
Process
méthode:la source
CountrySpecificHandlerFactory
... o_0public class ProcessOptions
devrait vraiment être juste[Flags] enum class ProcessOptions : int { ... }
...ProcessOptions
. Très pratique.Lorsque le framework .NET a tenté de gérer ce type de problèmes, il n'a pas tout modélisé comme
string
. Vous avez donc, par exemple, laCultureInfo
classe :Maintenant, cette classe peut ne pas contenir les fonctionnalités spécifiques dont vous avez besoin, mais vous pouvez évidemment créer quelque chose d'analogue. Et puis vous changez de
Process
méthode:Votre
CountryInfo
classe peut alors avoir unebool RequiresCapitalization
propriété, etc., qui aide votreProcess
méthode à diriger son traitement de manière appropriée.la source
Peut-être pourriez-vous en avoir un
Processor
par pays?Et une classe de base pour gérer les parties communes du traitement:
De plus, vous devez retravailler vos types de retour car ils ne se compileront pas comme vous les avez écrits - parfois une
string
méthode ne retourne rien.la source
Vous pouvez créer une interface commune avec une
Process
méthode ...Ensuite, vous l'implémentez pour chaque pays ...
Vous pouvez ensuite créer une méthode commune pour instancier et exécuter chaque classe liée au pays ...
Ensuite, il vous suffit de créer et d'utiliser les processeurs comme ça ...
Voici un exemple de violon dotnet fonctionnel ...
Vous placez tous les traitements spécifiques au pays dans chaque classe de pays. Créez une classe commune (dans la classe Processing) pour toutes les méthodes individuelles réelles, afin que chaque processeur de pays devienne une liste d'autres appels communs, plutôt que de copier le code dans chaque classe de pays.
Remarque: vous devrez ajouter ...
afin que la méthode statique crée une instance de la classe country.
la source
Process
, et l'utiliser à la place une fois pour obtenir le bon processeur IP? Vous traitez généralement beaucoup de texte selon les règles du même pays.Process("GBR", "text");
il exécute la méthode statique qui crée une instance du processeur GBR et exécute la méthode Process sur celle-ci. Il ne l'exécute que sur une seule instance, pour ce type de pays spécifique.Il y a quelques versions, le swtich C # a été entièrement pris en charge pour la correspondance de modèles . De sorte que le cas "plusieurs pays correspondent" est facile à faire. Bien qu'il n'ait toujours pas de capacité de chute, une entrée peut faire correspondre plusieurs cas avec la correspondance de motifs. Cela pourrait peut-être rendre ce spam si un peu plus clair.
Npw un commutateur peut généralement être remplacé par une collection. Vous devez utiliser des délégués et un dictionnaire. Le processus peut être remplacé par.
Ensuite, vous pouvez créer un dictionnaire:
J'ai utilisé functionNames pour remettre le délégué. Mais vous pouvez utiliser la syntaxe Lambda pour y fournir l'intégralité du code. De cette façon, vous pouvez simplement masquer cette collection entière comme vous le feriez pour toute autre grande collection. Et le code devient une simple recherche:
Ce sont à peu près les deux options. Vous pouvez envisager d'utiliser des énumérations au lieu de chaînes pour la correspondance, mais c'est un détail mineur.
la source
J'irais peut-être (selon les détails de votre cas d'utilisation) avec l'
Country
idée d'être un "vrai" objet au lieu d'une chaîne. Le mot clé est "polymorphisme".Donc, fondamentalement, cela ressemblerait à ceci:
Ensuite, vous pouvez créer des pays spécialisés pour ceux dont vous avez besoin. Remarque: vous n'avez pas à créer d'
Country
objet pour tous les pays, vous pouvez en avoirLatinlikeCountry
, ou mêmeGenericCountry
. Là, vous pouvez collecter ce qui doit être fait, même en réutiliser d'autres, comme:Ou similaire.
Country
peut-être en faitLanguage
, je ne suis pas sûr du cas d'utilisation, mais je vous comprends.En outre, la méthode ne doit pas être bien sûr,
Process()
elle devrait être la chose que vous devez réellement faire. CommeWords()
ou peu importe.la source
Vous voulez déléguer (signe de tête à la chaîne de responsabilité) quelque chose qui connaît sa propre culture. Donc, utilisez ou créez une construction de type Country ou CultureInfo, comme mentionné ci-dessus dans d'autres réponses.
Mais en général et fondamentalement, votre problème est que vous prenez des constructions procédurales comme «processeur» et les appliquez à OO. OO consiste à représenter des concepts du monde réel à partir d'un domaine commercial ou problématique dans un logiciel. Le processeur ne se traduit par rien dans le monde réel à part le logiciel lui-même. Chaque fois que vous avez des cours comme Processor, Manager ou Governor, les sonneries d'alarme devraient sonner.
la source
La chaîne de responsabilité est le genre de chose que vous recherchez peut-être, mais dans la POO, c'est un peu lourd ...
Qu'en est-il d'une approche plus fonctionnelle avec C #?
REMARQUE: il ne doit pas nécessairement être entièrement statique. Si la classe Process a besoin d'un état, vous pouvez utiliser une classe instanciée ou une fonction partiellement appliquée;).
Vous pouvez créer le processus pour chaque pays au démarrage, stocker chacun dans une collection indexée et les récupérer en cas de besoin avec un coût O (1).
la source
Je mettre en œuvre simplement des routines
Capitalise
,RemovePunctuation
etc. comme sous - processus qui peut être messaged avectext
etcountry
paramètres, et je renvoie un texte traité.Utilisez des dictionnaires pour regrouper les pays qui correspondent à un attribut spécifique (si vous préférez les listes, cela fonctionnerait également avec seulement un faible coût de performance). Par exemple:
CapitalisationApplicableCountries
etPunctuationRemovalApplicableCountries
.la source
Je pense que les informations sur les pays devraient être conservées dans des données, pas dans du code. Ainsi, au lieu d'une classe CountryInfo ou d'un dictionnaire CapitalisationApplicableCountries, vous pourriez avoir une base de données avec un enregistrement pour chaque pays et un champ pour chaque étape de traitement, puis le traitement pourrait parcourir les champs d'un pays donné et traiter en conséquence. La maintenance est alors principalement dans la base de données, avec un nouveau code uniquement nécessaire lorsque de nouvelles étapes sont nécessaires, et les données peuvent être lisibles par l'homme dans la base de données. Cela suppose que les étapes sont indépendantes et n'interfèrent pas entre elles; si ce n'est pas le cas, les choses sont compliquées.
la source