Nim 語言程式教學:組合 (Composition) 和繼承 (Inheritance)

PUBLISHED ON APR 1, 2018 — PROGRAMMING
FacebookTwitter LinkedIn LINE Skype EverNote GMail Email Email

    組合和繼承是兩種不同思維的重用程式碼的方式,本文介紹在 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)

    如果單獨使用這兩個類別,不會有什麼問題,但如果需要一些多型的特性,這種方式則無法滿足我們的需求。我們將於後文說明如何處理。