ContextePolymorphisme par types génériques où les types ne partagent pas une classe de base
Ceci est une question de refactoring. J'ai un tas de méthodes qui ont plus ou moins exactement le même code mais qui agissent sur différents types. Il y a essentiellement une méthode par type et je veux les combiner tous en un qui peut utiliser un type générique.
Code actuel
Peut-être que le code ci-dessous vous aidera à comprendre ce que je suis en train -
Les méthodes suivantes diffèrent principalement dans le DbSet <> argument entité. À l'intérieur du code de méthode, ils utilisent principalement exactement les mêmes propriétés, mais dans une ou deux lignes, ils peuvent utiliser des propriétés qui ne sont pas partagées par les types d'entité. Par exemple, AccountId (à partir de l'entité Account) et CustomerId (à partir de l'entité Customer).
int? MethodToRefactor(DbSet<Account> entity, List someCollection, string[] moreParams)
{
int? keyValue = null;
foreach (var itemDetail in someCollection)
{
string refText = GetRefTextBySource(itemDetail, moreParams);
//Only the below two lines differ in all MethodToRefactor because they use entity's properties that are not shared by all entities
if (entity.Count(a => a.Name == refText) > 0)
keyValue = entity.Where(a => a.Name == refText).First().AccountId;
if (...some conditional code...)
break;
}
return keyValue;
}
int? MethodToRefactor(DbSet<Customer> entity, List someCollection, string[] moreParams)
{
int? keyValue = null;
foreach (var itemDetail in someCollection)
{
string refText = GetRefTextBySource(itemDetail, moreParams);
//Only the below two lines differ in all MethodToRefactor because they use entity's properties that are not shared by all entities
if (entity.Count(c => c.CustomerName == refText) > 0)
keyValue = entity.Where(c => c.CustomerName == refText).First().CustomerId;
if (...some conditional code...)
break;
}
return keyValue;
}
Ci-dessous le code qui appelle les méthodes ci-dessus -
void Caller()
{
foreach (var entity in EntityCollection)
{
if (entity.Name == "Account")
{
id = MethodToRefactor(db.Accounts,...);
}
else if (entity.Name == "Customer")
{
id = MethodToRefactor(db.Customers,...);
}
}
}
Problème
Ce n'est pas extensible pour une chose car il faut copier/coller une nouvelle MethodToRefactor pour chaque nouveau entité ajoutée. C'est difficile à maintenir aussi bien. Je peux peut-être refactoriser le code commun à tous les MethodToRefactors dans une méthode séparée et y faire un ifelse par entité, mais je fusionnerais fondamentalement l'appelant avec MethodToRefactor. Je suis à la recherche d'une solution plus simple avec des changements minimes dans la méthode Caller, comme décrit ci-dessous.
Idéal/désiré Code refactorisé
C'est un excellent candidat pour les types génériques/modèles. Comme on le voit ci-dessous, je peux changer l'entité actuelle pour qu'elle soit un T générique et passer les deux lignes qui n'utilisent pas les propriétés communes parmi les entités en tant qu'expressions/méthodes. Voici le pseudo-code de type C# qui illustre la solution idéale, mais je ne sais pas comment le faire réellement en C#.
int? MethodToRefactor<T>(DbSet<T> entity, Expression<Func<T, T> filterMethod,
Expression<Func<T, T> getIdMethod, List someCollection, string[] moreParams) where T : Account, Customer //This will fail
{
int? keyValue = null;
foreach (var itemDetail in someCollection)
{
string refText = GetRefTextBySource(itemDetail, moreParams);
if (filterMethod(entity) == true)
keyValue = getIdMethod(entity);
if (...some conditional code...)
break;
}
return keyValue;
}
void Caller()
{
foreach (var entity in EntityCollection)
{
if (entity.Name == "Account")
{
id = MethodToRefactor<Account>(db.Accounts,() => {entity.Count(a => a.Name == refText) > 0},() => {entity.Where(a => a.Name == refText).First().AccountId},...);
}
else if (entity.Name == "Customer")
{
id = MethodToRefactor<Customer>(db.Customer,() => {entity.Count(c => c.CustomerName == refText) > 0},() => {entity.Where(c => c.CustomerName == refText).First().CustomerId},...);
}
}
}
Avantages/objectifs atteints 1. Nous avons combiné tous MethodToRefactors en un seul et éliminé tout le code en double. 2. Nous avons éliminé les opérations spécifiques à l'entité à l'appelant. Ceci est important car cette logique est déplacée vers l'emplacement logique qui sait comment les différentes entités diffèrent les unes des autres (Caller avait une entité par entité ifelse pour commencer) et comment ces différences doivent être utilisées. 2. En déléguant le code spécifique d'entité à l'appelant, nous l'avons également rendu plus flexible, de sorte que nous n'avons pas besoin de créer un seul MethodToRefactor par logique spécifique à l'entité.
Note: Je ne suis pas un grand fan d'Adaptateur, Stratégie etc, je préfère les solutions qui peuvent atteindre ces objectifs en utilisant les fonctionnalités de langage C#. Cela ne veut pas dire que je suis anti-classique-design-patterns, c'est juste que je n'aime pas l'idée de créer un tas de nouvelles classes quand je peux le faire en refactorisant en quelques méthodes.
Merci. Je l'ai juste essayé et la 'var entity = entities.FirstOrDefault (filterExpression (refText));' line semble lancer l'erreur "Nom de la méthode attendue". Je ne suis pas sûr de ce qui le cause. – Achilles
c'est parce que la méthode donnée à FirstOrDefault n'a pas la bonne signature. FirstOrDefault besoin d'une méthode comme celle-ci Func, vous donnez Func . Ou cela peut être dû à votre expression. N'importe qui, corrigez-moi si je me trompe, je ne suis pas vraiment expérimenté avec les expressions pour l'instant. –
entities.FirstOrDefault (e => filterExpression (e, refText)) –