¿Puedo hacer que mis constructores sean menos repetitivos?

7

Estoy extendiendo una clase con 10 constructores diferentes . La nueva subclase, SpecialImage , se usa así:

SpecialImage specialImage = new SpecialImage(..);

// Leverage the Rotate() method of superclass Image, which
// returns a new rotated image.
// Notice the type is still SpecialImage.
SpecialImage rotated = specialImage.Rotate(...);

SpecialImage implementa 5 de los constructores, cada uno de los cuales toma los mismos parámetros adicionales, Thing stuff y int range :

public class SpecialImage<TDepth> : Image<Gray, TDepth>
    where TDepth : new()
{

    Thing _stuff;
    int _range;

    public SpecialImage(Thing stuff, int range) : base()
    {
        InitParams(stuff, range)
    }

    public SpecialImage(Thing stuff, int range, Size size) : base(size)
    {
        InitParams(stuff, range)
    }

    public SpecialImage(Thing stuff, int range, int width, int height) : base(width, height)
    {
        InitParams(stuff, range)
    }

    public SpecialImage(Thing stuff, int range, string filename) : base(filename)
    {
        InitParams(stuff, range)
    }

    public SpecialImage(Thing stuff, int range, TDepth[,,] data) : base(data)
    {
        InitParams(stuff, range)
    }

    private void InitParams(Thing stuff, int range)
    {
        _stuff = stuff;
        _range = range;
    }
}

Esto es un poco repetitivo, lo cual es solo un problema en el aspecto de mantenimiento. Cada vez que cambian los requisitos de construcción de SpecialImage , tengo 5 constructores para cambiar.

Estoy seguro de que siento como si me repitiera menos, pero no sé cómo eliminar este tipo de redundancia. ¿Es posible hacer que esto sea menos redundante?

    
pregunta kdbanman 11.07.2015 - 00:19

4 respuestas

7

Puede utilizar el patrón de creación .

No tiene 10 constructores en Image , solo tiene uno que toma todos los parámetros posibles. Pero no llame a este constructor desde su código real. Llámelo desde una clase de Generador separada.

Un generador es una clase separada que se inicializa con los valores predeterminados, ofrece varios configuradores para cambiar estos valores y un método build() para crear un nuevo objeto con sus valores actuales.

Tu código para crear una nueva imagen se verá así:

ImageBuilder builder = new ImageBuilder();
builder.setStuff(stuff);
builder.setRange(range);
builder.setSize(size); // alternative overload: builder.setSize(width, height);
builder.setFilename(filename);
Image image = builder.build();

Su SpecialImageBuilder heredaría de ImageBuilder . Cuando Image y SpecialImage tienen las mismas propiedades, es probable que el único método que deba reemplazar sea build .

Por cierto, un truco elegante que puedes hacer con los constructores es hacer que cada configurador devuelva this . Esto se llama "interfaz fluida" y le permite escribir código como este:

Image image = new ImageBuilder()
                  .setStuff(stuff)
                  .setRange(range)
                  .setSize(size)
                  .setFilename(filename)
                  .build();
    
respondido por el Philipp 11.07.2015 - 10:52
2

No, asumiendo que no puedes cambiar la clase base, o saber que un constructor encadena a los otros. No hay forma de elegir uno de los múltiples constructores base de otro constructor. Pensé que podrías hacerlo con genéricos, pero aparentemente no.

Incluso si usa una fábrica o una clase de constructor, donde puede llamar a un método de creación y cambiar entre los constructores que usa dentro de ese método, no resuelve su problema inmediato. Como todavía necesitas varios constructores en tu clase de imagen especial

Una solución es envolver su clase base en lugar de heredarla. Esto supone que la clase base implementa una interfaz, o puede agregar una en

using System;

namespace LessConstructors
{
    public class Size
    { }
    public class Thing
    { }
    public class Gray
    { }

    public interface IImage
    {
        object MethodX();
    }

    public class Image<TColour, TDepth> : IImage
    {
        public Image() { }

        public Image(Size size) { }

        public Image(int width, int height) { }

        public Image(string filename) { }

        public Image(TDepth[,,] data) { }

        public object MethodX()
        {
            throw new NotImplementedException();
        }
    }

    public interface IConstructor<T1, T2>
    {
        Image<T1, T2> Create();
    }
    public class SizeConstructor<T1, T2> : IConstructor<T1, T2>
    {
        public Size size { get; set; }

        public Image<T1, T2> Create()
        {
            return new Image<T1, T2>(this.size);
        }
    }

    public class HeightAndWidthConstructor<T1, T2> : IConstructor<T1, T2>
    {
        public int Height { get; set; }
        public int Width { get; set; }

        public Image<T1, T2> Create()
        {
            return new Image<T1, T2>(this.Height, this.Width);
        }
    }

    public class SpecialImage<TDepth> : IImage
        where TDepth : new()
    {
        private Image<Gray, TDepth> image;
        Thing _stuff;
        int _range;

        public SpecialImage(Thing stuff, int range, IConstructor<Gray, TDepth> constructor)
        {
            _stuff = stuff;
            _range = range;
            image = constructor.Create();
        }

        public object MethodX()
        {
            return this.image.MethodX();
        }
    }
}
    
respondido por el Ewan 11.07.2015 - 12:51
1

Podrías usar la composición como sugirió lxrec y luego recuperar el delegado cuando realmente necesites una instancia Image . Eso podría ser problemático si solo desea utilizar SpecialImage en cualquier lugar en el que anteriormente estuviera usando Image .

Aparte de eso, realmente no puedo ver una mejor solución. La única otra sugerencia que tengo es usar métodos de fábrica con nombre en lugar de constructores, pero eso es solo para facilitar la lectura.

    
respondido por el moofins 11.07.2015 - 09:57
1

La consideración principal es la conveniencia para los usuarios de su clase, que es parte de la usabilidad de su API. Demasiadas o muy pocas opciones podrían dañar la usabilidad. En particular, demasiadas opciones pueden confundir a los usuarios y obligarlos a dedicar más tiempo a la elección. Por lo tanto, debe considerar cuidadosamente si se utilizará realmente la sobrecarga de cada constructor. Elimine agresivamente los que no se utilicen antes de publicar (para usuarios internos o externos de la API).

Por ejemplo, si proporciona un constructor que toma Size , ciertamente no necesita proporcionar otro constructor que tome int width, int height .

El constructor que toma string filename también es un poco falso. El espacio de color de un archivo de imagen se decide por el contenido del archivo. Un archivo JPEG puede ser gris o YCbCr o YCCK. (Los YCbCr se suelen convertir automáticamente a RGB al cargarlos, pero hay situaciones en las que necesita los valores YCbCr del archivo sin el error de cuantificación causado por la conversión). Por lo tanto, un constructor no debería haber decidido el espacio de color sin examinar el formato de la imagen. primero.

Estoy desconcertado por el constructor que no inicializa su Image . El resto del diseño de su clase parece implicar que su Image debe inicializarse. ¿Es tu intención convertirlo en un objeto nulo ? Si esa no es tu intención, ese constructor no debería haber existido.

Por último, un poco de precaución. Parece que estás diseñando una capa de procesamiento de imágenes de "alto rango dinámico de un solo canal" sobre una biblioteca existente. (Canal único como en "gris", y alto rango dinámico como en la intención de admitir enteros de 32 bits, flotación de 32 bits y flotación de 64 bits.) Es posible que se sorprenda de que algunas operaciones de imagen no sean compatibles con HDR Profundidades en absoluto: en otras palabras, algunas operaciones de imagen están limitadas a TDepth = Byte solamente. Intentar realizar operaciones en profundidad HDR puede generar una excepción en tiempo de ejecución.

Puedo encontrar muchos errores en las bibliotecas subyacentes, pero debo admitir que diseñar una API para una biblioteca de imágenes es muy difícil. Aplaudo a todos los que hicieron un buen esfuerzo y compartieron su trabajo.

    
respondido por el rwong 11.07.2015 - 13:24

Lea otras preguntas en las etiquetas