J'ai eu une discussion intéressante aujourd'hui avec un autre développeur sur la façon d'aborder une classe avec une méthode qui accepte une chaîne et génère une chaîne.
Imaginez quelque chose comme ce qui suit qui est complètement composé à des fins d'exemple
public string GetStringPart(string input)
{
//Some input validation which is removed for clarity
if(input.Length > 5)
return input.Substring(0,1);
if(input.Substring(0,1) == "B")
return input.Substring(0,3);
return string.empty;
}
Une fonction qui a une logique basée sur son entrée de chaîne est ajoutée à un projet à l'aide de DI et a un conteneur DI en place. Souhaitez-vous ajouter cette nouvelle classe avec une interface et l'injecter si nécessaire, ou en feriez-vous une classe statique? Quels sont les avantages et les inconvénients de chacun? Pourquoi voudriez-vous (ou non) que ce quelque chose soit utilisé avec l'injection de constructeur plutôt que simplement accessible lorsque cela est requis n'importe où?.
Il n'y a aucune raison pour que cela soit injecté. C'est juste une fonction, elle n'a pas de dépendances, alors appelez-la simplement. Il peut même être statique si vous le souhaitez car il semble pur. On peut écrire des tests unitaires contre cela sans difficulté. S'il est utilisé dans d'autres classes, des tests unitaires peuvent toujours être écrits.
Il n'est pas nécessaire de faire abstraction des fonctions sans dépendances, c'est exagéré.
Si cela devient plus complexe, il est peut-être justifié de passer une interface à un constructeur ou à une méthode. Mais je n'irais pas dans cette direction à moins d'avoir une logique complexe de GetStringPart
basée sur l'emplacement, etc.
Voici pourquoi
class DOSClient {
OrderParser orderParser;
string orderCode;
DOSClient(OrderParser orderParser, string ordercode) {
this.orderParser = orderParser;
this.ordercode = ordercode;
}
void DisplayOrderCode() {
Console.Write( "Prefix: " + orderParser.GetStringPart(ordercode) );
...
}
}
class GUIClient {
OrderParser orderParser;
string orderCode;
GUI gui;
GUIClient(OrderParser orderParser, string ordercode, GUI gui) {
this.orderParser = orderParser;
this.ordercode = ordercode;
this.gui = gui;
}
void DisplayOrderCode() {
gui.Prefix( orderParser.GetStringPart(ordercode) );
...
}
}
class OrderParserUS : IOrderParser {
public string GetStringPart(string input)
{
//Some input validation which is removed for clarity
if(input.Length > 5)
return input.Substring(0,1);
if(input.Substring(0,1) == "B")
return input.Substring(0,3);
return string.empty;
}
}
class OrderParserEU : IOrderParser {
public string GetStringPart(string input)
{
//Some input validation which is removed for clarity
if(input.Length > 6)
return input.Substring(0,1);
if(input.Substring(0,1) == "#")
return input.Substring(0,3);
return string.empty;
}
}
Si vous aviez opté pour une méthode statique, il n'y aurait aucun moyen de modifier le comportement de GetStringPart
sans détruire l'ancien comportement ou le polluer avec une logique conditionnelle. Il est vrai que les statiques sont de mauvais globaux déguisés, mais le fait qu'ils désactivent le polymorphisme est ma principale plainte à leur sujet. Les méthodes statiques ne sont pas de première classe dans les langages OOP. En donnant à la méthode un objet dans lequel vivre, même sans état, nous rendons la méthode portable. Son comportement peut être transmis comme le valeur d'une variable.
Ici, j'ai imaginé un système qui doit se comporter légèrement différemment lorsqu'il est déployé en Europe puis aux États-Unis. Au lieu de cela, forcez l'un ou l'autre système à contenir du code uniquement nécessaire à l'autre, nous pouvons changer le comportement en contrôlant quel objet d'analyse syntaxique est injecté dans les clients. Cela nous permet de contenir la propagation du détail de la région. Il permet également d'ajouter facilement OrderParserCanada sans avoir à toucher les analyseurs existants.
Si cela ne signifie rien pour vous, il n'y a vraiment pas de bon argument pour cela.
BTW, GetStringPart
est un nom terrible.