Pregunta

Imagine una clase base con muchos constructores y un método virtual

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

y ahora quiero crear una clase descendiente que anule el método virtual:

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

Y otro descendiente que hace algo más:

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

¿Realmente tengo que copiar todos los constructores de Foo a Bar y Bah? Y luego, si cambio la firma de un constructor en Foo, ¿tengo que actualizarla en Bar y Bah?

¿No hay forma de heredar constructores? ¿No hay manera de fomentar la reutilización del código?

¿Fue útil?

Solución

Sí, tendrá que implementar los constructores que tengan sentido para cada derivación y luego usar la palabra clave base para dirigir a ese constructor a la clase base adecuada o al este palabra clave para dirigir un constructor a otro constructor en la misma clase.

Si el compilador hiciera suposiciones sobre la herencia de constructores, no podríamos determinar correctamente cómo se crearon instancias de nuestros objetos. En su mayor parte, debe considerar por qué tiene tantos constructores y considerar la posibilidad de reducirlos a solo uno o dos en la clase base. Las clases derivadas pueden enmascarar algunas de ellas usando valores constantes como null y solo exponer las necesarias a través de sus constructores.

Actualizar

En C # 4, puede especificar valores de parámetros predeterminados y usar parámetros con nombre para hacer que un único constructor soporte múltiples configuraciones de argumentos en lugar de tener un constructor por configuración.

Otros consejos

387 constructores ?? Ese es tu principal problema. ¿Qué tal esto en su lugar?

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

Sí, tienes que copiar todos los 387 constructores. Puedes reutilizarlos un poco redirigiéndolos:

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

pero eso es lo mejor que puedes hacer.

Lástima que estemos obligados a decirle al compilador lo obvio:

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

Solo necesito hacer 3 constructores en 12 subclases, así que no es gran cosa, pero no me gusta repetir eso en cada subclase, después de estar acostumbrado a no tener que escribirlo durante tanto tiempo. Estoy seguro de que hay una razón válida para ello, pero no creo que haya encontrado un problema que requiera este tipo de restricción.

No olvides que también puedes redirigir los constructores a otros constructores en el mismo nivel de herencia:

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

Otra solución simple podría ser usar una estructura o una clase de datos simple que contenga los parámetros como propiedades; de esa manera puede tener todos los valores y comportamientos predeterminados configurados de antemano, pasando la " clase de parámetro " en como el único parámetro constructor:

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

Esto evita el desorden de varios constructores (a veces) y proporciona una tipificación sólida, valores predeterminados y otros beneficios no proporcionados por una matriz de parámetros. También hace que sea fácil de llevar adelante, ya que todo lo que se hereda de Foo aún puede llegar a, o incluso agregar, a FooParams, según sea necesario. Aún necesita copiar el constructor, pero siempre (la mayoría de las veces) solo (como regla general) alguna vez (al menos, por ahora) necesita un constructor.

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

Realmente me gustan más los enfoques de Initailize () y Class Factory Pattern sobrecargados, pero a veces solo es necesario tener un constructor inteligente. Solo un pensamiento.

Como Foo es una clase, ¿no puede crear métodos virtuales sobrecargados de Initialize () ? Entonces, ¿estarían disponibles para las subclases y aún serían 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() {...}
   ...
}

Esto no debería tener un costo de rendimiento más alto a menos que tenga muchos valores de propiedad predeterminados y lo alcance mucho.

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

    }   
}

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

    }
}

Personalmente, creo que esto es un error en la parte de Microsofts, deberían haber permitido al programador anular la visibilidad de Constructores, Métodos y Propiedades en las clases básicas, y luego hacerlo para que los Constructores siempre sean heredados.

De esta manera, simplemente anulamos (con menor visibilidad, es decir, Privados) los constructores que NO queremos en lugar de tener que agregar todos los constructores que QUEREMOS. Delphi lo hace de esta manera, y lo extraño.

Tome, por ejemplo, si desea anular la clase System.IO.StreamWriter, debe agregar los 7 constructores a su nueva clase, y si desea comentar, debe comentar cada uno con el encabezado XML. Para empeorar las cosas, la vista de metadatos dosnt coloca los comentarios XML como comentarios XML adecuados, por lo que tenemos que ir línea por línea y copiarlos y pegarlos. ¿Qué estaba pensando Microsoft aquí?

En realidad, he escrito una pequeña utilidad en la que puedes pegar el código de metadatos y lo convertirá en comentarios XML con visibilidad oculta.

  

¿Realmente tengo que copiar todos los constructores de Foo en Bar y Bah ? Y luego, si cambio la firma de un constructor en Foo , ¿tengo que actualizarla en Bar y Bah ?

Sí, si utiliza constructores para crear instancias.

  

¿No hay forma de heredar constructores?

No.

  

¿No hay manera de fomentar la reutilización del código?

Bueno, no me ocuparé de si los constructores hereditarios serían una cosa buena o mala y si alentaría la reutilización del código, ya que no los tenemos y no los vamos a obtener. :-)

Pero aquí en 2014, con el C # actual, puede obtener algo muy parecido a como constructores heredados usando un método genérico create . Puede ser una herramienta útil para tener en tu cinturón, pero no lo alcanzarás a la ligera. Lo alcancé recientemente cuando me enfrenté a la necesidad de pasar algo al constructor de un tipo base usado en un par de cientos de clases derivadas (hasta hace poco, la base no necesitaba ningún argumento, por lo que el constructor predeterminado estaba bien & nbsp; & # 8212; las clases derivadas no declararon constructores en absoluto, y obtuvieron el suministrado automáticamente).

Se parece a esto:

// 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;
}

Eso dice que puedes llamar a la función genérica create usando un parámetro de tipo que es cualquier subtipo de Foo que tiene un constructor de cero argumentos.

Luego, en lugar de hacer Bar b new Bar (42) , harías esto:

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);

Allí he mostrado que el método create está en Foo directamente, pero, por supuesto, podría estar en una clase de fábrica de algún tipo, si la información que está configurando puede ser establecido por esa clase de fábrica.

Sólo por claridad: el nombre crear no es importante, podría ser makeThingy o cualquier otra cosa que desee.

Ejemplo completo

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);
        }
    }
}

El problema no es que Bar y Bah tengan que copiar 387 constructores, el problema es que Foo tiene 387 constructores. Foo claramente hace demasiadas cosas - ¡refactoriza rápido! Además, a menos que tenga una buena razón para tener valores establecidos en el constructor (que, si proporciona un constructor sin parámetros, probablemente no lo haga), recomiendo usar la configuración / obtención de propiedades.

No, no es necesario copiar todos los 387 constructores a Bar y Bah. Bar y Bah pueden tener tantos o tan pocos constructores como quieras, independientemente de cuántos hayas definido en Foo. Por ejemplo, puedes elegir tener solo un constructor de barra que construya Foo con el constructor 212 de Foo.

Sí, cualquier constructor que cambies en Foo del que dependen Bar o Bah requerirá que modifiques Bar y Bah en consecuencia.

No, no hay forma en .NET de heredar constructores. Pero puede lograr la reutilización de código llamando al constructor de una clase base dentro del constructor de la subclase o llamando a un método virtual que defina (como Inicializar ()).

Puedes adaptar una versión de C ++ idioma constructor virtual . Que yo sepa, C # no admite tipos de retorno covariantes. Creo que está en la lista de deseos de muchas personas.

Demasiados constructores es un signo de un diseño roto. Mejor una clase con pocos constructores y la capacidad de establecer propiedades. Si realmente necesita control sobre las propiedades, considere una fábrica en el mismo espacio de nombres y haga que los establecedores de propiedades sean internos. Deje que la fábrica decida cómo crear una instancia de la clase y establecer sus propiedades. La fábrica puede tener métodos que toman tantos parámetros como sea necesario para configurar el objeto correctamente.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top