J'ai une fonction qui retourne le même type d'objets (résultats de requête) mais sans propriétés ni méthodes en commun. Afin d'avoir un type commun, j'ai recouru en utilisant une interface vide comme type de retour et "implémenté" cela sur les deux.
Cela ne semble pas juste bien sûr. Je ne peux que me consoler en m'accrochant à espérer qu'un jour ces classes auront quelque chose en commun et je déplacerai cette logique commune vers mon interface vide. Pourtant, je ne suis pas satisfait et je me demande si je devrais avoir deux méthodes différentes et appeler sous condition ensuite. Serait-ce une meilleure approche?
On m'a également dit que .NET Framework utilise des interfaces vides à des fins de balisage.
Ma question est: une interface vide est-elle un signe fort d'un problème de conception ou est-elle largement utilisée?
[~ # ~] edit [~ # ~] : Pour les personnes intéressées, j'ai découvert plus tard que les unions discriminées dans les langages fonctionnels sont la solution parfaite pour ce que J'essayais de réussir. C # ne semble pas encore compatible avec ce concept.
[~ # ~] modifier [~ # ~] : j'ai écrit un morceau plus long à propos de ce problème, expliquant le problème et la solution en détail.
Bien qu'il semble qu'il existe un modèle de conception (beaucoup ont mentionné "interface marqueur" maintenant) pour ce cas d'utilisation, je pense que l'utilisation d'une telle pratique est une indication d'une odeur de code (la plupart du temps au moins).
Comme l'a signalé @ V4Vendetta, il existe une règle d'analyse statique qui cible cela: http://msdn.Microsoft.com/en-us/library/ms182128 (v = VS.100) .aspx
Si votre conception comprend des interfaces vides que les types sont censés implémenter, vous utilisez probablement une interface comme marqueur ou un moyen d'identifier un groupe de types. Si cette identification se produit au moment de l'exécution, la bonne façon d'y parvenir est d'utiliser un attribut personnalisé. Utilisez la présence ou l'absence de l'attribut, ou les propriétés de l'attribut, pour identifier les types cibles. Si l'identification doit se produire au moment de la compilation, alors il est acceptable d'utiliser une interface vide.
Voici la recommandation MSDN citée:
Supprimez l'interface ou ajoutez-y des membres. Si l'interface vide est utilisée pour étiqueter un ensemble de types, remplacez l'interface par un attribut personnalisé.
Cela reflète également la section Critique du lien wikipedia déjà publié.
Un problème majeur avec les interfaces de marqueur est qu'une interface définit un contrat pour implémenter des classes et que ce contrat est hérité par toutes les sous-classes. Cela signifie que vous ne pouvez pas "supprimer la mise en œuvre" d'un marqueur. Dans l'exemple donné, si vous créez une sous-classe que vous ne voulez pas sérialiser (peut-être parce qu'elle dépend de l'état transitoire), vous devez recourir à la levée explicite de NotSerializableException (par document ObjectOutputStream).
Vous déclarez que votre fonction "renvoie des objets entièrement différents en fonction de certains cas" - mais à quel point sont-ils différents? Pourrait-on être un écrivain de flux, un autre une classe d'interface utilisateur, un autre un objet de données? Non ... j'en doute!
Vos objets peuvent ne pas avoir de méthodes ou de propriétés communes, cependant, ils sont probablement semblables dans leur rôle ou leur utilisation. Dans ce cas, une interface marqueur semble tout à fait appropriée.
S'il n'est pas utilisé comme interface marqueur , je dirais que oui, c'est une odeur de code.
Une interface définit un contrat auquel l'implémentateur adhère - si vous avez des interfaces vides sur lesquelles vous n'utilisez pas de réflexion (comme c'est le cas avec les interfaces de marqueur), vous pouvez aussi bien utiliser Object
que le (déjà existant ) type de base.
Vous avez répondu à votre propre question ... "J'ai une fonction qui renvoie des objets entièrement différents en fonction de certains cas." ... Pourquoi voudriez-vous avoir la même fonction qui renvoie des objets complètement différents? Je ne vois aucune raison pour que cela soit utile, peut-être en avez-vous une bonne, auquel cas, veuillez la partager.
EDIT: Compte tenu de votre clarification, vous devez en effet utiliser une interface de marqueur. "complètement différent" est très différent de "sont du même genre". S'ils étaient complètement différents (pas seulement qu'ils n'ont pas de membres partagés), ce serait une odeur de code.
Comme beaucoup l'ont probablement déjà dit, une interface vide a une utilisation valable comme "interface marqueur".
La meilleure utilisation à laquelle je peux penser est probablement de désigner un objet comme appartenant à un sous-ensemble particulier du domaine, géré par un référentiel correspondant. Supposons que vous disposez de différentes bases de données à partir desquelles vous récupérez des données et que vous disposez d'une implémentation de référentiel pour chacune. Un référentiel particulier ne peut gérer qu'un sous-ensemble et ne doit pas recevoir d'instance d'un objet d'un autre sous-ensemble. Votre modèle de domaine pourrait ressembler à ceci:
//Every object in the domain has an identity-sourced Id field
public interface IDomainObject
{
long Id{get;}
}
//No additional useful information other than this is an object from the user security DB
public interface ISecurityDomainObject:IDomainObject {}
//No additional useful information other than this is an object from the Northwind DB
public interface INorthwindDomainObject:IDomainObject {}
//No additional useful information other than this is an object from the Southwind DB
public interface ISouthwindDomainObject:IDomainObject {}
Vos référentiels peuvent ensuite être rendus génériques à ISecurityDomainObject, INorthwindDomainObject et ISouthwindDomainObject, et vous avez ensuite une vérification au moment de la compilation que votre code n'essaie pas de passer un objet Security à la base de données Northwind (ou toute autre permutation). Dans de telles situations, l'interface fournit des informations précieuses sur la nature de la classe même si elle ne fournit aucun contrat d'implémentation.