10 面向对象编程
第 10 章:面向对象编程
10.1 对象(Object)
Nim 的对象是值类型,存储在栈上(除非用 ref):
type
Point = object
x, y: float
Person = object
name: string
age: int
email: string
# 创建对象
var p1 = Point(x: 1.0, y: 2.0)
var p2 = Point(x: 3.0, y: 4.0)
echo p1.x, ", ", p1.y # 1.0, 2.0
# 对象是值类型
var p3 = p1
p3.x = 10.0
echo p1.x # 1.0(不受影响)
# 创建 Person
var person = Person(name: "张三", age: 28, email: "[email protected]")
echo person.name # 张三
10.1.1 对象构造
type
Config = object
host: string
port: int
debug: bool
# 逐字段构造
var cfg1 = Config(host: "localhost", port: 8080, debug: true)
# 部分构造(其余字段为默认值)
var cfg2 = Config(host: "0.0.0.0", port: 3000)
echo cfg2.debug # false
# 默认值
type
ServerConfig = object
host {.defaultValue: "localhost".}: string
port {.defaultValue: 8080.}: int
# 使用 proc 作为构造函数
proc newConfig(host = "localhost", port = 8080, debug = false): Config =
Config(host: host, port: port, debug: debug)
var cfg = newConfig(port = 3000, debug = true)
echo cfg
10.1.2 对象字段
type
User = object
id: int # 私有字段(不导出)
name*: string # 公开字段(导出)
age*: int
PublicUser = object
id*: int # 公开
name*: string
age*: int
# 私有字段只能在同一模块中访问
var u = User(id: 1, name: "Alice", age: 30)
echo u.name # ✅ 可访问
# echo u.id # ❌ 其他模块无法访问
10.2 引用对象(Ref Object)
ref object 是引用类型,存储在堆上,适合大对象和共享数据:
type
Node = ref object
value: int
next: Node
TreeNode = ref object
value: int
left, right: TreeNode
# 创建引用对象
var root = TreeNode(
value: 1,
left: TreeNode(value: 2),
right: TreeNode(value: 3)
)
# 引用语义——共享同一对象
var a = root
a.value = 100
echo root.value # 100(同一对象)
# nil 检查
var n: Node = nil
echo n.isNil # true
n = Node(value: 42, next: nil)
echo n.isNil # false
10.3 继承
Nim 支持单一继承,使用 ref object of 语法:
type
# 基类
Shape = ref object of RootObj
color: string
# 子类
Circle = ref object of Shape
radius: float
Rectangle = ref object of Shape
width, height: float
Square = ref object of Rectangle # 多层继承
# 构造
proc newCircle(radius: float, color = "red"): Circle =
Circle(radius: radius, color: color)
proc newRectangle(w, h: float, color = "blue"): Rectangle =
Rectangle(width: w, height: h, color: color)
proc newSquare(side: float, color = "green"): Square =
Square(width: side, height: side, color: color)
# 使用
let c = newCircle(5.0)
let r = newRectangle(4.0, 6.0)
let s = newSquare(3.0)
echo c.color # red
echo r.width # 4.0
echo s.width # 3.0
echo s.height # 3.0
10.4 方法与多态
10.4.1 方法(method)
method 支持动态分发(运行时多态):
type
Animal = ref object of RootObj
name: string
Dog = ref object of Animal
Cat = ref object of Animal
Bird = ref object of Animal
# 基类方法(必须标注 {.base.})
method speak(a: Animal): string {.base.} =
raise newException(CatchableError, "Not implemented")
method speak(d: Dog): string =
"汪汪!"
method speak(c: Cat): string =
"喵喵!"
method speak(b: Bird): string =
"叽叽!"
# 多态调用
let animals: seq[Animal] = @[
Dog(name: "旺财"),
Cat(name: "咪咪"),
Bird(name: "小黄"),
]
for a in animals:
echo &"{a.name}: {a.speak()}"
# 旺财: 汪汪!
# 咪咪: 喵喵!
# 小黄: 叽叽!
10.4.2 类型转换
type
Vehicle = ref object of RootObj
speed: float
Car = ref object of Vehicle
doors: int
Truck = ref object of Vehicle
payload: float
proc newCar(speed: float, doors: int): Car =
Car(speed: speed, doors: doors)
proc newTruck(speed, payload: float): Truck =
Truck(speed: speed, payload: payload)
# 向上转换(隐式)
let v: Vehicle = newCar(120.0, 4) # Car → Vehicle
# 向下转换(显式,运行时检查)
if v of Car:
let car = Car(v) # 或 v.Car
echo &"Car with {car.doors} doors"
# of 检查
proc describe(v: Vehicle) =
if v of Car:
echo "Car: ", Car(v).doors, " doors"
elif v of Truck:
echo "Truck: payload ", Truck(v).payload
else:
echo "Unknown vehicle"
10.4.3 method vs proc
type
Base = ref object of RootObj
Derived = ref object of Base
method testMethod(b: Base) {.base.} =
echo "Base method"
proc testProc(b: Base) =
echo "Base proc"
method testMethod(d: Derived) =
echo "Derived method"
proc testProc(d: Derived) =
echo "Derived proc"
let d: Base = Derived()
d.testMethod() # "Derived method"(动态分发)
d.testProc() # "Base proc"(静态分发)
⚠️ 注意:优先使用 proc,只有需要运行时多态时才用 method。
10.5 运算符重载
type Vec3 = object
x, y, z: float
proc vec3(x, y, z: float): Vec3 =
Vec3(x: x, y: y, z: z)
proc `+`(a, b: Vec3): Vec3 =
vec3(a.x+b.x, a.y+b.y, a.z+b.z)
proc `-`(a, b: Vec3): Vec3 =
vec3(a.x-b.x, a.y-b.y, a.z-b.z)
proc `*`(v: Vec3, s: float): Vec3 =
vec3(v.x*s, v.y*s, v.z*s)
proc `==`(a, b: Vec3): bool =
a.x == b.x and a.y == b.y and a.z == b.z
proc `$`(v: Vec3): string =
&"({v.x}, {v.y}, {v.z})"
let a = vec3(1, 2, 3)
let b = vec3(4, 5, 6)
echo a + b # (5.0, 7.0, 9.0)
echo a * 2.0 # (2.0, 4.0, 6.0)
10.6 封装模式
10.6.1 属性模式
type
BankAccount = ref object
balance: float
owner: string
proc newBankAccount(owner: string, initialBalance = 0.0): BankAccount =
BankAccount(balance: initialBalance, owner: owner)
# 通过 proc 封装访问
proc getBalance(account: BankAccount): float =
account.balance
proc deposit(account: BankAccount, amount: float) =
if amount <= 0:
raise newException(ValueError, "Amount must be positive")
account.balance += amount
proc withdraw(account: BankAccount, amount: float) =
if amount > account.balance:
raise newException(ValueError, "Insufficient funds")
account.balance -= amount
proc `$`(account: BankAccount): string =
&"{account.owner}: ${account.balance:.2f}"
var acc = newBankAccount("张三", 1000)
acc.deposit(500)
acc.withdraw(200)
echo acc # 张三: $1300.00
10.6.2 工厂模式
type
Connection = ref object of RootObj
host: string
port: int
MySQLConn = ref object of Connection
database: string
PostgresConn = ref object of Connection
schema: string
proc newConnection(dbType, host: string, port: int): Connection =
case dbType
of "mysql":
MySQLConn(host: host, port: port, database: "default")
of "postgres":
PostgresConn(host: host, port: port, schema: "public")
else:
raise newException(ValueError, "Unknown DB type: " & dbType)
let conn = newConnection("mysql", "localhost", 3306)
echo conn of MySQLConn # true
10.7 接口模拟
Nim 没有原生接口,但可以用 concept 或过程类型模拟:
# 使用 concept 模拟接口
type
Drawable = concept d
d.draw() is string
d.area() is float
type
Circle = object
radius: float
Rectangle = object
width, height: float
proc draw(c: Circle): string =
&"Drawing circle r={c.radius}"
proc area(c: Circle): float =
PI * c.radius * c.radius
proc draw(r: Rectangle): string =
&"Drawing rect {r.width}x{r.height}"
proc area(r: Rectangle): float =
r.width * r.height
# 接受任何满足 Drawable concept 的类型
proc render[T: Drawable](shape: T) =
echo shape.draw()
echo &"Area: {shape.area():.2f}"
render(Circle(radius: 5.0))
render(Rectangle(width: 4.0, height: 6.0))
10.8 实战示例
🏢 场景:插件系统
type
Plugin = ref object of RootObj
name*: string
version*: string
LoggerPlugin = ref object of Plugin
logFile: string
CachePlugin = ref object of Plugin
maxSize: int
cache: seq[string]
method init(p: Plugin) {.base.} =
echo &"Initializing plugin: {p.name}"
method init(lp: LoggerPlugin) =
echo &"Logger initialized, file: {lp.logFile}"
method init(cp: CachePlugin) =
echo &"Cache initialized, max: {cp.maxSize}"
method shutdown(p: Plugin) {.base.} =
echo &"Shutting down: {p.name}"
type PluginManager = object
plugins: seq[Plugin]
proc newPluginManager(): PluginManager =
PluginManager(plugins: @[])
proc register(pm: var PluginManager, plugin: Plugin) =
pm.plugins.add(plugin)
proc initAll(pm: PluginManager) =
for p in pm.plugins:
p.init()
proc shutdownAll(pm: PluginManager) =
for p in pm.plugins:
p.shutdown()
var pm = newPluginManager()
pm.register(LoggerPlugin(name: "Logger", version: "1.0", logFile: "app.log"))
pm.register(CachePlugin(name: "Cache", version: "2.0", maxSize: 1000))
pm.initAll()
pm.shutdownAll()
本章小结
| 概念 | 语法 | 说明 |
|---|---|---|
| 值对象 | type X = object | 栈分配,值语义 |
| 引用对象 | type X = ref object | 堆分配,引用语义 |
| 继承 | ref object of Base | 单一继承 |
| 方法 | method | 动态分发 |
| 过程 | proc | 静态分发 |
| 基类方法 | {.base.} | 标记基类方法 |
| 类型检查 | x of T | 运行时类型检查 |
| 类型转换 | T(x) 或 x.T | 向下转换 |
练习
- 实现一个简单的图形类层次结构(Shape → Circle/Rectangle/Triangle)
- 实现一个带继承的事件系统
- 使用
ref object实现一个链表 - 使用
concept创建一个可序列化接口