2010-03-19 4 views
4

Nous avons hérité d'un projet qui entoure une section du modèle métier principal.Suggestions pour utiliser cette méthode générique héritée

Une méthode prend un élément générique et trouve des éléments correspondant à ce type dans un membre, puis renvoie une liste de ce type.

public List<T> GetFoos<T>() 
{ 
    List<IFoo> matches = Foos.FindAll(
     f => f.GetType() == typeof(T) 
    ); 

    List<T> resultList = new List<T>(); 
    foreach (var match in matches) 
    { 
     resultList.Add((T)obj); 
    } 
} 

Foos peut contenir le même objet jeté dans différentes classes dans la hiérarchie d'héritage aux totaux agrégés différemment selon les présentations de l'interface utilisateur. Il y a 20 types différents de descendants qui peuvent être retournés par GetFoos.

Le code existant a essentiellement une grande instruction de commutateur copiée et collée dans le code. Le code dans chaque section appelle GetFoos avec son type correspondant.

Nous sommes en train de refactoriser cela dans une zone consolidée, mais comme nous le faisons, nous examinons d'autres façons de travailler avec cette méthode. Une pensée était d'utiliser la réflexion pour passer dans le type, et cela fonctionnait bien jusqu'à ce que nous nous rendions compte que l'Invoke renvoyait un objet, et qu'il devait être jeté d'une manière ou d'une autre à la liste <T>.

Un autre était d'utiliser simplement l'instruction switch jusqu'à la version 4.0, puis d'utiliser les options de langage dynamique.

Nous nous réjouissons de toute autre réflexion sur la façon dont nous pouvons travailler avec cette méthode. J'ai laissé le code assez court, mais si vous souhaitez connaître d'autres détails, n'hésitez pas à demander.

Mise à jour

L'instruction switch a été à l'origine au moyen de chaînes et le premier passage se déplaçait dans un présentateur avec quelque chose comme ceci: (refactorisation fait sur le commutateur, à l'endroit où les données sont allés).

// called on an event handler in FooPresenter 
// view is the interface for the ASPX page injected into FooPresenter's constructor 
// wrapper is the instance of the model wrapper that has the GetFoos method 
// selectedFooName is the value of a DropDownList in the Page 
// letting the user say how they want to see the animals 
// its either one big group (Animal) 
// or individual types (Tiger, Lion) 
private void LoadFoos(string selectedFooName) 
{ 
    switch (selectedFooName) 
    { 
     case "Animal": // abstract base class for all other types 
      this.view.SetData(this.wrapper.GetFoos<Animal>(); 

     case "Lion": 
      this.view.SetData(this.wrapper.GetFoos<Lion>(); 
      break; 

     case "Tiger": 
      this.view.SetData(this.wrapper.GetFoos<Tiger>();  
      break; 

     case "Bear": 
      this.view.SetData(this.wrapper.GetFoos<Bear>(); 
      break;  
    } 
} 

La mise en œuvre de vue (codebehind pour une page ASPX)

public void SetData<T>(List<T> data) 
{ 
    // there is a multiview on the page that contains user controls with 
    // grid layouts for the different types 

    // there is a control for the common type of "Animal" 
    // and other controls for Tiger, Bear, etc 

    // the controls contain a 3rd party grid of pain 
    // and the grids' binding event handlers cast the data item 
    // for the row and do some stuff with it specific to that type 
} 

Notre premier passage allait être au moins en utilisant le type dans l'instruction switch, ou l'ajout d'un ENUM.

J'ai joué avec l'utilisation du Pattern Stratégie mais j'ai dû arrêter quand je suis arrivé à l'usine de chargement en retournant la Liste et en réalisant que je n'avais pas le type.

+0

Pouvez-vous nous montrer votre déclaration de commutateur? Que fait-il réellement? –

Répondre

2

Il est difficile de voir le code appelant GetFoos() ... Si vous pouvez afficher plus de code décrivant comment cela est appelé, nous pouvons suggérer comment le refactoriser. Il semble que la solution consiste à faire de votre routine d'appel une routine générique, de sorte qu'elle puisse éviter le "basculement" autour des 20 types en utilisant simplement un seul type générique, spécifié au besoin. Cependant, cela peut ne pas être faisable, mais encore une fois, sans code, il est difficile de savoir ...

Cela étant dit, vous pouvez refactoring GetFoos être beaucoup plus simple:

public List<T> GetFoos<T>() 
{ 
    return Foos.OfType<T>().ToList(); 
} 

Edit: Comme le souligne Eric Lippert, le code ci-dessus renvoie tout type qui est un type de T, mais aussi des sous-classes de T.Bien que ce soit probablement le comportement qui serait réellement souhaité, il est différent du code original. Si cela est souhaitable pour une raison quelconque, vous pouvez, au lieu, utilisez:

public List<T> GetFoos<T>() 
{ 
    Type type = typeof(T); 
    return Foos.Where(item => item.GetType() == type).ToList(); 
} 

cela aura le même comportement le code d'origine.

+0

Vous avez dit la même chose. +1 –

+5

Notez que votre refactoring ne fait pas la même chose. Supposons que Foos contient des Lions, des Tigres et des Ours. Leur version, GetFoos retourne une liste vide car aucune de celles-ci n'est de * exactement * le type Feline. L'opération (probablement la plus raisonnable) de rendre une liste des Lions et des Tigres est la vôtre. Mon argument est qu'un refactoring qui introduit un changement sémantique n'est pas réellement un refactoring *. Dans ce cas, c'est probablement une correction de bogue. –

+0

@Eric: Merci pour le bon point. J'ai ajouté une section pour mentionner cela aussi. (Je crois que ma première exécution est probablement ce qui était prévu, mais vous avez absolument raison dans le fait que ce n'est pas un refactoring, car cela change le comportement du code existant ...) –

0

Quelque chose comme ça?

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Diagnostics; 

namespace SO_2480770 { 
    interface IFoo {} 
    class MyBase : IFoo {} 
    class Bar : MyBase {} 

    class Program {    
     IEnumerable<IFoo> Foos { get; set; } 

     static void Main(string[] args) {   
      List<MyBase> bases = new List<MyBase>() { new MyBase(), new MyBase() }; 
      List<Bar> bars = new List<Bar>() { new Bar(), new Bar() };    
      Program p = new Program(); 
      p.Foos = bases.Concat(bars);    
      var barsFromFoos = p.GetFoos<Bar>(); 
      var basesFromFoos = p.GetFoos<MyBase>(); 
      Debug.Assert(barsFromFoos.SequenceEqual(bars)); 
      Debug.Assert(basesFromFoos.SequenceEqual(bases.Concat(bars))); 
      Debug.Assert(!barsFromFoos.SequenceEqual(bases)); 
      Console.ReadLine(); 
     }    

     public List<T> GetFoos<T>() where T : IFoo {    
      return Foos.OfType<T>().ToList(); 
     } 
    }  
} 

Pour se débarrasser des grandes instructions de commutation, vous devez soit pousser les génériques vers le haut. C'EST À DIRE. faites en sorte que la méthode qui a l'instruction switch prenne un paramètre de type générique, et continuez jusqu'à ce que vous ne puissiez rien faire de plus, si vous devez remonter la chaîne d'appel. Lorsque cela devient trop difficile, pensez aux modèles de conception tels que l'usine abstraite, l'usine, les méthodes de modèle, etc. Cela dépend de la complexité du code d'appel.

Questions connexes