Scrivere un metodo per 'trasformare' un oggetto immutabile: come dovrei avvicinarmi a questo?

0

(Mentre questa domanda ha a che fare con un concreto dilemma del codice, è soprattutto su quale sia il modo migliore di progettare una funzione.)

Sto scrivendo un metodo che dovrebbe prendere due oggetti Color e trasformare gradualmente il primo Colore nel secondo, creando un'animazione. Il metodo sarà in una classe di utilità.

Il mio problema è che il colore è un oggetto immutabile. Ciò significa che non posso fare color.setRGB o color.setBlue all'interno di un ciclo nel metodo.

Quello che posso fare, è creare un'istanza di un nuovo colore e restituirlo dal metodo. Ma poi non potrò gradatamente cambiare il colore.

Quindi ho pensato a tre possibili soluzioni:

1- Il codice client include la chiamata al metodo all'interno di un ciclo. Ad esempio:

int duration = 1500; // duration of the animation in milliseconds
int steps = 20; // how many 'cycles' the animation will take
for(int i=0; i<steps; i++) color = transformColor(color, targetColor, duration, steps);

E il metodo sarebbe simile a questo:

Color transformColor(Color original, Color target, int duration, int steps){
    int redDiff = target.getRed() - original.getRed();
    int redAddition = redDiff / steps;
    int newRed = original.getRed() + redAddition;

    // same for green and blue ..

    Thread.sleep(duration / STEPS); // exception handling omitted
    return new Color(newRed, newGreen, newBlue);
}

Lo svantaggio di questo approccio è che il codice client deve "fare parte del lavoro del metodo" e includere un ciclo for . Il metodo non funziona completamente a proprio, cosa che non mi piace.

2- Crea una sottoclasse Color mutabile con metodi come setRed e passa gli oggetti di questa classe in transformColor . Quindi potrebbe essere simile a questo:

void transformColor(MutableColor original, Color target, int duration){
    final int STEPS = 20;

    int redDiff = target.getRed() - original.getRed();
    int redAddition = redDiff / steps;
    int newRed = original.getRed() + redAddition;

    // same for green and blue ..

    for(int i=0; i<STEPS; i++){
        original.setRed(original.getRed() + redAddition);
        // same for green and blue ..
        Thread.sleep(duration / STEPS); // exception handling omitted
    }
}

Quindi il codice chiamante di solito sarebbe simile a questo:

// The method will usually transform colors of JComponents
JComponent someComponent = ... ;
// setting the Color in JComponent to be a MutableColor
Color mutableColor = new MutableColor(someComponent.getForeground());
someComponent.setForeground(mutableColor);
// later, transforming the Color in the JComponent
transformColor((MutableColor)someComponent.getForeground(), new Color(200,100,150), 2000);

Lo svantaggio è: la necessità di creare una nuova classe MutableColor e anche la necessità di eseguire il casting.

3- Passa nel metodo l'oggetto mutabile reale che contiene il colore. Quindi il metodo potrebbe eseguire object.setColor o simili ogni iterazione del ciclo.

Due svantaggi:

A- Non così elegante. Passare nell'oggetto che trattiene il colore solo per trasformare il colore sembra innaturale.

B- Mentre la maggior parte delle volte questo metodo verrà usato per trasformare i colori all'interno di JComponent oggetti, anche altri tipi di oggetti potrebbero avere colori. Quindi il metodo dovrebbe essere sovraccaricato per ricevere altri tipi, o ricevere Object s e avere instanceof controlli all'interno. Non ottimale.

In questo momento, penso che mi piaccia la soluzione n. 2, più della soluzione n. 1 e della soluzione n. 3. Tuttavia mi piacerebbe sentire le vostre opinioni e suggerimenti su questo.

    
posta Aviv Cohn 05.06.2014 - 19:34
fonte

3 risposte

4

I'm writing a method that should take two Color objects, and gradually transform the first Color into the second one, creating an animation. The method will be in a utility class.

OK, quindi quanti dubbi non correlati abbiamo in quella frase?

  1. un metodo che dovrebbe prendere due oggetti Color e trasformare gradualmente il primo colore nel secondo - OK, è una cosa!

    Sono un po 'preoccupato per il tuo uso della parola "graduale" in quanto potrebbe implicare la miscelazione del concetto di tempo con il concetto di colore. Tuttavia, posso (e lo farò) scegliere di leggere l'altro significato, come in gradazioni di colore . Quindi, prendiamo due colori e (in qualche modo) produciamo un determinato numero di sfumature intermedie. Non hai specificato se dovremmo farlo in RGB, HSV, CMYK o qualsiasi altro spazio colore, ma va bene.

  2. creazione di un'animazione - beh, sembra una cosa completamente separata. La funzione sopra può essere utilizzata, ad esempio, creando un gradiente di colore per l'ombreggiatura (dove il colore varia nello spazio anziché nel tempo). Quindi, perché legarlo alla cosa dell'animazione?

Quindi ora sappiamo che vogliamo che alcune funzioni producano una sequenza di colori ordinata dati alcuni input. Questo potrebbe essere un contenitore, o potrebbe essere un iteratore, oppure potrebbe essere un loop lineare che richiede alcune funzioni di callback. Questi sono tutti simili, con i seguenti compromessi:

  • restituisce un contenitore: utilizza più spazio di archiviazione (gli altri due sono di dimensioni costanti e questo scala linearmente con il numero di passaggi) e carica frontalmente il calcolo (ci vuole un po 'per popolare quel contenitore se è grande)
  • iteratore: il codice client deve ancora scrivere quello per il loop a cui hai obiettato, e devi scrivere un tipo iteratore (a meno che il tuo linguaggio non abbia delle co-routines o qualcosa di simile, come i generatori di Python) ma è molto efficiente
  • callback: il codice client effettua una singola chiamata, ma invece del ciclo for, deve scrivere alcune funzioni di callback con accesso a tutti gli stati corretti (a meno che la tua lingua non abbia simpatici lambda o chiusure lessicali)

L'opzione contenitore è solo per completezza e consiglierei a uno degli altri due, a seconda delle preferenze e del supporto linguistico, a meno che tu non sappia che sarà piccolo o che non hai altre opzioni.

    
risposta data 05.06.2014 - 20:17
fonte
1

Ecco un caso in cui puoi effettivamente utilizzare uno schema: Iterator

Il tuo oggetto iteratore è costruito con i colori di partenza e di arrivo e una serie di passaggi. Ogni chiamata a next() restituisce il colore successivo nella sequenza, fino a raggiungere la fine.

Vedo che il tuo esempio include un sonno, che è una pessima idea per la programmazione della GUI. Invece, usa un javax.swing.Timer per attivare le letture dal tuo iteratore. Se si desidera incapsulare la durata nell'oggetto iteratore (una buona idea), è possibile aggiungere un metodo sleepTime() .

    
risposta data 05.06.2014 - 19:43
fonte
0

Un'alternativa a un iteratore è quella di passare a qualsiasi cosa tu voglia fare con il colore come argomento a transformColor .

void transformColor(Color original, Color target, int duration, int steps, Function<Color, Void> f){
    int redDiff = target.getRed() - original.getRed();
    int redAddition = redDiff / steps;
    int newRed = original.getRed() + redAddition;

    // same for green and blue ..
    for (int i = 0; i < steps; i++) {
        f(new Color(newRed, newGreen, newBlue));
        Thread.sleep(duration / STEPS); // exception handling omitted
    }
}

Su quella nota direi che è sorprendente e potenzialmente indesiderabile che transformColor si prenda cura dei tempi.

    
risposta data 05.06.2014 - 20:01
fonte

Leggi altre domande sui tag