Posso rendere i miei costruttori meno ripetitivi?

7

Sto estendendo un corso con 10 diversi costruttori . La nuova sottoclasse, SpecialImage , viene utilizzata in questo modo:

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 dei costruttori, ognuno dei quali prende gli stessi parametri aggiuntivi, Thing stuff e 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;
    }
}

Questo è un po 'ripetitivo, che è solo un vero problema di manutenzione. Ogni volta che i requisiti di costruzione di SpecialImage cambiano, ho 5 costruttori da modificare.

Sicuramente sento come se dovessi ripetermi di meno, ma non so come rimuovere questo tipo di ridondanza. È possibile renderlo meno ridondante?

    
posta kdbanman 11.07.2015 - 00:19
fonte

4 risposte

7

Potresti utilizzare il pattern Builder .

Non hai 10 costruttori in Image , ne hai solo uno che prende tutti i parametri possibili. Ma non chiamare questo costruttore dal tuo codice attuale. Chiamalo da una classe Builder separata.

Un Builder è una classe separata che viene inizializzata con i valori predefiniti, offre diversi setter per modificare questi valori e un metodo build() per creare un nuovo oggetto con i suoi valori correnti.

Il tuo codice per creare una nuova immagine sarebbe quindi simile a questo:

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

Il tuo SpecialImageBuilder erediterà da ImageBuilder . Quando Image e SpecialImage hanno le stesse proprietà, allora probabilmente l'unico metodo che devi sostituire è build .

A proposito, un trucco elegante che puoi fare con i costruttori è che ogni setter restituisca this . Si chiama "fluent interface" e ti permette di scrivere codice come questo:

Image image = new ImageBuilder()
                  .setStuff(stuff)
                  .setRange(range)
                  .setSize(size)
                  .setFilename(filename)
                  .build();
    
risposta data 11.07.2015 - 10:52
fonte
2

No, Supponendo che tu non possa cambiare la classe base, o sapere che una catena di costruttori alle altre. Non c'è modo di scegliere uno dei costruttori di base multipli da un altro costruttore. Ho pensato che potresti essere in grado di farlo con i generici, ma apparentemente no.

Anche se si utilizza una classe factory o builder, in cui è possibile chiamare un metodo create e passare da costruttori che utilizza all'interno di tale metodo, non si risolve il problema immediato. Poiché sono ancora necessari più costruttori nella classe SpecialImage

Una soluzione è avvolgere la tua classe base piuttosto che ereditarla. Questo presuppone che la classe base implementa un'interfaccia, oppure puoi aggiungerne una su

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();
        }
    }
}
    
risposta data 11.07.2015 - 12:51
fonte
1

È possibile utilizzare la composizione come suggerito da lxrec e quindi recuperare il delegato quando è effettivamente necessaria un'istanza Image . Potrebbe essere problematico se desideri utilizzare solo SpecialImage ovunque tu abbia precedentemente utilizzato un Image .

Oltre a questo, non posso davvero vedere una soluzione migliore. L'unico altro suggerimento che ho è quello di utilizzare i metodi factory predefiniti al posto dei costruttori, ma questo è solo per la leggibilità.

    
risposta data 11.07.2015 - 09:57
fonte
1

La considerazione principale è la comodità per gli utenti della tua classe, che è parte dell'usabilità della tua API. Troppe o poche opzioni potrebbero danneggiare l'usabilità. In particolare, troppe opzioni possono confondere gli utenti e costringerli a dedicare più tempo alla scelta. Pertanto, è necessario valutare attentamente se verrà effettivamente utilizzato ogni sovraccarico del costruttore. Rimuovere in modo aggressivo quelli non utilizzati prima della pubblicazione (per gli utenti interni o esterni dell'API).

Ad esempio, se fornisci un costruttore che accetta un Size , non devi certamente fornire un altro costruttore che prende int width, int height .

Anche il costruttore che prende un string filename è un po 'falso. Lo spazio cromatico di un file immagine è deciso dal contenuto del file. Un file JPEG può essere grigio o YCbCr o YCCK. (YCbCr viene automaticamente convertito automaticamente in RGB al momento del caricamento, ma ci sono situazioni in cui sono necessari i valori YCbCr dal file senza l'errore di quantizzazione causato dalla conversione.) Quindi un costruttore non avrebbe dovuto decidere lo spazio colore senza esaminare il formato dell'immagine prima.

Sono profondamente perplesso dal costruttore che non inizializza il suo Image . Il resto del tuo design di classe sembra implicare che il suo Image debba essere inizializzato. È tua intenzione renderlo un Oggetto Null ? Se questa non è la tua intenzione, quel costruttore non avrebbe dovuto esistere.

Finalmente un po 'di cautela. Sembra che tu stia progettando un livello di elaborazione delle immagini "single-channel high-dynamic-range" su una libreria esistente. (Canale singolo come in "grigio" e intervallo dinamico elevato come nell'intenzione di supportare numero intero a 32 bit, float a 32 bit e float a 64 bit.) Potresti essere sorpreso dal fatto che alcune operazioni di immagine non supportino l'HDR profondità - in altre parole, alcune operazioni dell'immagine sono limitate solo a TDepth = Byte . Cercando di eseguire operazioni su profondità HDR potresti generare un'eccezione in fase di runtime.

Posso trovare molti errori con le librerie sottostanti, ma devo ammettere che progettare un'API per una libreria di immagini è molto difficile. Mi congratulo con tutti coloro che hanno fatto un buon lavoro e hanno condiviso il loro lavoro.

    
risposta data 11.07.2015 - 13:24
fonte

Leggi altre domande sui tag