Modello di progettazione dei visitatori in OCaml
-
03-07-2019 - |
Domanda
Sto tentando di implementare il Visitor Design Pattern usando i costrutti OO di OCaml e il sistema di tipi e sto incontrando problemi all'istanza di un elemento.
class virtual ['hrRep] employee = object
method virtual receiveEvaluation : 'hrRep -> unit
method virtual getName : string
end;;
class ['hrRep] accountant myName = object (self : 'a)
inherit ['hrRep]employee
val name = myName
method receiveEvaluation rep = rep#visitAccountant self
method getName = name
end;;
class ['hrRep] salesman myName = object (self : 'a)
inherit ['hrRep]employee
val name = myName
method receiveEvaluation rep = rep#visitSalesman self
method getName = name
end;;
class virtual ['accountant, 'salesman] hrRep = object (self)
method virtual visitSalesman : 'salesman -> unit
method virtual visitAccountant : 'accountant -> unit
end;;
class ['employee, 'salesman] lowerLevelHRRep =
object (self) inherit ['employee, 'salesman]hrRep
method visitSalesman s = print_endline ("Visiting salesman "^s#getName)
method visitAccountant a =
print_endline ("Visiting accountant "^a#getName)
end;;
let s1 : (<visitSalesman : 'a -> unit>) salesman = new salesman "Bob";;
let a1 : (<visitAccountant : 'a -> unit>) accountant = new accountant "Mary";;
let s2 : (<visitSalesman : 'a -> unit>) salesman = new salesman "Sue";;
let h1 : (<getName : string>, <getName : string>) lowerLevelHRRep = new lowerLevelHRRep;;
s1#receiveEvaluation h1;;
L'errore che ricevo durante la compilazione è:
The type of this expression, <visitSalesman : 'a -> unit; _.. > salesman as 'a,
contains type variables that cannot be generalized.
Tuttavia, il codice viene compilato meno la riga che crea un'istanza del venditore
.
Come posso fare per creare un'istanza del venditore
mantenendo la funzionalità delle classi?
Modifica Errore ricevuto con la chiamata a ricevere Valutazione:
This expression has type (<getName:string>, < getName:string>) lowerLevelHRRep
but is here used with type <visitSalesman : 'a salesman -> unit > as 'a.
Il secondo tipo di oggetto non ha metodo visitAccountant
.
Soluzione
EDIT - Separata la risposta in 3 punti principali: la risoluzione dell'errore di compilazione iniziale, una soluzione ricorsiva e una soluzione parametrizzata
Risoluzione dell'errore di compilazione
Nota che il tuo codice funziona bene al livello superiore:
# let s = new salesman ();;
val s : < visitSalesman : 'a -> unit; _.. > salesman as 'a = <obj>
Questo tipo di errore di compilazione viene generalmente risolto aggiungendo un'annotazione di tipo per aiutare il compilatore a capire il tipo. Come il livello superiore ci ha gentilmente detto di cosa si trattava, possiamo modificare l'istanza:
let s : (< visitSalesman : 'a -> unit>) salesman = new salesman ();;
E questo compila!
Una soluzione ricorsiva
È possibile ridurre la complessità utilizzando le classi ricorsive. Ciò elimina totalmente la necessità di classi parametrizzate, ma significa che tutti gli oggetti devono essere definiti nello stesso file di origine.
class virtual employee =
object
method virtual receiveEvaluation:(hrrep -> unit)
end
and accountant =
object(self)
inherit employee
method receiveEvaluation:(hrrep -> unit) = fun rep -> rep#visitAccountant (self :> accountant)
end
and salesman =
object (self)
inherit employee
method receiveEvaluation:(hrrep -> unit) = fun rep -> rep#visitSalesman (self :> salesman)
end
and hrrep =
object
method visitSalesman:(salesman -> unit) = fun s -> print_endline ("Visiting salesman")
method visitAccountant:(accountant -> unit) = fun s -> print_endline ("Visiting accountant")
end
let s = new salesman;;
let e = (s :> employee);;
let v = new hrrep;;
e#receiveEvaluation v;;
Stampa "venditore in visita". La coercizione al dipendente è solo quella di avvicinarlo a uno scenario del mondo reale.
Una soluzione parametrizzata
Guardando di nuovo il problema, penso che non sia necessario avere un hrRep parametrizzato, perché in questo momento, tutti gli altri tipi sono noti. Semplicemente facendo parametrizzare la classe dei dipendenti, ottengo questo:
class virtual ['a] employee =
object
method virtual receiveEvaluation : 'a -> unit
method virtual getName : string
end
class ['a] accountant name =
object(self)
inherit ['a] employee
val name = name
method receiveEvaluation rep = rep#visitAccountant self
method getName = "A " ^ name
end
class ['a] salesman name =
object(self)
inherit ['a] employee
val name = name
method receiveEvaluation rep = rep#visitSalesman self
method getName = "S " ^ name
end
class virtual hrRep =
object
method virtual visitAccountant : hrRep accountant -> unit
method virtual visitSalesman : hrRep salesman -> unit
end
class lowerLevelHRRep =
object
inherit hrRep
method visitAccountant a = print_endline ("Visiting accountant " ^ a#getName)
method visitSalesman s = print_endline ("Visiting salesman " ^ s#getName)
end;;
let bob = new salesman "Bob";;
let mary = new accountant "Mary";;
let sue = new salesman "Sue";;
let h = new lowerLevelHRRep;;
bob#receiveEvaluation h;;
mary#receiveEvaluation h;;
sue#receiveEvaluation h;;
Questo restituisce:
Venditore in visita S Bob
Visitatore contabile A Mary
Venditore in visita S Sue
Il vantaggio di questa soluzione è che i dipendenti non hanno bisogno di conoscere il visitatore e quindi possono essere definiti nelle proprie unità di compilazione, portando a codice più pulito e meno ricompilazione da fare quando si aggiungono nuovi tipi di dipendenti.