【在主画面加入捷径】
       
【选择语系】
繁中 简中

Nim 语言程序教学:组合 (Composition) 和继承 (Inheritance)

【赞助商连结】

    组合和继承是两种不同思维的重用程序代码的方式,本文介绍在 Nim 里面如何使用这两种模式撰写程序。

    继承

    透过继承,类之间可以共用程序代码,两个类分别是父类 (parent class) 和子类 (child class),子类可重覆利用父类的程序代码,但父类则无法使用子类的程序代码。在 Nim 语言中,使用 ref object of 在声明继承,如下例:

    import random
    
    # Parent class
    type
      Employee* = ref object of RootObj
        s: float
    
    proc salary*(e: Employee): float =
      e.s
    
    proc `salary=`*(e: Employee, salary: float) =
      assert(salary >= 0.0)
      e.s = salary
    
    proc newEmployee*(salary: float): Employee =
      new(result)
      result.s = salary
    
    # Child class
    type
      Programmer* = ref object of Employee
        plns: seq[string]
        pes: seq[string]
    
    proc langs*(p: Programmer): seq[string] =
      p.plns
    
    proc `langs=`*(p: Programmer, langs: seq[string]) =
      p.plns = langs
    
    proc editors*(p: Programmer): seq[string] =
      p.pes
    
    proc `editors=`*(p: Programmer, editors: seq[string]) =
      p.pes = editors
    
    proc solve*(p: Programmer, problem: string) =
      randomize()
    
      let ln = p.langs[random(p.langs.low..p.langs.len)]
      let e = p.editors[random(p.editors.low..p.editors.len)]
    
      echo "The programmer solved " & problem & " in " & ln & " with " & e
    
    proc newProgrammer*(langs: seq[string], editors: seq[string], salary: float): Programmer =
      new(result)
      result.plns = langs
      result.pes = editors
      result.s = salary
    
    # Main program
    when isMainModule:
      let pr: Programmer = newProgrammer(
        langs = @["Go", "Rust", "D", "Nim"],
        editors = @["Atom", "Sublime Text", "Visual Studio Code"],
        salary = 1000)
    
      # Use the method from child class.
      pr.solve("Linked List")
      pr.solve("Tower of Hanoi")
    
      # Use the method from parent class.
      assert(pr.salary == 1000)

    目前 Nim 的问题在于仅有单一继承,却没有官方的接口 (interface) 或 mixin 等替代的方案,无法利用接口来实现一些设计模式。目前一些可行的替代方法:

    • 多用组合,少用继承:用类似 C 或 Go 语言的思维来写物件,见下文
    • 使用带有方法声明的 tuple:某种程度可仿真接口,见后续关于多态的说明
    • 使用模板 (template):跳脱类型的限制,详见后文

    组合

    组合 (composition) 的想法在于直接重用类,但类之间没有继承的关系,从外部来看,两个类是各自独立的。我们修改先前的例子,建立两个类,在这两个类中,Programmer 类直接重用 Employee 类:

    import random
    
    type
      Employee* = ref object
        s: float
    
    # Declare procedures as above.
    
    type
      Programmer* = ref object
        plns: seq[string]
        pes: seq[string]
        pee: Employee
    
    proc salary*(p: Programmer): float =
      p.pee.salary
    
    proc `salary=`*(p: Programmer, salary: float) =
      assert(salary >= 0.0)
      p.pee.salary = salary
    
    # Declare procedures as above.
    
    proc newProgrammer*(langs: seq[string], editors: seq[string], salary: float): Programmer =
      new(result)
      result.plns = langs
      result.pes = editors
      result.pee = newEmployee(salary = salary)
    
    when isMainModule:
      let pr: Programmer = newProgrammer(
        langs = @["Go", "Rust", "D", "Nim"],
        editors = @["Atom", "Sublime Text", "Visual Studio Code"],
        salary = 100)
    
      pr.solve("Linked List")
      pr.solve("Tower of Hanoi")
    
      assert(pr.salary == 100)

    如果单独使用这两个类,不会有什么问题,但如果需要一些多态的特性,这种方式则无法满足我们的需求。我们将于后文说明如何处理。

    【赞助商连结】