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 .

È stato utile?

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top