Assez simple. J'implémente une interface, mais il y a une propriété qui n'est pas nécessaire pour cette classe et, en fait, ne devrait pas être utilisée. Mon idée initiale était de faire quelque chose comme:
int IFoo.Bar
{
get { raise new NotImplementedException(); }
}
Je suppose qu'il n'y a rien de mal à cela, en soi, mais cela ne semble pas "correct". Quelqu'un d'autre a-t-il déjà rencontré une situation similaire? Si oui, comment l'avez-vous abordé?
c#
design
interfaces
implementations
Chris Pratt
la source
la source
Réponses:
Il s'agit d'un exemple classique de la façon dont les gens décident de violer le principe de substitution de Liskov. Je le décourage fortement mais encouragerais éventuellement une solution différente:
Si le premier est le cas pour vous, n'implémentez simplement pas l'interface sur cette classe. Considérez-le comme une prise électrique où le trou de mise à la terre n'est pas nécessaire pour qu'il ne se fixe pas réellement à la terre. Vous ne branchez rien avec de la terre et ce n'est pas grave! Mais dès que vous utilisez quelque chose qui a besoin d'un terrain - vous pourriez être dans un échec spectaculaire. Mieux vaut ne pas percer un faux trou. Donc, si votre classe ne fait pas vraiment ce que propose l'interface, ne l'implémentez pas.
Voici quelques extraits de Wikipédia:
Le principe de substitution de Liskov peut être formulé simplement comme suit: "Ne renforcez pas les conditions préalables et n'affaiblissez pas les conditions postérieures".
Pour l'interopérabilité sémantique et la substituabilité entre différentes implémentations des mêmes contrats - vous avez tous besoin d'eux pour vous engager dans les mêmes comportements.
Le principe de ségrégation des interfaces exprime l'idée que les interfaces doivent être séparées en ensembles cohérents de sorte que vous n'avez pas besoin d'une interface qui fait beaucoup de choses disparates lorsque vous ne voulez qu'une seule installation. Pensez à nouveau à l'interface d'une prise électrique, elle pourrait également avoir un thermostat, mais cela rendrait plus difficile l'installation d'une prise électrique et pourrait la rendre plus difficile à utiliser à des fins autres que le chauffage. Comme une prise électrique avec un thermostat, les grandes interfaces sont difficiles à mettre en œuvre et difficiles à utiliser.
la source
Ça me va bien, si telle est votre situation.
Cependant, il me semble que votre interface (ou son utilisation) est cassée si une classe dérivée ne l'implémente pas réellement. Pensez à diviser cette interface.
Avertissement: cela nécessite un héritage multiple pour fonctionner correctement, et je n'ai aucune idée si C # le prend en charge.la source
J'ai rencontré cette situation. En fait, comme indiqué ailleurs, la BCL a de tels cas ... Je vais essayer de fournir de meilleurs exemples et de fournir une justification:
Lorsque vous avez une interface déjà livrée que vous conservez pour des raisons de compatibilité et ...
L'interface contient des membres obsolètes ou décolorés. Par exemple
BlockingCollection<T>.ICollection.SyncRoot
(entre autres), bien qu'ilICollection.SyncRoot
ne soit pas obsolète en soi, il lanceraNotSupportedException
.L'interface contient des membres qui sont documentés comme étant facultatifs et que l'implémentation peut lever l'exception. Par exemple, sur MSDN,
IEnumerator.Reset
il est dit:Par une erreur de conception de l'interface, il aurait dû y avoir plus d'une interface en premier lieu. C'est un modèle courant dans BCL d'implémenter des versions en lecture seule des conteneurs avec
NotSupportedException
. Je l'ai fait moi-même, c'est ce qui est attendu maintenant ... Je fais leICollection<T>.IsReadOnly
retourtrue
pour que vous puissiez leur dire à part. La conception correcte aurait été d'avoir une version lisible de l'interface, puis l'interface complète en hérite.Il n'y a pas de meilleure interface à utiliser. Par exemple, j'ai une classe qui vous permet d'accéder aux éléments par index, vérifiez s'il contient un élément et sur quel index, il a une certaine taille, vous pouvez le copier dans un tableau ... cela semble un travail
IList<T>
mais ma classe a une taille fixe, et ne prend pas en charge l'ajout ni la suppression, donc cela fonctionne plus comme un tableau qu'une liste. Mais il n'yIArray<T>
en a pas dans la BCL .L'interface appartient à une API qui est portée sur plusieurs plates-formes et dans la mise en œuvre d'une plate-forme particulière, certaines parties de celle-ci ne sont pas prises en charge. Idéalement, il y aurait un moyen de le détecter, de sorte que le code portable qui utilise une telle API puisse décider d'appeler ou non ces parties ... mais si vous les appelez, il est tout à fait approprié de les obtenir
NotSupportedException
. Cela est particulièrement vrai s'il s'agit d'un portage vers une nouvelle plate-forme qui n'était pas prévue dans la conception d'origine.Considérez également pourquoi il n'est pas pris en charge?
C'est parfois
InvalidOperationException
une meilleure option. Par exemple, une autre façon d'ajouter du polymorphisme dans une classe est d'avoir différentes implémentations d'une interface interne et votre code choisit celle à instancier en fonction des paramètres donnés dans le constructeur de la classe. [Ceci est particulièrement utile si vous savez que l'ensemble des options est fixe et vous ne voulez pas permettre à des classes de tiers à introduire par l' injection de dépendance.] Je l' ai fait à backport ThreadLocal parce que la mise en œuvre de suivi et non suivi sont trop loin, et qu'est-ce quiThreadLocal.Values
jette sur l'implémentation sans suivi?InvalidOperationException
même si cela ne dépend pas de l'état de l'objet. Dans ce cas, j'ai présenté la classe moi-même et je savais que cette méthode devait être implémentée en lançant simplement une exception.Parfois, une valeur par défaut est logique. Par exemple sur ce qui est
ICollection<T>.IsReadOnly
mentionné ci-dessus, il est logique de renvoyer simplement «vrai» ou «faux» selon le cas. Alors ... quelle est la sémantique deIFoo.Bar
? il peut y avoir une valeur par défaut raisonnable à retourner.Addendum: si vous contrôlez l'interface (et que vous n'avez pas besoin de rester avec elle pour des raisons de compatibilité), il ne devrait pas y avoir de cas où vous devez lancer
NotSupportedException
. Cependant, vous devrez peut-être diviser l'interface en deux ou plusieurs interfaces plus petites pour avoir le bon ajustement pour votre cas, ce qui peut entraîner une "pollution" dans les situations extrêmes.la source
Oui, les concepteurs de bibliothèques .Net l'ont fait. La classe Dictionary fait ce que vous faites. Il utilise une implémentation explicite pour masquer efficacement * certaines des méthodes IDictionary. Il est mieux expliqué ici , mais pour résumer, afin d'utiliser les méthodes Add, CopyTo ou Remove du dictionnaire qui prennent KeyValuePairs, vous devez d'abord convertir l'objet en un IDictionary.
* Il ne s'agit pas de "masquer" les méthodes au sens strict du mot "masquer" telles que Microsoft les utilise . Mais je ne connais pas de meilleur terme dans ce cas.
la source
Vous pouvez toujours simplement implémenter et renvoyer une valeur bénigne ou une valeur par défaut. Après tout, ce n'est qu'une propriété. Il pourrait renvoyer 0 (la propriété par défaut de int), ou toute valeur ayant un sens dans votre implémentation (int.MinValue, int.MaxValue, 1, 42, etc ...)
Lancer une exception semble être une mauvaise forme.
la source