Je ne sais pas quel modèle de conception pourrait m'aider à résoudre ce problème.
J'ai une classe, 'Coordinator', qui détermine quelle classe Worker doit être utilisée - sans avoir à connaître tous les différents types de travailleurs qu'il y a - elle appelle simplement une WorkerFactory et agit sur l'interface IWorker commune.
Il définit ensuite le Worker approprié pour fonctionner et renvoie le résultat de sa méthode «DoWork».
Cela s'est bien passé ... jusqu'à maintenant; nous avons une nouvelle exigence pour une nouvelle classe Worker, "WorkerB" qui nécessite une quantité supplémentaire d'informations, c'est-à-dire un paramètre d'entrée supplémentaire, pour qu'elle puisse faire son travail.
C'est comme si nous avions besoin d'une méthode DoWork surchargée avec le paramètre d'entrée supplémentaire ... mais alors tous les Workers existants devraient implémenter cette méthode - ce qui semble faux car ces Workers n'ont vraiment pas besoin de cette méthode.
Comment puis-je refactoriser cela pour que le coordinateur ne sache pas quel travailleur est utilisé et qu'il permette toujours à chaque travailleur d'obtenir les informations dont il a besoin pour faire son travail mais qu'aucun travailleur ne fasse les choses dont il n'a pas besoin?
Il y a déjà beaucoup de travailleurs existants.
Je ne veux pas avoir à changer l'un des travailleurs en béton existants pour répondre aux exigences de la nouvelle classe WorkerB.
Je pensais que peut-être un motif de décorateur serait bien ici, mais je n'ai vu aucun décorateur décorer un objet avec la même méthode mais des paramètres différents auparavant ...
Situation dans le code:
public class Coordinator
{
public string GetWorkerResult(string workerName, int a, List<int> b, string c)
{
var workerFactor = new WorkerFactory();
var worker = workerFactor.GetWorker(workerName);
if(worker!=null)
return worker.DoWork(a, b);
else
return string.Empty;
}
}
public class WorkerFactory
{
public IWorker GetWorker(string workerName)
{
switch (workerName)
{
case "WorkerA":
return new ConcreteWorkerA();
case "WorkerB":
return new ConcreteWorkerB();
default:
return null;
}
}
}
public interface IWorker
{
string DoWork(int a, List<int> b);
}
public class ConcreteWorkerA : IWorker
{
public string DoWork(int a, List<int> b)
{
// does the required work
return "some A worker result";
}
}
public class ConcreteWorkerB : IWorker
{
public string DoWork(int a, List<int> b, string c)
{
// does some different work based on the value of 'c'
return "some B worker result";
}
public string DoWork(int a, List<int> b)
{
// this method isn't really relevant to WorkerB as it is missing variable 'c'
return "some B worker result";
}
}
la source
IWorker
interface est-elle répertoriée dans l'ancienne version ou s'agit-il d'une nouvelle version avec un paramètre ajouté?Coordinator
a déjà dû être modifié pour tenir compte de ce paramètre supplémentaire dans saGetWorkerResult
fonction - cela signifie que le principe ouvert-fermé de SOLID est violé. En conséquence, tous les appels de codeCoordinator.GetWorkerResult
devaient également être modifiés. Regardez donc l'endroit où vous appelez cette fonction: comment décidez-vous quel IWorker demander? Cela pourrait conduire à une meilleure solution.Réponses:
Vous aurez besoin de généraliser les arguments afin qu'ils tiennent dans un seul paramètre avec une interface de base et un nombre variable de champs ou de propriétés. Un peu comme ça:
Notez les vérifications nulles ... car votre système est flexible et à liaison tardive, il n'est pas non plus sûr pour le type, vous devrez donc vérifier votre conversion pour vous assurer que les arguments qui sont passés sont valides.
Si vous ne voulez vraiment pas créer d'objets concrets pour toutes les combinaisons possibles d'arguments, vous pouvez utiliser un tuple à la place (ce ne serait pas mon premier choix.)
la source
if (args == null) throw new ArgumentException();
Désormais, chaque consommateur d'un IWorker doit connaître son type de béton - et l'interface est inutile: vous pouvez également vous en débarrasser et utiliser les types de béton à la place. Et c'est une mauvaise idée, non?WorkerFactory.GetWorker
ne peut avoir qu'un seul type de retour). Bien qu'en dehors de la portée de cet exemple, nous savons que l'appelant est capable de trouver unworkerName
; on peut supposer qu'il peut également présenter des arguments appropriés.J'ai repensé la solution en fonction du commentaire de @ Dunk:
J'ai donc déplacé tous les arguments possibles nécessaires pour créer un IWorker dans la méthode IWorerFactory.GetWorker, puis chaque travailleur a déjà ce dont il a besoin et le coordinateur peut simplement appeler worker.DoWork ();
la source
Je suggérerais plusieurs choses.
Si vous souhaitez conserver l'encapsulation, afin que les sites d'appels n'aient rien à savoir sur le fonctionnement interne des travailleurs ou de la fabrique de travailleurs, vous devrez modifier l'interface pour avoir le paramètre supplémentaire. Le paramètre peut avoir une valeur par défaut, de sorte que certains sites d'appels peuvent toujours utiliser 2 paramètres. Cela nécessitera que toutes les bibliothèques consommatrices soient recompilées.
L'autre option que je recommanderais contre, car elle rompt l'encapsulation et est généralement mauvaise POO. Cela nécessite également que vous puissiez au moins modifier tous les appels pour
ConcreteWorkerB
. Vous pouvez créer une classe qui implémente l'IWorker
interface, mais possède également uneDoWork
méthode avec un paramètre supplémentaire. Ensuite, dans vos appels, essayez de lancer leIWorker
avecvar workerB = myIWorker as ConcreteWorkerB;
, puis utilisez les trois paramètresDoWork
sur le type concret. Encore une fois, c'est une mauvaise idée, mais c'est quelque chose que vous pourriez faire.la source
@Jtech, avez-vous envisagé d'utiliser l'
params
argument? Cela permet de transmettre une quantité variable de paramètres.https://msdn.microsoft.com/en-us/library/w5zay9db(v=vs.71).aspx
la source