組合和繼承是兩種不同思維的重用程式碼的方式,本文介紹在 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)
如果單獨使用這兩個類別,不會有什麼問題,但如果需要一些多型的特性,這種方式則無法滿足我們的需求。我們將於後文說明如何處理。