c# qué - Cómo heredar constructores?




polimorfismo? programación (13)

El problema no es que Bar y Bah tengan que copiar 387 constructores, el problema es que Foo tiene 387 constructores. Es obvio que Foo hace demasiadas cosas, ¡refactor rápido! Además, a menos que tengas una buena razón para tener valores establecidos en el constructor (que, si proporcionas un constructor sin parámetros, probablemente no), te recomendaría usar la propiedad get / setting.

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 algunas cosas más:

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

¿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?

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


Sí, tienes que copiar los 387 constructores. Puede reutilizarlos 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.


Es posible que pueda adaptar una versión de la expresión de constructor virtual de C ++ . Hasta donde yo sé, C # no admite tipos de retorno covariantes. Creo que eso está en la lista de deseos de muchas personas.


Lástima que estamos forzados 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 todas las subclases, después de haberme acostumbrado a no tener que escribir 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.


Sí, tendrá que implementar los constructores que tengan sentido para cada derivación y luego usar la palabra clave base para dirigir ese constructor a la clase base apropiada o la palabra clave this 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 adecuadamente cómo se crearon las instancias de nuestros objetos. En su mayor parte, debe considerar por qué tiene tantos constructores y considerar 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 admita múltiples configuraciones de argumentos en lugar de tener un constructor por configuración.


Otra solución simple podría ser usar una estructura o clase de datos simple que contenga los parámetros como propiedades; De esta forma, puede configurar todos los valores predeterminados y los comportamientos configurados con anticipación, pasando la "clase de parámetro" como el único parámetro del 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 múltiples constructores (a veces) y proporciona un tipado fuerte, valores predeterminados y otros beneficios no proporcionados por una matriz de parámetros. También hace que sea fácil de seguir, ya que todo lo que hereda de Foo puede obtener, o incluso agregar, 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) {}
}

Me encantan los enfoques sobreimpresos de Initailize () y Class Factory Pattern, pero a veces solo necesitas tener un constructor inteligente. Solo un pensamiento.


¿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 usa constructores para crear instancias.

¿No hay forma de heredar constructores?

Nop.

¿No hay forma de alentar la reutilización del código?

Bueno, no entraré en la cuestión de si heredar constructores sería algo bueno o malo y si eso alentaría la reutilización del código, ya que no los tenemos y no vamos a obtenerlos. :-)

Pero aquí en 2014, con el C # actual, puede obtener algo muy parecido a los constructores heredados utilizando en su lugar un método de create genérico. Puede ser una herramienta útil para tener en su cinturón, pero no podría alcanzarla a la ligera. Lo busqué recientemente cuando me enfrenté con la necesidad de pasar algo al constructor de un tipo base utilizado 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 - el derivado las clases no declararon constructores en absoluto, y obtuvieron la oferta automática).

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 puede llamar a la función de create genérica usando un parámetro de tipo que es cualquier subtipo de Foo que tiene un constructor de argumentos cero.

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

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

Solo por claridad: el nombre create no es importante, podría ser makeThingy o cualquier otra cosa que te guste.

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

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 establezca los ajustadores de propiedades 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 tomen tantos parámetros como sea necesario para configurar el objeto correctamente.


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

    }   
}

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

    }
}

No olvide que también puede redirigir constructores a otros constructores en el mismo nivel de herencia:

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

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 base, y luego hacerlo de modo que los Constructores siempre se hereden.

De esta forma, simplemente anulamos (con menor visibilidad, es decir, privada) 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.

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

De hecho, he escrito una pequeña utilidad en la que puedes pegar el código de metadatos y lo convertirá en comentarios XML utilizando visibilidad obsoleta.


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

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

Accessors    | Base Class | Derived Class | World
—————————————+————————————+———————————————+———————
public       |      y     |       y       |   y
—————————————+————————————+———————————————+———————
protected    |      y     |       y       |   n
—————————————+————————————+———————————————+———————
private      |            |               |    
  or         |      y     |       n       |   n
no accessor  |            |               |

y: accessible
n: not accessible

Basado en this ejemplo para java ... Creo que una mesita vale más que mil palabras :)





c# design inheritance