OCaml中的访问者设计模式

4

我正在尝试使用OCaml的面向对象构造和类型系统实现访问者设计模式,但在元素实例化时遇到了问题。

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;;

编译时出现的错误如下所示:
The type of this expression, <visitSalesman : 'a -> unit; _.. > salesman as 'a, 
contains type variables that cannot be generalized.

然而,除了实例化salesman的那一行代码外,代码都可以编译。

我该如何在保持类功能的同时实例化salesman

编辑 调用 receiveEvaluation 时收到的错误:

This expression has type (<getName:string>, < getName:string>) lowerLevelHRRep 
but is here used with type <visitSalesman : 'a salesman -> unit > as 'a. 

第二种对象类型没有visitAccountant方法。
1个回答

4

编辑 - 将答案分为三个主要部分:解决初始编译错误、递归解决方案和参数化解决方案

解决编译错误

请注意,您的代码在顶层可以正常工作:

# let s = new salesman ();;
val s : < visitSalesman : 'a -> unit; _.. > salesman as 'a = <obj>

这种编译错误通常通过添加类型注释来帮助编译器确定类型来解决。由于顶部已经告诉我们它是什么,所以我们可以修改实例化:

let s : (< visitSalesman : 'a -> unit>) salesman = new salesman ();;

这个可以编译!

递归解决方案

使用递归类可以降低复杂度。这完全消除了需要使用参数化类的必要性,但意味着所有对象都需要在同一个源文件中定义。

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;;

这将打印出“访问销售员”。强制转换为员工只是为了使它更接近实际情况。

一个参数化的解决方案

再次审视这个问题,我认为没有必要拥有一个参数化的hrRep,因为在这一刻,所有其他类型都是已知的。只需使员工类参数化,我就得到了以下代码:

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;;

这将返回:

访问推销员S Bob

访问会计师A Mary

访问推销员S Sue

这种解决方案的优势在于员工不需要知道来访者的信息,因此可以在自己的编译单元中定义,从而使代码更清晰,添加新类型的员工时需要重新编译的次数更少。


似乎已经解决了编译问题,但是当我尝试使用销售员对象中的函数调用进行编译时,出现了类似的问题。我该如何调用这个函数呢?再次感谢! - Mat Kelly
不确定我是否理解了这个问题,你能贴一些代码吗?此外,我添加了一个希望更简单(虽然有限)的递归定义解决方案。希望能有所帮助! - small_duck
你的解决方案更加优雅,可以实现相同的功能。如果你认为有一种方法可以实现我在修订版中提出的要求,我已经更新了原问题中的代码。 - Mat Kelly
问题可能是由于hrRep的参数化引起的,我在仅限员工的帖子中发布了一个参数化解决方案,似乎解决了这个问题。 - small_duck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接