Alcuni pro e contro
Pro per polimorfico:
- Un'interfaccia polimorfica più piccola è più facile da leggere. Devo solo ricordare un metodo.
- Si accompagna al modo in cui la lingua è destinata a essere utilizzata: la digitazione Duck.
- Se è chiaro da quali oggetti voglio estrarre un coniglio, non dovrebbe esserci comunque ambiguità.
- Fare un sacco di controllo del tipo è considerato negativo anche in linguaggi statici come Java, dove avere un sacco di controlli del tipo per il tipo di oggetto rende brutto codice, nel caso in cui il mago abbia davvero bisogno di distinguere tra il tipo di oggetti da cui sta tirando fuori un coniglio ?
Pro per ad-hoc:
- È meno esplicito, posso estrarre una stringa da
Cat
un'istanza? Funzionerebbe semplicemente? in caso contrario, qual è il comportamento? Se non limito il tipo qui, devo farlo nella documentazione o nei test che potrebbero peggiorare il contratto.
- Hai tutta la maneggevolezza di tirare un coniglio in un posto, il Mago (alcuni potrebbero considerarlo un imbroglione)
- I moderni ottimizzatori JS differenziano tra le funzioni monomorfe (funziona solo su un tipo) e polimorfiche. Sanno come ottimizzare quelli monomorfi molto meglio, quindi
pullRabbitOutOfString
è probabile che la versione sia molto più veloce in motori come V8. Guarda questo video per ulteriori informazioni. Modifica: ho scritto un perf da solo, si scopre che in pratica, non è sempre così .
Alcune soluzioni alternative:
Secondo me, questo tipo di design non è molto "Java-Scripty" per cominciare. JavaScript è una lingua diversa con modi diversi da linguaggi come C #, Java o Python. Questi idiomi nascono da anni di sviluppatori che cercano di capire le parti deboli e forti del linguaggio, quello che farei è cercare di attenermi a questi idiomi.
Ci sono due belle soluzioni a cui riesco a pensare:
- Elevare gli oggetti, renderli "pulibili", renderli conformi a un'interfaccia in fase di esecuzione, quindi far lavorare il Mago su oggetti pulibili.
- Usando il modello strategico, insegnando al Mago in modo dinamico come gestire diversi tipi di oggetti.
Soluzione 1: elevare oggetti
Una soluzione comune a questo problema è quella di "elevare" gli oggetti con la capacità di farli estrarre dai conigli.
Cioè, avere una funzione che prende un qualche tipo di oggetto e aggiunge per estrarre un cappello. Qualcosa di simile a:
function makePullable(obj){
obj.pullOfHat = function(){
return new Rabbit(obj.toString());
}
}
Posso fare tali makePullable
funzioni per altri oggetti, potrei creare un makePullableString
, ecc. Sto definendo la conversione su ogni tipo. Tuttavia, dopo aver elevato i miei oggetti, non ho alcun tipo per usarli in modo generico. Un'interfaccia in JavaScript è determinata dalla digitazione di un'anatra, se ha un pullOfHat
metodo posso estrarla con il metodo del Mago.
Quindi Magician potrebbe fare:
Magician.pullRabbit = function(pullable) {
var rabbit = obj.pullOfHat();
return {rabbit:rabbit,text:"Tada, I pulled a rabbit out of "+pullable};
}
Elevare gli oggetti, usando una sorta di pattern mixin sembra la cosa più JS da fare. (Si noti che ciò è problematico con i tipi di valore nella lingua che sono stringa, numero, null, indefinito e booleano, ma sono tutti in grado di boxare)
Ecco un esempio di come potrebbe apparire tale codice
Soluzione 2: modello di strategia
Quando ho discusso di questa domanda nella chat room di JS in StackOverflow, il mio amico phenomnomnominal ha suggerito l'uso del modello strategico .
Ciò ti consentirebbe di aggiungere le capacità di estrarre i conigli da vari oggetti in fase di esecuzione e di creare codice molto JavaScript. Un mago può imparare a estrarre oggetti di diversi tipi dai cappelli e li tira in base a quella conoscenza.
Ecco come potrebbe apparire in CoffeeScript:
class Magician
constructor: ()-> # A new Magician can't pull anything
@pullFunctions = {}
pullRabbit: (obj) -> # Pull a rabbit, handler based on type
func = pullFunctions[obj.constructor.name]
if func? then func(obj) else "Don't know how to pull that out of my hat!"
learnToPull: (obj, handler) -> # Learns to pull a rabbit out of a type
pullFunctions[obj.constructor.name] = handler
Puoi vedere il codice JS equivalente qui .
In questo modo, beneficiate di entrambi i mondi, l'azione di come tirare non è strettamente accoppiata né agli oggetti, né al Mago e penso che questo rappresenti una soluzione molto bella.
L'utilizzo sarebbe qualcosa del tipo:
var m = new Magician();//create a new Magician
//Teach the Magician
m.learnToPull("",function(){
return "Pulled a rabbit out of a string";
});
m.learnToPull({},function(){
return "Pulled a rabbit out of a Object";
});
m.pullRabbit(" Str");