Question

Imaginez une classe de base avec de nombreux constructeurs et une méthode virtuelle

public class Foo
{
   ...
   public Foo() {...}
   public Foo(int i) {...}
   ...
   public virtual void SomethingElse() {...}
   ...
}

et maintenant je veux créer une classe descendante qui remplace la méthode virtuelle:

public class Bar : Foo 
{
   public override void SomethingElse() {...}
}

Et un autre descendant qui fait encore plus de choses:

public class Bah : Bar
{
   public void DoMoreStuff() {...}
}

Dois-je vraiment copier tous les constructeurs de Foo dans Bar and Bah? Et puis, si je change de signature de constructeur dans Foo, dois-je la mettre à jour dans Bar and Bah?

N'y a-t-il aucun moyen d'hériter des constructeurs? N'y a-t-il aucun moyen d'encourager la réutilisation du code?

Était-ce utile?

La solution

Oui, vous devrez implémenter les constructeurs appropriés pour chaque dérivation, puis utiliser le mot clé base pour diriger ce constructeur vers la classe de base appropriée ou le this . mot clé pour diriger un constructeur vers un autre constructeur de la même classe.

Si le compilateur a émis des hypothèses sur l'héritage des constructeurs, nous ne serions pas en mesure de déterminer correctement le mode d'instanciation de nos objets. Dans la plupart des cas, vous devez vous demander pourquoi vous avez autant de constructeurs et envisager de les réduire à un ou deux seulement dans la classe de base. Les classes dérivées peuvent ensuite masquer certaines d’entre elles à l’aide de valeurs constantes telles que null et n’exposer que celles nécessaires par le biais de leurs constructeurs.

Mettre à jour

En C # 4, vous pouvez spécifier les valeurs de paramètre par défaut et utiliser des paramètres nommés pour qu'un constructeur unique prenne en charge plusieurs configurations d'arguments plutôt que d'avoir un constructeur par configuration.

Autres conseils

387 constructeurs ?? C'est ton problème principal. Que diriez-vous de cela à la place?

public Foo(params int[] list) {...}

Oui, vous devez copier les 387 constructeurs. Vous pouvez les réutiliser en les redirigeant:

  public Bar(int i): base(i) {}
  public Bar(int i, int j) : base(i, j) {}

mais c'est ce que vous pouvez faire de mieux.

Dommage que nous soyons obligés de dire au compilateur l'évidence:

Subclass(): base() {}
Subclass(int x): base(x) {}
Subclass(int x,y): base(x,y) {}

Je n'ai besoin que de faire 3 constructeurs dans 12 sous-classes, donc ce n'est pas grave, mais je n'aime pas trop répéter cela dans chaque sous-classe, après avoir été habitué à ne pas avoir à l'écrire aussi longtemps. Je suis sûr qu'il y a une raison valable à cela, mais je ne pense pas avoir jamais rencontré un problème nécessitant ce type de restriction.

N'oubliez pas que vous pouvez également rediriger des constructeurs vers d'autres constructeurs au même niveau d'héritage:

public Bar(int i, int j) : this(i) { ... }
                            ^^^^^

Une autre solution simple pourrait consister à utiliser une structure ou une classe de données simple contenant les paramètres en tant que propriétés; Ainsi, toutes les valeurs et tous les comportements par défaut peuvent être configurés à l’avance, en passant par la "classe de paramètres". dans comme paramètre constructeur unique:

public class FooParams
{
    public int Size...
    protected myCustomStruct _ReasonForLife ...
}
public class Foo
{
    private FooParams _myParams;
    public Foo(FooParams myParams)
    {
          _myParams = myParams;
    }
}

Ceci évite le désordre de plusieurs constructeurs (parfois) et donne un typage fort, des valeurs par défaut et d'autres avantages non fournis par un tableau de paramètres. Il est également facile à reporter car tout ce qui hérite de Foo peut toujours accéder à FooParams, voire même en ajouter, si nécessaire. Vous devez toujours copier le constructeur, mais vous avez toujours (la plupart du temps) seulement (en règle générale) toujours (au moins, pour le moment) besoin d’un constructeur.

public class Bar : Foo
{
    public Bar(FooParams myParams) : base(myParams) {}
}

J'aime beaucoup les approches surchargée Initailize () et Class Factory Pattern, mais vous avez parfois besoin d’un constructeur intelligent. Juste une pensée.

Comme Foo est une classe, ne pouvez-vous pas créer de méthodes Initialise () surchargées virtuelles? Ensuite, ils seraient disponibles pour les sous-classes et toujours extensibles?

public class Foo
{
   ...
   public Foo() {...}

   public virtual void Initialise(int i) {...}
   public virtual void Initialise(int i, int i) {...}
   public virtual void Initialise(int i, int i, int i) {...}
   ... 
   public virtual void Initialise(int i, int i, ..., int i) {...}

   ...

   public virtual void SomethingElse() {...}
   ...
}

Cela ne devrait pas avoir un coût de performance supérieur, sauf si vous avez beaucoup de valeurs de propriétés par défaut et que vous y frappez beaucoup.

public class BaseClass
{
    public BaseClass(params int[] parameters)
    {

    }   
}

public class ChildClass : BaseClass
{
    public ChildClass(params int[] parameters)
        : base(parameters)
    {

    }
}

Personnellement, j’estime que c’est une erreur de la part de Microsofts. Ils auraient dû permettre au programmeur d’ignorer la visibilité des constructeurs, méthodes et propriétés dans les classes de base, puis de faire en sorte que les constructeurs soient toujours hérités.

De cette façon, nous ne faisons que remplacer (avec une visibilité réduite - c'est-à-dire privés) les constructeurs que nous ne voulons pas au lieu d'avoir à ajouter tous les constructeurs que nous voulons. Delphi le fait de cette façon, et ça me manque.

Prenez par exemple si vous souhaitez remplacer la classe System.IO.StreamWriter, vous devez ajouter les 7 constructeurs à votre nouvelle classe et, si vous aimez les commentaires, vous devez commenter chacun d'eux avec l'en-tête XML. Pour aggraver les choses, la vue Métadonnées affiche les commentaires XML sous forme de commentaires XML appropriés. Nous devons donc aller ligne par ligne et les copier-coller. À quoi pensait Microsoft ici?

J'ai en fait écrit un petit utilitaire dans lequel vous pouvez coller le code de métadonnées et le convertir en commentaires XML à l'aide d'une visibilité masquée.

  

Dois-je vraiment copier tous les constructeurs de Foo dans Bar et Bah ? Et puis, si je change une signature de constructeur dans Foo , dois-je la mettre à jour dans Bar et Bah ?

Oui, si vous utilisez des constructeurs pour créer des instances.

  

N'y a-t-il aucun moyen d'hériter des constructeurs?

Nope.

  

N'y a-t-il aucun moyen d'encourager la réutilisation du code?

Eh bien, je ne vais pas me demander si l'héritage de constructeurs serait une bonne ou une mauvaise chose et si cela encouragerait la réutilisation du code, car nous ne les avons pas et nous ne les aurons pas. : -)

Mais ici en 2014, avec le C # actuel, vous pouvez obtenir quelque chose de beaucoup comme les constructeurs hérités en utilisant une méthode générique create . Ce peut être un outil utile à porter à votre ceinture, mais vous ne voudriez pas le prendre à la légère. Je l'ai récemment touché lorsque j'ai eu besoin de passer quelque chose au constructeur d'un type de base utilisé dans quelques centaines de classes dérivées (jusqu'à récemment, la base ne nécessitait pas d'argument, et le constructeur par défaut était donc très bien & nbsp; - la Les classes dérivées ne déclarent pas du tout les constructeurs et obtiennent celle qui est fournie automatiquement).

Cela ressemble à ceci:

// In Foo:
public T create<T>(int i) where: where T : Foo, new() {
    T obj = new T();
    // Do whatever you would do with `i` in `Foo(i)` here, for instance,
    // if you save it as a data member;  `obj.dataMember = i;`
    return obj;
}

Cela signifie que vous pouvez appeler la fonction générique create en utilisant un paramètre de type qui correspond à tout sous-type de Foo ayant un constructeur à zéro argument.

Ensuite, au lieu de faire Bar b new Bar (42) , procédez comme suit:

var b = Foo.create<Bar>(42);
// or
Bar b = Foo.create<Bar>(42);
// or
var b = Bar.create<Bar>(42); // But you still need the <Bar> bit
// or
Bar b = Bar.create<Bar>(42);

Là, j'ai montré que la méthode create était sur Foo directement, mais bien sûr, il pourrait s'agir d'une classe fabrique quelconque, si les informations qu'elle configure sont en cours peut être défini par cette classe d'usine.

Juste pour plus de clarté: le nom create n'a pas d'importance, il pourrait s'agir de makeThingy ou de ce que vous voudrez.

Exemple complet

using System.IO;
using System;

class Program
{
    static void Main()
    {
        Bar b1 = Foo.create<Bar>(42);
        b1.ShowDataMember("b1");

        Bar b2 = Bar.create<Bar>(43); // Just to show `Foo.create` vs. `Bar.create` doesn't matter
        b2.ShowDataMember("b2");
    }

    class Foo
    {
        public int DataMember { get; private set; }

        public static T create<T>(int i) where T: Foo, new()
        {
            T obj = new T();
            obj.DataMember = i;
            return obj;
        }
    }

    class Bar : Foo
    {
        public void ShowDataMember(string prefix)
        {
            Console.WriteLine(prefix + ".DataMember = " + this.DataMember);
        }
    }
}

Le problème n'est pas que Bar et Bah doivent copier 387 constructeurs, le problème est que Foo a 387 constructeurs. Foo fait clairement trop de choses - refactor rapidement! De plus, à moins que vous n'ayez une très bonne raison d'avoir des valeurs définies dans le constructeur (ce qui, si vous fournissez un constructeur sans paramètre, ce n'est probablement pas le cas), je vous recommanderais d'utiliser property get / setting.

Non, vous n'avez pas besoin de copier tous les 387 constructeurs sur Bar and Bah. Bar et Bah peuvent avoir autant de constructeurs que vous le souhaitez, indépendamment du nombre de constructeurs que vous définissez sur Foo. Par exemple, vous pouvez choisir d’avoir un seul constructeur Bar qui construit Foo avec le constructeur 212e de Foo.

Oui, tous les constructeurs que vous modifiez dans Foo et dont dépend Bar ou Bah vous demanderont de modifier Bar et Bah en conséquence.

Non, il n'y a aucun moyen dans .NET d'hériter des constructeurs. Mais vous pouvez obtenir une réutilisation de code en appelant le constructeur d'une classe de base à l'intérieur du constructeur de la sous-classe ou en appelant une méthode virtuelle que vous définissez (comme Initialize ()).

Vous pourrez peut-être adapter une version de Idiome de constructeur virtuel C ++ . Autant que je sache, C # ne prend pas en charge les types de retour covariants. Je crois que cela fait partie des souhaits de beaucoup de gens.

Un trop grand nombre de constructeurs est le signe d’une conception cassée. Mieux vaut une classe avec peu de constructeurs et la possibilité de définir des propriétés. Si vous avez vraiment besoin de contrôler les propriétés, considérez une fabrique dans le même espace de noms et rendez les paramètres de propriété internes. Laissez l’usine décider comment instancier la classe et définir ses propriétés. L’usine peut avoir des méthodes qui prennent autant de paramètres que nécessaire pour configurer correctement l’objet.

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