Implementazione del pattern di stato con Object.setPrototypeOf ()

0

Dai un'occhiata a questa implementazione del pattern di stato in JavaScript:

var ON = {
    switch: function() {
        Object.setPrototypeOf(this, OFF);
        this.state = "OFF";
    }
}

var OFF = {
    switch: function() {
        Object.setPrototypeOf(this, ON);
        this.state = "ON";
    }
}

var lightBulb = Object.create(OFF);
lightBulb.switch(); // lightBulb.state is "ON"
lightBulb.switch(); // lightBulb.state is "OFF"

Due vantaggi che mi piacciono qui sono che il contesto (lightBulb) non deve mantenere un riferimento allo stato attivo e che non deve definire un metodo switch() solo per delegare quella chiamata al suo metodo switch() dello stato attivo. In altre parole, meno codice boilerplate.

Sono nuovo di JavaScript e non ho fatto alcun serio sviluppo con esso. Mi piacerebbe sapere se ci sono delle insidie che mi mancano? In particolare, mi chiedo il fatto che sto cambiando il prototipo di un oggetto costruito. È un no-no in JavaScript land?

    
posta jrahhali 09.08.2015 - 00:44
fonte

1 risposta

1

Funzionerebbe, ma è un modo molto insolito per realizzare questo compito, quindi lo eviterei a meno che non sia inteso come parte di un esercizio di apprendimento su come funzionano i prototipi.

Per essere sicuri di essere sulla stessa pagina, il "modo tipico" che ho in mente è questo:

var Switch = function() {
    this.state = "ON";
};

Switch.prototype.switch = function() {
    if(this.state === "ON") {
        this.state = "OFF";
    } else {
        this.state = "ON";
    }
};

Preferirei questa versione "tradizionale" rispetto alla versione "prototipi circolari" perché:

  • Se mi fossi imbattuto nella versione circolare del prototipo "in the wild", dovrei passare un po 'di tempo a capire cosa avrebbe dovuto fare, o convincermi che questo fa davvero quello che dovrebbe fare (come ho fatto per alcuni minuti quando ho trovato questa domanda), proprio perché è così insolito. Quello tradizionale potrei probabilmente dare un'occhiata immediatamente e andare avanti.

  • La modifica delle catene di prototipi in fase di esecuzione è generalmente disapprovata perché può comportare un comportamento estremamente non intuitivo. In particolare, gli errori in tale codice possono essere estremamente difficili da eseguire il debug. È molto simile al codice auto-modificante nei linguaggi di livello inferiore. Il tuo esempio sembra essere molto semplice, ma se questo fosse parte di un vero programma, senza dubbio finirebbe con un'interfaccia molto più complessa su ogni stato e / o molto più di due stati tra cui spostarsi. Immagina di provare a eseguire il debug del codice quando l'oggetto che stai guardando nel debugger cambia il suo tipo in ogni chiamata al metodo, e ognuno di questi tipi ha aspettative diverse sul suo stato interno.

  • La versione dei prototipi circolari ha già un po 'di codice duplicato tra i due oggetti. Se questo fosse un oggetto più complicato, ci sarebbe senza dubbio molta più duplicazione, il che renderebbe molto più difficile aggiungere nuove funzionalità a questo oggetto senza fare un errore stupido da qualche parte (che dovremmo quindi eseguire il debug!). E ogni volta che due degli oggetti differivano in qualche modo, sarebbe impossibile dire se quella differenza fosse intenzionale o meno.

  • Anche se l'ottimizzazione e gli interni del motore non sono la mia specialità, sono abbastanza sicuro che i motori Javascript sono progettati per ottimizzare strongmente le ricerche di prototipi (dal momento che ogni chiamata di accesso e metodo di un singolo membro richiede una ricerca) ed è molto più facile fallo quando le catene del prototipo rimangono statiche. Inoltre, la maggior parte del codice non modifica le catene in fase di esecuzione, quindi la maggior parte degli ottimizzatori presume che sia la norma.

Inoltre non capisco i tuoi presunti vantaggi.

the context (lightBulb) doesn't have to keep a reference to the active state

Tecnicamente hai ragione, ma in pratica semplicemente "mantiene quel riferimento" sotto forma di un link prototipo a una chiusura che conosce lo stato (ma non dirà a nessuno di cosa si tratta), che è molto meno diretto e meno flessibile del semplice mantenimento dello stato come variabile membro. Inoltre, non vedo perché vorresti un interruttore della luce per non sapere se è acceso o meno; sembra come cercare di disaccoppiare cose che non possono e non devono essere disaccoppiate.

it doesn't have to define a switch() method just to delegate that call to its active state's switch() method

Sembra che tu stia confrontando la tua versione con un'implementazione in cui lightSwitch.switch () chiamerebbe ON.switch () o OFF.switch (). Vorrei anche criticare questa implementazione per la sua non necessaria delega, ma dal momento che è molto facile scrivere una versione tradizionale che semplicemente non lo fa, non è chiaro come questo sia un vantaggio.

In other words, less boilerplate code.

Le nostre versioni sembrano uguali in lunghezza / standard.

Infine, ecco uno scenario ipotetico che potrebbe aiutare a dimostrare alcuni dei punti astratti che stavo facendo sopra: Dì che vuoi dare al tuo lightSwitch un metodo isOn() che ritorna indipendentemente dal fatto che sia attivo. Come lo faresti?

Con la versione tradizionale, la soluzione è una banale one-liner senza evidenti svantaggi. Con la versione circolare dei prototipi, non riesco a pensare ad alcun modo pulito per aggiungerlo (cioè, nessuna duplicazione di codice, nessun confronto diretto di prototipi) senza essenzialmente reimplementare l'intera classe in un modo più tradizionale.

    
risposta data 10.08.2015 - 02:02
fonte

Leggi altre domande sui tag