Question

Quelle est la meilleure pratique pour définir plusieurs méthodes qui renvoient la même forme de données avec des filtres différents? Noms de méthodes explicites ou méthodes surchargées?

Par exemple. Si j'ai des produits et que je tire d'une base de données

manière explicite:

public List<Product> GetProduct(int productId) {    // return a List    }
public List<Product> GetProductByCategory(Category category) {    // return a List    }
public List<Product> GetProductByName(string Name ) {    // return a List    }

manière surchargée:

public List<Product> GetProducts() {    // return a List of all products    }
public List<Product> GetProducts(Category category) { // return a List by Category }
public List<Product> GetProducts(string searchString ) { // return a List by search string }

Je réalise que vous risquez de rencontrer un problème avec des signatures similaires , mais si vous transmettez des objets au lieu de types de base (chaîne, int, char, DateTime, etc.), ce sera moins problème. Alors ... est-ce une bonne idée de surcharger une méthode afin de réduire le nombre de méthodes existantes et pour plus de clarté, ou si chaque méthode qui filtre les données d'une manière différente a un nom de méthode différent ?

Était-ce utile?

La solution

Oui, la surcharge peut facilement être surchargée.

J'ai découvert que la clé pour déterminer si une surcharge est justifiée ou non est de prendre en compte l'audience - pas le compilateur, mais le programmeur de maintenance qui se présentera dans des semaines, des mois, des années et doit comprendre le code tente de réaliser.

Un nom de méthode simple, tel que GetProducts (), est clair et compréhensible, mais il en reste beaucoup à dire.

Dans de nombreux cas, si le paramètre transmis à GetProducts () porte bien son nom, le responsable de la maintenance sera en mesure de déterminer ce que fait la surcharge - mais cela repose sur une bonne discipline de nommage au point d'utilisation, ce que vous pouvez faire. t appliquer. Ce que vous pouvez appliquer, c'est le nom de la méthode qu'ils appellent.

La ligne directrice que je suis est de surcharger les méthodes si elles sont interchangeables, si elles font la même chose. Ainsi, peu importe la version invoquée par le consommateur de ma classe, car elle est équivalente.

Pour illustrer mon propos, j'utiliserais volontiers les surcharges pour une méthode DeleteFile ():

void DeleteFile(string filePath);
void DeleteFile(FileInfo file);
void DeleteFile(DirectoryInfo directory, string fileName);

Toutefois, pour vos exemples, j'utiliserais des noms distincts:

public IList<Product> GetProductById(int productId) {...}
public IList<Product> GetProductByCategory(Category category) {...}
public IList<Product> GetProductByName(string Name ) {...}

Avoir les noms complets rend le code plus explicite pour le responsable de la maintenance (qui pourrait bien être moi). Cela évite les problèmes liés aux collisions de signature:

// No collisions, even though both methods take int parameters
public IList<Employee> GetEmployeesBySupervisor(int supervisorId);
public IList<Employee> GetEmployeesByDepartment(int departmentId);

Il est également possible d'introduire la surcharge pour chaque objectif:

// Examples for GetEmployees

public IList<Employee> GetEmployeesBySupervisor(int supervisorId);
public IList<Employee> GetEmployeesBySupervisor(Supervisor supervisor);
public IList<Employee> GetEmployeesBySupervisor(Person supervisor);

public IList<Employee> GetEmployeesByDepartment(int departmentId);
public IList<Employee> GetEmployeesByDepartment(Department department);

// Examples for GetProduct

public IList<Product> GetProductById(int productId) {...}
public IList<Product> GetProductById(params int[] productId) {...}

public IList<Product> GetProductByCategory(Category category) {...}
public IList<Product> GetProductByCategory(IEnumerable<Category> category) {...}
public IList<Product> GetProductByCategory(params Category[] category) {...}

Le code est lu beaucoup plus qu’il n’est écrit - même si vous ne revenez jamais sur le code après la vérification initiale du contrôle de code source, vous lirez toujours cette ligne de code une douzaine de fois pendant que vous écrivez le code qui suit.

Enfin, sauf si vous écrivez du code jetable, vous devez autoriser d'autres personnes à appeler votre code depuis d'autres langues. Il semble que la plupart des systèmes d’entreprise finissent par rester dans la production bien au-delà de leur date d’utilisation. Il se peut que le code consommant votre classe en 2016 finisse par être écrit en VB.NET, C # 6.0, F # ou quelque chose de complètement nouveau qui n'a pas encore été inventé. Il se peut que la langue ne supporte pas les surcharges.

Autres conseils

Autant que je sache, vous n'aurez pas moins de méthodes, mais moins de noms. Je préfère généralement le système de nommage avec méthode surchargée, mais je ne pense pas que cela fasse vraiment une grande différence tant que vous commentez et documentez bien votre code (ce que vous devriez faire dans les deux cas).

Pouvez-vous en abuser? oui, c'est vrai.

Cependant, les exemples que vous avez donnés sont des exemples parfaits de l’utilisation de la surcharge de méthodes. Ils remplissent tous la même fonction, pourquoi leur attribuer des noms différents simplement parce que vous leur transmettez des types différents.

La règle principale est de faire la chose la plus claire, la plus facile à comprendre. N'utilisez pas la surcharge pour être malin ou intelligent, faites-le quand cela vous semble logique. D'autres développeurs pourraient également travailler sur ce code. Vous voulez leur faciliter le plus possible la lecture et la compréhension du code et pouvoir mettre en œuvre les modifications sans implémenter de bogues.

J'aime surcharger mes méthodes afin que, plus tard, dans l'intellisense, je ne dispose pas d'un million de méthodes identiques. Et il me semble plus logique de le surcharger simplement au lieu de le nommer différemment une douzaine de fois.

Une chose à considérer est que vous ne pouvez pas exposer les méthodes surchargées en tant que contrats d'opération dans un service Web WCF. Donc, si vous pensez avoir besoin de le faire, ce serait un argument pour utiliser des noms de méthodes différents.

Un autre argument en faveur de noms de méthode différents est qu’ils peuvent être plus facilement détectables avec intellisense.

Mais il y a des avantages et des inconvénients pour l'un ou l'autre choix: toute conception est un compromis.

Vous avez probablement besoin de normes couvrant l’ensemble du projet. Personnellement, je trouve les méthodes surchargées beaucoup plus faciles à lire. Si vous avez le support de l'IDE, allez-y.

Le point de surcharge est de faciliter la courbe d'apprentissage de quelqu'un qui utilise votre code ... et de vous permettre d'utiliser des schémas de nommage qui informent l'utilisateur du fonctionnement de la méthode.

Si vous disposez de dix méthodes différentes qui renvoient toutes une collection d'employés, le fait de générer dix noms différents (notamment s'ils commencent par des lettres différentes!) entraîne leur apparition sous forme d'entrées multiples dans le menu déroulant intellisense de vos utilisateurs, ce qui étend longueur de la liste déroulante et dissimulation de la distinction entre l’ensemble des dix méthodes qui renvoient toutes une collection d’employés et les autres méthodes de votre classe ...

Pensez à ce qui est déjà appliqué par le framework .Net pour, par exemple, les constructeurs et les indexeurs ... Ils sont tous obligés de porter le même nom, et vous ne pouvez créer de multiples qu'en les surchargeant ...

Si vous les surchargez, ils apparaîtront tous ensemble, avec leurs signatures et leurs commentaires disparates.

Vous ne devez pas surcharger deux méthodes si elles effectuent des fonctions différentes ou non liées ...

Quant à la confusion qui peut exister lorsque vous souhaitez surcharger deux méthodes avec la même signature par type comme dans

public List<Employee> GetEmployees(int supervisorId);
public List<Employee> GetEmployees(int departmentId); // Not Allowed !!

Vous pouvez créer des types distincts en tant qu'emballeurs pour le type de noyau incriminé afin de distinguer les signatures.

  public struct EmployeeId 
  { 
      private int empId;
      public int EmployeeId { get { return empId; } set { empId = value; } }
      public EmployeeId(int employeId) { empId = employeeId; }
  }

  public struct DepartmentId 
  {
   // analogous content
  }

 // Now it's fine, as the parameters are defined as distinct types...
 public List<Employee> GetEmployees(EmployeeId supervisorId);
 public List<Employee> GetEmployees(DepartmentId  departmentId);

Une autre option consiste à utiliser un objet de requête pour créer la "clause WHERE". Donc, vous n’auriez qu’une méthode comme celle-ci:

public List<Product> GetProducts(Query query)

L'objet Requête contient la condidition exprimée sous forme d'objet. GetProducts obtient la requête en " analysant " l'objet de requête.

http://martinfowler.com/eaaCatalog/queryObject.html

J'ai constaté une surcharge de la surcharge lorsque vous n’avez que des différences subtiles dans les arguments de la méthode. Par exemple:

public List<Product> GetProduct(int productId) { // return a List  }
public List<Product> GetProduct(int productId, int ownerId ) { // return a List  }
public List<Product> GetProduct(int productId, int vendorId, boolean printInvoice) { // return a List  }

Dans mon petit exemple, il devient difficile de savoir si le deuxième argument int doit être le propriétaire ou l'ID client.

Un bref coup d’œil sur le cadre devrait vous convaincre que de nombreuses surcharges sont acceptées. Face aux innombrables surcharges, la conception des surcharges destinées à faciliter l’utilisation est directement traitée dans la section 5.1.1 des Directives de conception de Microsoft Framework (Kwalina et Abrams, 2006). Voici un bref résumé de cette section:

  • DO essayez d'utiliser des noms de paramètres descriptifs pour indiquer la valeur par défaut utilisée par les surcharges plus courtes.

  • ÉVITER la variation arbitraire des noms de paramètres dans les surcharges.

  • ÉVITER d'être incohérent dans l'ordre des paramètres dans les membres surchargés.

  • DO ne créez que la surcharge virtuelle la plus longue (si une extensibilité est requise). Les surcharges plus courtes devraient simplement entraîner une surcharge plus longue.

  • NE PAS utiliser les paramètres ref ou out pour surcharger les membres.

  • DO autoriser la transmission de null à des arguments facultatifs.

  • FAIRE l'utilisation de la surcharge de membres plutôt que la définition de membres avec des arguments par défaut.

Oui, vous pouvez en abuser, mais voici un autre concept qui pourrait vous aider à en contrôler l’utilisation ...

Si vous utilisez .Net 3.5+ et que vous devez appliquer plusieurs filtres, il est probablement préférable d’utiliser IQueryable et d’enchaîner, par exemple

.
GetQuery<Type>().ApplyCategoryFilter(category).ApplyProductNameFilter(productName);

Ainsi, vous pourrez réutiliser la logique de filtrage à tout moment. "

public static IQueryable<T> ApplyXYZFilter(this IQueryable<T> query, string filter)
{
     return query.Where(XYZ => XYZ == filter);
} 

Vous pouvez utiliser la surcharge autant que vous le souhaitez. Du point de vue des meilleures pratiques également, il est recommandé d’utiliser la surcharge si vous essayez d’effectuer la même opération. (globalement) sur les données. Par exemple. getProduct ()

De plus, si vous voyez l'API Java, la surcharge est partout. Vous ne trouverez pas un plus grand soutien que cela.

La surcharge est un comportement polymorphe souhaitable. Cela aide le programmeur humain à se souvenir du nom de la méthode. Si explicit est redondant avec le paramètre type, il est mauvais. Si le paramètre type n'implique pas ce que la méthode est en train de faire, alors explicit commence à avoir un sens.

Dans votre exemple, getProductByName est le seul cas où explicite pourrait avoir un sens, car vous souhaiterez peut-être obtenir le produit avec une autre chaîne. Ce problème était dû à l'ambiguïté des types primitifs; getProduct (Nom n) pourrait être une meilleure solution de surcharge dans certains cas.

oui, vous pouvez en abuser. Dans votre exemple, il semblerait que les premier et troisième renverraient probablement un seul élément, tandis que le second en renverrait plusieurs. Si cela est correct, j'appellerais les premier et troisième GetProduct et les seconds GetProducts ou GetProductList

si ce n'est pas le cas et que les trois en renvoient plusieurs (comme si vous transmettez le productID 5, il renvoie tous les éléments avec 5 dans le productid, ou renvoie tous les éléments avec le paramètre string dans son nom), puis j'appellerais GetProducts ou GetProductList et remplacez-les tous.

Dans tous les cas, le nom doit refléter le rôle de la fonction. Par conséquent, l'appel de GetProduct (singulier) lorsqu'il renvoie une liste de produits ne constitue pas un bon nom de fonction. IMNSHO

Je suis un inconditionnel des "explicites". manière: attribuer à chaque fonction un nom différent. J'ai même refactoré du code contenant beaucoup de fonctions Add (...) , en AddRecord (enregistrement Const & amp;) , AddCell (const Cellule & amp;) , etc.

Je pense que cela permet d'éviter certaines confusions, les jets fortuits (au moins en C ++) et les avertissements du compilateur, et améliore la clarté.

Peut-être que dans certains cas, vous avez besoin de l’autre stratégie. Je n'en ai pas encore rencontré.

Que diriez-vous de

public IList<Product> GetProducts() { /* Return all. */}

public IList<Product> GetProductBy(int productId) {...}
public IList<Product> GetProductBy(Category category) {...}
public IList<Product> GetProductBy(string Name ) {...}

Et ainsi de suite?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top