OCaml 强大的混合类型mixins

什么时候应该使用多重继承?如果你问多个人,你可能会得到多个(也许是激烈的)答案。有些人会争辩说多重继承过于复杂。其他人会争辩说继承通常是有问题的,应该使用对象组合来代替。但无论你和谁交谈,你都很少听说多重继承很棒,你应该广泛使用它。

在任何情况下,如果您正在使用对象进行编程,那么有一种通用的多重继承模式既有用又相当简单:mixin模式。一般来说,mixin只是一个虚拟类,它基于另一个实现一个特性。如果你有一个实现方法A的类,并且你有一个从A提供方法B的 mixin M ,那么你可以从M继承——“混合”它——来获得特性 B。

这太抽象了,所以让我们根据我们的交互式形状给出一些示例。我们可能希望允许鼠标拖动形状。我们可以为任何具有可变 x和y字段的对象以及 on_mousedown添加事件处理程序的方法定义此功能:

class virtual draggable = object(self)
  method virtual on_mousedown:
    ?start:unit Deferred.t ->
    ?stop:unit Deferred.t ->
    (int -> int -> unit) -> unit
  val virtual mutable x: int
  val virtual mutable y: int

  val mutable dragging = false
  method dragging = dragging

  initializer
    self#on_mousedown
      (fun mouse_x mouse_y ->
         let offset_x = x - mouse_x in
         let offset_y = y - mouse_y in
         let mouse_up = Ivar.create () in
         let stop = Ivar.read mouse_up in
         dragging <- true;
         on_mouseup ~stop
           (fun _ ->
              Ivar.fill mouse_up ();
              dragging <- false);
         on_mousemove ~stop
           (fun ev ->
              x <- ev.mouse_x + offset_x;
              y <- ev.mouse_y + offset_y))
end

 

这允许我们使用多重继承创建可拖动的形状:

class small_square = object
  inherit square 20 40 40
  inherit draggable
end

 

我们还可以使用 mixins 来创建动画形状。每个动画形状都有一个要在动画期间调用的更新函数列表。我们创建了一个animatedmixin 来提供这个更新列表,并确保在形状动画时定期调用其中的函数:

class virtual animated span = object(self)
  method virtual on_click:
    ?start:unit Deferred.t ->
    ?stop:unit Deferred.t ->
    (int -> int -> unit) -> unit
  val mutable updates: (int -> unit) list = []
  val mutable step = 0
  val mutable running = false

  method running = running

  method animate =
    step <- 0;
    running <- true;
    let stop =
      Clock.after span
      >>| fun () -> running <- false
    in
    Clock.every ~stop (Time.Span.of_sec (1.0 /. 24.0))
      (fun () ->
         step <- step + 1;
         List.iter ~f:(fun f -> f step) updates
      )

  initializer
    self#on_click (fun _x _y -> if not self#running then self#animate)
end

 

我们使用初始化器将函数添加到此更新列表中。例如,此类将生成单击时向右移动一秒钟的圆圈:

class my_circle = object
  inherit circle 20 50 50
  inherit animated Time.Span.second
  initializer updates <- [fun _ -> x <- x + 5]
end

 

这些初始化器也可以使用 mixins 添加:

class virtual linear x' y' = object
  val virtual mutable updates: (int -> unit) list
  val virtual mutable x: int
  val virtual mutable y: int

  initializer
    let update _ =
      x <- x + x';
      y <- y + y'
    in
    updates <- update :: updates
end

let pi = (Float.atan 1.0) *. 4.0

class virtual harmonic offset x' y' = object
  val virtual mutable updates: (int -> unit) list
  val virtual mutable x: int
  val virtual mutable y: int

  initializer
    let update step =
      let m = Float.sin (offset +. ((Float.of_int step) *. (pi /. 64.))) in
      let x' = Float.to_int (m *. Float.of_int x') in
      let y' = Float.to_int (m *. Float.of_int y') in
      x <- x + x';
      y <- y + y'
    in
    updates <- update :: updates
end

 

由于linear和harmonicmixins 仅用于它们的副作用,它们可以在同一个对象内多次继承以产生各种不同的动画:

class my_square x y = object
  inherit square 40 x y
  inherit draggable
  inherit animated (Time.Span.of_int_sec 5)
  inherit linear 5 0
  inherit harmonic 0.0 7 ~-10
end

let my_circle = object
  inherit circle 30 250 250
  inherit animated (Time.Span.minute)
  inherit harmonic 0.0 10 0
  inherit harmonic (pi /. 2.0) 0 10
end

分类: 默认 标签: 发布于: 2022-06-22 10:37:50, 更新于: 2022-06-22 11:33:44