Imagine une classe de base avec plusieurs 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 redéfinit la méthode virtuelle:
public class Bar : Foo
{
public override void SomethingElse() {...}
}
Et un autre descendant qui fait 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 une 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?
Oui, vous devrez implémenter les constructeurs pertinents pour chaque dérivation, puis utiliser le mot clé base
pour diriger ce constructeur vers la classe de base appropriée ou le mot clé this
pour diriger un constructeur vers un autre constructeur dans la même classe.
Si le compilateur formulait des hypothèses sur l'héritage des constructeurs, nous ne serions pas en mesure de déterminer correctement la manière dont nos objets étaient instanciés. 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 alors masquer certaines d’entre elles en utilisant des valeurs constantes telles que null
et n’exposer que celles nécessaires par l’intermédiaire de leurs constructeurs.
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 de disposer d'un constructeur par configuration.
387 constructeurs ?? C'est ton problème principal. Que diriez-vous de cela à la place?
public Foo(params int[] list) {...}
Oui, vous devez copier tous 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 le mieux que vous puissiez faire.
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 sur chaque sous-classe, après avoir été habitué à ne pas l'écrire pendant si 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 la "classe de paramètres" en tant que 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;
}
}
Cela é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. Cela facilite également le report 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 mieux les approches surchargée Initailize () et Class Factory Pattern, mais parfois vous avez simplement besoin d'un constructeur intelligent. Juste une pensée.
Comme Foo
est une classe, ne pouvez-vous pas créer de méthodes virtuelles surchargées Initialise()
? Ensuite, ils seraient disponibles pour les sous-classes et encore 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 plus élevé, 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, je pense 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 remplaçons simplement 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 en utilisant une visibilité masquée.
Dois-je vraiment copier tous les constructeurs de
Foo
dansBar
etBah
? Et puis, si je change une signature de constructeur dansFoo
, dois-je la mettre à jour dansBar
etBah
?
Oui, si vous utilisez des constructeurs pour créer des instances.
N'y a-t-il aucun moyen d'hériter des constructeurs?
Nan.
N'y a-t-il aucun moyen d'encourager la réutilisation du code?
Eh bien, je ne vais pas me demander si hériter 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 beaucoup comme les constructeurs hérités en utilisant plutôt 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 correct - le dérivé les classes ne déclaraient pas du tout les constructeurs et obtenaient celui fourni 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
à l'aide d'un paramètre de type qui correspond à tout sous-type de Foo
possédant un constructeur à zéro argument.
Ensuite, au lieu de Bar b new Bar(42)
, vous feriez ceci:
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 d’usine quelconque, si les informations qu’elle configure peuvent être définies par cette classe 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/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 un seul constructeur Bar qui construit Foo avec le 212ème constructeur de Foo.
Oui, tous les constructeurs que vous changez 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 du 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++ . Pour autant que je sache, C # ne prend pas en charge les types de retour covariants. Je crois que cela fait partie de la liste de souhaits de nombreux peuples.
Trop 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.