54 votes

Pourquoi ma classe publique ne peut-elle pas étendre une classe interne ?

Je ne comprends vraiment pas.

Si la classe de base est abstraite et n'est destinée à être utilisée que pour fournir des fonctionnalités communes aux sous-classes publiques définies dans l'assemblage, pourquoi ne devrait-elle pas être déclarée interne ?

Je ne veux pas que la classe abstraite soit visible par du code extérieur à l'assemblage. Je ne veux pas que le code externe la connaisse.

49voto

Eric Lippert Points 300275

UPDATE : Cette question a été le sujet de mon blog le 13 novembre 2012 . Vous y trouverez d'autres réflexions sur ce sujet. Merci pour cette excellente question !


Vous avez raison, ce n'est pas forcément le cas. D'autres langages OO autorisent l'"héritage privé", selon lequel le fait que D hérite de B ne peut être exploité que par le code qui a la possibilité de voir B.

C'était une décision de conception des concepteurs originaux de C#. Malheureusement, je suis loin de mon bureau en ce moment - je prends quelques jours de congé pour le long week-end - et je n'ai pas sous les yeux les notes de conception du langage de 1999. Si j'y pense à mon retour, je les parcourrai et verrai s'il y a une justification à cette décision.

Mon opinion personnelle est que l'héritage devrait être utilisé pour représenter les relations "est une sorte de" ; c'est-à-dire que l'héritage devrait représenter la sémantique du domaine modélisé dans le langage . J'essaye d'éviter les situations où l'héritage est utilisé comme une mécanisme de partage de code . Comme d'autres l'ont mentionné, il est probablement préférable de préférer la composition à l'héritage si ce que vous voulez représenter est "cette classe partage des mécanismes d'implémentation avec d'autres classes".

1 votes

"préférer l'encapsulation à l'héritage" - pouvez-vous en donner un exemple ? Je ne suis pas sûr de comprendre exactement ce que vous voulez dire ici, ou comment vous utiliseriez l'encapsulation pour fournir un code d'implémentation partagé.

0 votes

@Erik : J'avais l'intention de taper "prefer composition" et j'ai accidentellement tapé "prefer encapsulation". Une faute de frappe assez importante !

0 votes

Je comprends votre point de vue, même si les coups de menton sur l'architecture sont un luxe pour les personnes qui maîtrisent mieux la langue que moi ! (Je dis cela de la meilleure façon possible, si par erreur cela peut paraître sarcastique ou agressif).

42voto

Justin Niessner Points 144953

En héritant d'une classe, vous exposez la fonctionnalité de la classe de base à travers votre enfant.

Comme la classe enfant a une plus grande visibilité que son parent, vous exposeriez des membres qui seraient autrement protégés.

Vous ne pouvez pas violer le niveau de protection de la classe parent en implémentant un enfant avec une plus grande visibilité.

Si la classe de base est réellement destinée à être utilisée par des classes enfantines publiques, vous devez également rendre le parent public.

L'autre option est de garder votre "parent" interne, de le rendre non-abstrait, et de l'utiliser pour composer vos classes enfant, et d'utiliser une interface pour forcer les classes à implémenter la fonctionnalité :

public interface ISomething
{
    void HelloWorld();
}

internal class OldParent : ISomething
{
    public void HelloWorld(){ Console.WriteLine("Hello World!"); }
}

public class OldChild : ISomething
{
    OldParent _oldParent = new OldParent();

    public void HelloWorld() { _oldParent.HelloWorld(); }
}

6 votes

Merci. Mais pourquoi les membres d'une classe interne ne seraient-ils pas considérés comme internes, sauf s'ils sont exposés par une sous-classe publique ? Je pense que cela a du sens.

1 votes

Merci pour votre réponse détaillée et votre suggestion d'utiliser la composition. Ai-je raison de penser que la réponse à ma question est plutôt "parce que le C# n'a pas été créé de cette façon" que "parce que ce serait une chose stupide à faire" ?

1 votes

C'est une question de convention. Si vous cherchez à modifier la portée de la classe par le biais de l'héritage, vous ne pouvez tout simplement pas modifier les règles pour rendre quelque chose plus accessible. L'interne doit être utilisé spécifiquement pour rendre quelque chose accessible à toutes les classes d'un assemblage. Changer la portée en public viole cette règle, en rendant une sous-classe accessible à d'autres assemblages. Une bonne utilisation d'une classe interne est la protection des préoccupations centrales du cadre, par exemple. Si vous insistez pour que votre classe interne soit accessible en dehors de l'assemblage, vous ne devriez probablement pas utiliser le modificateur interne.

18voto

Sam Points 1115

Je pense que la chose la plus proche que vous pouvez faire est d'empêcher d'autres assemblages de créer la classe abstraite en rendant son constructeur interne, pour citer de MSDN :

Un constructeur interne empêche la classe abstraite d'être utilisée comme classe de base de types qui ne sont pas dans la même assemblée que la classe abstraite.

Vous pouvez alors essayer d'ajouter un EditorBrowsableAttribute à la classe pour essayer de la cacher d'IntelliSense (bien que j'aie eu des résultats mitigés en l'utilisant pour être honnête) ou mettre la classe de base dans un espace de nom imbriqué, tel que MyLibrary.Internals pour le séparer du reste de vos classes.

0 votes

A mon avis, c'est la vraie réponse.

4voto

Jordão Points 29221

Je pense que vous mélangez les préoccupations ici, et que le C# est à blâmer, en fait (et Java avant lui).

L'héritage devrait servir de mécanisme de catégorisation, alors qu'il est souvent utilisé pour la réutilisation du code.

Pour la réutilisation du code, on a toujours su que la composition était supérieure à l'héritage. Le problème avec C# est qu'il nous donne un moyen si facile d'hériter :

class MyClass : MyReusedClass { }

Mais pour composer, nous devons le faire par nous-mêmes :

class MyClass {
  MyReusedClass _reused;
  // need to expose all the methods from MyReusedClass and delegate to _reused
}

Ce qui manque, c'est une construction comme un trait (pdf) qui amènera la composition au même niveau de convivialité que l'héritage.

Il y a des recherches sur Les traits en C# (pdf) et ça ressemblerait à quelque chose comme ça :

class MyClass {
  uses { MyTrait; }
}

Bien que j'aimerais voir un autre modèle (celle des rôles de Perl 6).

UPDATE :

A titre d'information, le langage Oxygene a un élément qui vous permet de déléguer tous les membres d'une interface à une propriété membre qui implémente cette interface :

type
  MyClass = class(IReusable)
  private
    property Reused : IReusable := new MyReusedClass(); readonly;
      implements public IReusable;
  end;

Ici, tous les membres de l'interface de IReusable seront exposés par MyClass et elles seront toutes déléguées à la Reused la propriété. Il existe des problèmes avec cette approche, cependant.

UNE AUTRE MISE À JOUR :

J'ai commencé à mettre en œuvre ce concept de composition automatique en C# : jetez un coup d'œil à NRoles .

0 votes

Je comprends tout à fait votre point de vue, mais je pense que votre affirmation selon laquelle "la composition bat l'héritage" pourrait être développée. Pouvez-vous dire autre chose pour expliquer pourquoi vous pensez ainsi ? Merci.

0 votes

@David : J'ai dit que la composition bat l'héritage pour la réutilisation du code. Vous ne devriez pas avoir à adhérer à une hiérarchie d'héritage pour la seule raison de réutiliser du code. La hiérarchie dit quelque chose sur qui vous êtes, et c'est une relation très forte (et très précieux dans un langage à héritage unique). Pour la réutilisation du code, vous voulez vraiment dire quelque chose sur ce que vous faites, et la composition et la délégation vous permettent de faire exactement cela. Les interfaces sont également efficaces à cet égard, mais vous devez toujours implémenter leurs membres ou déléguer à quelqu'un qui le fait.

0 votes

Le concept d'"héritage" comprend en fait deux concepts orthogonaux : la réutilisation du code avec des comportements par défaut, et la substituabilité. L'héritage est approprié lorsque les deux sont requis ; la composition est meilleure lorsque seule la réutilisation du code est requise. Ce qui serait vraiment cool, ce serait un support générique pour la composition d'interfaces, de sorte que si II est une interface, un objet pourrait implémenter II en termes de champ de ce type. Sans une contrainte générique pour les interfaces, cela ne fonctionnera évidemment pas, mais ce serait très pratique si Microsoft pouvait l'ajouter un jour.

3voto

Dave Points 5649

Je pense que cela violerait la Principe de substitution de Liskov .

Dans des cas comme celui-ci, j'ai utilisé des classes internes et je préfère la composition à l'héritage. Y a-t-il quelque chose dans votre conception qui interdit de contenir toute cette fonctionnalité dans votre classe interne, puis de faire en sorte que vos classes publiques contiennent une instance de cette classe interne ?

0 votes

En quoi cela viole-t-il le principe de substitution ? Je ne vois pas où vous voulez en venir.

0 votes

@Eric Je l'ai fait à l'envers, je suppose. Vous violez la règle si votre classe dérivée supprime une fonctionnalité fournie par la base, donc vous ne pourriez pas remplacer votre base par votre classe dérivée. Lorsque j'ai lu la question pour la première fois, j'ai tiré des conclusions hâtives. C'était juste une pensée à ce moment-là - la partie principale de ma réponse est toujours valable - utilisez simplement la composition au lieu de l'héritage. Merci pour la correction.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X