Was in meiner aktuellen Arbeit ziemlich häufig vorkommt, ist, dass ein verallgemeinerter Prozess stattfinden muss, aber dann muss der seltsame Teil dieses Prozesses je nach Wert einer bestimmten Variablen etwas anders ablaufen, und das bin ich nicht Ich bin mir ziemlich sicher, was der eleganteste Weg ist, damit umzugehen.
Ich werde das Beispiel verwenden, das wir normalerweise haben und das die Dinge je nach Land, mit dem wir es zu tun haben, etwas anders macht.
Also habe ich eine Klasse, nennen wir es 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);
}
}
Abgesehen davon, dass nur einige dieser Maßnahmen für bestimmte Länder durchgeführt werden müssen. Beispielsweise benötigen nur 6 Länder den Kapitalisierungsschritt. Der zu teilende Charakter kann sich je nach Land ändern. Akzent ersetzen'e'
möglicherweise nur je nach Land erforderlich.
Offensichtlich könnten Sie es lösen, indem Sie so etwas tun:
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);
}
Aber wenn Sie mit allen möglichen Ländern der Welt zu tun haben, wird das sehr umständlich. Und trotzdem dieif
erschweren Anweisungen das Lesen der Logik (zumindest, wenn Sie sich eine komplexere Methode als das Beispiel vorstellen), und die zyklomatische Komplexität steigt ziemlich schnell an.
Im Moment mache ich so etwas:
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);
}
}
Handler:
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; }
}
Was ich auch nicht wirklich sicher bin, ob ich es mag. Die Logik wird durch die gesamte Factory-Erstellung immer noch etwas verdeckt, und Sie können nicht einfach die ursprüngliche Methode betrachten und sehen, was passiert, wenn beispielsweise ein "GBR" -Prozess ausgeführt wird. Sie landen auch viele Klassen zu schaffen (in komplexeren Beispielen als diese) im Stil GbrPunctuationHandler
, UsaPunctuationHandler
etc ... was bedeutet , dass Sie aus allen möglichen Aktionen an mehreren verschiedenen Klassen Abbildung aussehen müssen , die während der Zeichensetzung passieren könnte Handhabung. Offensichtlich will ich keine Riesenklasse mit einer Milliardeif
Aussagen, aber ebenso 20 Klassen mit leicht unterschiedlicher Logik fühlen sich auch klobig an.
Grundsätzlich denke ich, dass ich mich in eine Art OOP-Knoten verwickelt habe und keine gute Möglichkeit kenne, ihn zu entwirren. Ich habe mich gefragt, ob es da draußen ein Muster gibt, das bei dieser Art von Prozess helfen würde.
PreProcess
Funktionalität, die je nach Land unterschiedlich implementiert werden könnte,DetermineSeparator
für alle verfügbar sein kann, und aPostProcess
. Alle von ihnen könnenprotected virtual void
mit einer Standardimplementierung sein, und dann können Sie spezifischeProcessors
pro Land habenif (country == "DEU")
Sie zu überprüfenif (config.ShouldRemovePunctuation)
.country
eine Zeichenfolge eher eine Instanz einer Klasse, die diese Optionen modelliert?Antworten:
Ich würde vorschlagen, alle Optionen in einer Klasse zusammenzufassen:
und geben Sie es in die
Process
Methode:quelle
CountrySpecificHandlerFactory
bin mir nicht sicher, warum so etwas nicht versucht wurde, bevor ich zu ... o_0public class ProcessOptions
sollte eigentlich nur sein[Flags] enum class ProcessOptions : int { ... }
...ProcessOptions
. Sehr angenehm.Als sich das .NET Framework auf diese Art von Problemen einließ, modellierte es nicht alles als
string
. So haben Sie zum Beispiel dieCultureInfo
Klasse :Diese Klasse enthält möglicherweise nicht die spezifischen Funktionen, die Sie benötigen, aber Sie können natürlich etwas Analoges erstellen. Und dann ändern Sie Ihre
Process
Methode:Ihre
CountryInfo
Klasse kann dann einebool RequiresCapitalization
Eigenschaft usw. haben, die IhrerProcess
Methode hilft , ihre Verarbeitung entsprechend zu steuern.quelle
Vielleicht könntest du einen haben
Processor
pro Land haben?Und eine Basisklasse für allgemeine Teile der Verarbeitung:
Außerdem sollten Sie Ihre Rückgabetypen überarbeiten, da sie nicht so kompiliert werden, wie Sie sie geschrieben haben. Manchmal gibt eine
string
Methode nichts zurück.quelle
Sie können eine gemeinsame Schnittstelle mit einer
Process
Methode erstellen ...Dann implementieren Sie es für jedes Land ...
Sie können dann eine gemeinsame Methode zum Instanziieren und Ausführen jeder länderbezogenen Klasse erstellen ...
Dann müssen Sie nur noch die Prozessoren wie folgt erstellen und verwenden ...
Hier ist ein funktionierendes Dotnet-Geigen-Beispiel ...
Sie platzieren die gesamte länderspezifische Verarbeitung in jeder Länderklasse. Erstellen Sie eine gemeinsame Klasse (in der Processing-Klasse) für alle tatsächlichen Einzelmethoden, sodass jeder Länderprozessor zu einer Liste anderer allgemeiner Aufrufe wird, anstatt den Code in jede Länderklasse zu kopieren.
Hinweis: Sie müssen hinzufügen ...
Damit die statische Methode eine Instanz der Länderklasse erstellt.
quelle
Process
die Länderzeichenfolge aus jedem Aufruf von entfernen und stattdessen einmal verwenden, um den richtigen IP-Prozessor zu erhalten? Normalerweise verarbeiten Sie viel Text gemäß den Regeln desselben Landes.Process("GBR", "text");
, wird die statische Methode ausgeführt, die eine Instanz des GBR-Prozessors erstellt, und die Process-Methode wird darauf ausgeführt. Es wird nur in einer Instanz für diesen bestimmten Landestyp ausgeführt.Vor einigen Versionen erhielt der C # -Swtich die volle Unterstützung für den Mustervergleich . Damit der Fall "mehrere Länder übereinstimmen" leicht gemacht werden kann. Während es noch keine Durchfallfähigkeit gibt, kann eine Eingabe mehrere Fälle mit Musterabgleich abgleichen. Es könnte das If-Spam vielleicht etwas klarer machen.
Npw ein Schalter kann normalerweise durch eine Sammlung ersetzt werden. Sie müssen Delegaten und ein Wörterbuch verwenden. Prozess kann durch ersetzt werden.
Dann könnten Sie ein Wörterbuch erstellen:
Ich habe functionNames verwendet, um den Delegaten abzugeben. Sie können jedoch die Lambda-Syntax verwenden, um den gesamten Code dort bereitzustellen. Auf diese Weise können Sie die gesamte Sammlung wie jede andere große Sammlung ausblenden. Und der Code wird zu einer einfachen Suche:
Das sind so ziemlich die beiden Optionen. Möglicherweise möchten Sie für die Zuordnung Aufzählungen anstelle von Zeichenfolgen verwenden, dies ist jedoch ein kleines Detail.
quelle
Ich würde vielleicht (abhängig von den Details Ihres Anwendungsfalls) das
Country
"echte" Objekt anstelle einer Zeichenfolge verwenden. Das Schlüsselwort lautet "Polymorphismus".Im Grunde würde es so aussehen:
Dann können Sie spezialisierte Länder für diejenigen erstellen, die Sie benötigen. Hinweis: Sie müssen nicht
Country
für alle Länder ein Objekt erstellen , das Sie habenLatinlikeCountry
können oder sogarGenericCountry
. Dort können Sie sammeln, was getan werden soll, und sogar andere wiederverwenden, wie zum Beispiel:O.ä.
Country
VielleichtLanguage
bin ich mir über den Anwendungsfall nicht sicher, aber ich verstehe , worum es geht.Außerdem sollte die Methode natürlich nicht die
Process()
sein, die Sie tatsächlich tun müssen. WieWords()
oder was auch immer.quelle
Sie möchten an etwas delegieren (an die Verantwortungskette nicken), das über seine eigene Kultur Bescheid weiß. Verwenden oder erstellen Sie also ein Konstrukt vom Typ Country oder CultureInfo, wie oben in anderen Antworten erwähnt.
Aber im Allgemeinen und im Grunde genommen besteht Ihr Problem darin, dass Sie prozedurale Konstrukte wie 'Prozessor' nehmen und sie auf OO anwenden. Bei OO geht es darum, reale Konzepte aus einer Geschäfts- oder Problemdomäne in Software darzustellen. Der Prozessor übersetzt in nichts in der realen Welt außer der Software selbst. Wann immer Sie Klassen wie Prozessor oder Manager oder Gouverneur haben, sollten Alarmglocken läuten.
quelle
Verantwortungskette ist die Art von Dingen, nach denen Sie vielleicht suchen, aber in OOP ist es etwas umständlich ...
Was ist mit einem funktionaleren Ansatz mit C #?
HINWEIS: Es muss natürlich nicht alles statisch sein. Wenn die Prozessklasse den Status benötigt, können Sie eine instanziierte Klasse oder eine teilweise angewendete Funktion verwenden;).
Sie können den Prozess für jedes Land beim Start erstellen, jedes in einer indizierten Sammlung speichern und bei Bedarf mit O (1) -Kosten abrufen.
quelle
Ich würde einfach Routinen implementieren
Capitalise
,RemovePunctuation
usw. als Teilprozesse , die mit einem messaged werden kanntext
undcountry
Parameter, und würde einen verarbeiteten Text zurückzukehren.Verwenden Sie Wörterbücher, um Länder zu gruppieren, die einem bestimmten Attribut entsprechen (wenn Sie Listen bevorzugen, funktioniert dies auch mit nur geringen Leistungskosten). Zum Beispiel:
CapitalisationApplicableCountries
undPunctuationRemovalApplicableCountries
.quelle
Ich bin der Meinung, dass die Informationen über die Länder in Daten und nicht im Code gespeichert werden sollten. Anstelle einer CountryInfo-Klasse oder eines CapitalizationApplicableCountries-Wörterbuchs könnten Sie also eine Datenbank mit einem Datensatz für jedes Land und einem Feld für jeden Verarbeitungsschritt haben, und dann könnte die Verarbeitung die Felder für ein bestimmtes Land durchlaufen und entsprechend verarbeiten. Die Wartung erfolgt dann hauptsächlich in der Datenbank, wobei neuer Code nur benötigt wird, wenn neue Schritte erforderlich sind, und die Daten in der Datenbank für Menschen lesbar sind. Dies setzt voraus, dass die Schritte unabhängig sind und sich nicht gegenseitig stören. Wenn das nicht so ist, sind die Dinge kompliziert.
quelle