summaryrefslogtreecommitdiff
path: root/doc/docs/zh/doc/objects/object-oriented-programming.md
diff options
context:
space:
mode:
Diffstat (limited to 'doc/docs/zh/doc/objects/object-oriented-programming.md')
-rw-r--r--doc/docs/zh/doc/objects/object-oriented-programming.md550
1 files changed, 550 insertions, 0 deletions
diff --git a/doc/docs/zh/doc/objects/object-oriented-programming.md b/doc/docs/zh/doc/objects/object-oriented-programming.md
new file mode 100644
index 0000000..9eb94d8
--- /dev/null
+++ b/doc/docs/zh/doc/objects/object-oriented-programming.md
@@ -0,0 +1,550 @@
1# 面向对象编程
2
3  在以下的示例中,月之脚本生成的 Lua 代码可能看起来会很复杂。所以最好主要关注月之脚本代码层面的意义,然后如果你想知道关于面向对象功能的实现细节,再查看 Lua 代码。
4
5  一个简单的类:
6
7```yuescript
8class Inventory
9 new: =>
10 @items = {}
11
12 add_item: (name) =>
13 if @items[name]
14 @items[name] += 1
15 else
16 @items[name] = 1
17```
18<YueDisplay>
19
20```yue
21class Inventory
22 new: =>
23 @items = {}
24
25 add_item: (name) =>
26 if @items[name]
27 @items[name] += 1
28 else
29 @items[name] = 1
30```
31
32</YueDisplay>
33
34&emsp;&emsp;在月之脚本中采用面向对象的编程方式时,通常会使用类声明语句结合 Lua 表格字面量来做类定义。这个类的定义包含了它的所有方法和属性。在这种结构中,键名为 “new” 的成员扮演了一个重要的角色,是作为构造函数来使用。
35
36&emsp;&emsp;值得注意的是,类中的方法都采用了粗箭头函数语法。当在类的实例上调用方法时,该实例会自动作为第一个参数被传入,因此粗箭头函数用于生成一个名为 “self” 的参数。
37
38&emsp;&emsp;此外,“@” 前缀在变量名上起到了简化作用,代表 “self”。例如,`@items` 就等同于 `self.items`。
39
40&emsp;&emsp;为了创建类的一个新实例,可以将类名当作一个函数来调用,这样就可以生成并返回一个新的实例。
41
42```yuescript
43inv = Inventory!
44inv\add_item "t-shirt"
45inv\add_item "pants"
46```
47<YueDisplay>
48
49
50```yue
51inv = Inventory!
52inv\add_item "t-shirt"
53inv\add_item "pants"
54```
55
56</YueDisplay>
57
58&emsp;&emsp;在月之脚本的类中,由于需要将类的实例作为参数传入到调用的方法中,因此使用了 **\\** 操作符做类的成员函数调用。
59
60&emsp;&emsp;需要特别注意的是,类的所有属性在其实例之间是共享的。这对于函数类型的成员属性通常不会造成问题,但对于其他类型的属性,可能会导致意外的结果。
61
62&emsp;&emsp;例如,在下面的示例中,clothes 属性在所有实例之间共享。因此,对这个属性在一个实例中的修改,将会影响到其他所有实例。
63
64```yuescript
65class Person
66 clothes: []
67 give_item: (name) =>
68 table.insert @clothes, name
69
70a = Person!
71b = Person!
72
73a\give_item "pants"
74b\give_item "shirt"
75
76-- 会同时打印出裤子和衬衫
77print item for item in *a.clothes
78```
79<YueDisplay>
80
81```yue
82class Person
83 clothes: []
84 give_item: (name) =>
85 table.insert @clothes, name
86
87a = Person!
88b = Person!
89
90a\give_item "pants"
91b\give_item "shirt"
92
93-- 会同时打印出裤子和衬衫
94print item for item in *a.clothes
95```
96
97</YueDisplay>
98
99&emsp;&emsp;避免这个问题的正确方法是在构造函数中创建对象的可变状态:
100
101```yuescript
102class Person
103 new: =>
104 @clothes = []
105```
106<YueDisplay>
107
108```yue
109class Person
110 new: =>
111 @clothes = []
112```
113
114</YueDisplay>
115
116## 继承
117
118&emsp;&emsp;`extends` 关键字可以在类声明中使用,以继承另一个类的属性和方法。
119
120```yuescript
121class BackPack extends Inventory
122 size: 10
123 add_item: (name) =>
124 if #@items > size then error "背包已满"
125 super name
126```
127<YueDisplay>
128
129```yue
130class BackPack extends Inventory
131 size: 10
132 add_item: (name) =>
133 if #@items > size then error "背包已满"
134 super name
135```
136
137</YueDisplay>
138
139&emsp;&emsp;在这一部分,我们对月之脚本中的 `Inventory` 类进行了扩展,加入了对可以携带物品数量的限制。
140
141&emsp;&emsp;在这个特定的例子中,子类并没有定义自己的构造函数。因此,当创建一个新的实例时,系统会默认调用父类的构造函数。但如果我们在子类中定义了构造函数,我们可以利用 `super` 方法来调用并执行父类的构造函数。
142
143&emsp;&emsp;此外,当一个类继承自另一个类时,它会尝试调用父类上的 `__inherited` 方法(如果这个方法存在的话),以此来向父类发送通知。这个 `__inherited` 函数接受两个参数:被继承的父类和继承的子类。
144
145```yuescript
146class Shelf
147 @__inherited: (child) =>
148 print @__name, "被", child.__name, "继承"
149
150-- 将打印: Shelf 被 Cupboard 继承
151class Cupboard extends Shelf
152```
153<YueDisplay>
154
155```yue
156class Shelf
157 @__inherited: (child) =>
158 print @__name, "被", child.__name, "继承"
159
160-- 将打印: Shelf 被 Cupboard 继承
161class Cupboard extends Shelf
162```
163
164</YueDisplay>
165
166## super 关键字
167
168&emsp;&emsp;`super` 是一个特别的关键字,它有两种不同的使用方式:既可以当作一个对象来看待,也可以像调用函数那样使用。它仅在类的内部使用时具有特殊的功能。
169
170&emsp;&emsp;当 `super` 被作为一个函数调用时,它将调用父类中与之同名的函数。此时,当前的 `self` 会自动作为第一个参数传递,正如上面提到的继承示例所展示的那样。
171
172&emsp;&emsp;在将 `super` 当作普通值使用时,它实际上是对父类对象的引用。通过这种方式,我们可以访问父类中可能被子类覆盖的值,就像访问任何普通对象一样。
173
174&emsp;&emsp;此外,当使用 `\` 操作符与 `super` 一起使用时,`self`将被插入为第一个参数,而不是使用 `super` 本身的值。而在使用`.`操作符来检索函数时,则会返回父类中的原始函数。
175
176&emsp;&emsp;下面是一些使用 `super` 的不同方法的示例:
177
178```yuescript
179class MyClass extends ParentClass
180 a_method: =>
181 -- 以下效果相同:
182 super "你好", "世界"
183 super\a_method "你好", "世界"
184 super.a_method self, "你好", "世界"
185
186 -- super 作为值等于父类:
187 assert super == ParentClass
188```
189<YueDisplay>
190
191```yue
192class MyClass extends ParentClass
193 a_method: =>
194 -- 以下效果相同:
195 super "你好", "世界"
196 super\a_method "你好", "世界"
197 super.a_method self, "你好", "世界"
198
199 -- super 作为值等于父类:
200 assert super == ParentClass
201```
202
203</YueDisplay>
204
205&emsp;&emsp;**super** 也可以用在函数存根的左侧。唯一的主要区别是,生成的函数不是绑定到 super 的值,而是绑定到 self。
206
207## 类型
208
209&emsp;&emsp;每个类的实例都带有它的类型。这存储在特殊的 \_\_class 属性中。此属性会保存类对象。类对象是我们用来构建新实例的对象。我们还可以索引类对象以检索类方法和属性。
210
211```yuescript
212b = BackPack!
213assert b.__class == BackPack
214
215print BackPack.size -- 打印 10
216```
217<YueDisplay>
218
219```yue
220b = BackPack!
221assert b.__class == BackPack
222
223print BackPack.size -- 打印 10
224```
225
226</YueDisplay>
227
228## 类对象
229
230&emsp;&emsp;在月之脚本中,当我们编写类的定义语句时,实际上是在创建一个类对象。这个类对象被保存在一个与该类同名的变量中。
231
232&emsp;&emsp;类对象具有函数的特性,可以被调用来创建新的实例。这正是我们在之前示例中所展示的创建类实例的方式。
233
234&emsp;&emsp;一个类由两个表构成:类表本身和一个基表。基表作为所有实例的元表。在类声明中列出的所有属性都存放在基表中。
235
236&emsp;&emsp;如果在类对象的元表中找不到某个属性,系统会从基表中检索该属性。这就意味着我们可以直接从类本身访问到其方法和属性。
237
238&emsp;&emsp;需要特别注意的是,对类对象的赋值并不会影响到基表,因此这不是向实例添加新方法的正确方式。相反,需要直接修改基表。关于这点,可以参考下面的 “__base” 字段。
239
240&emsp;&emsp;此外,类对象包含几个特殊的属性:当类被声明时,类的名称会作为一个字符串存储在类对象的 “__name” 字段中。
241
242```yuescript
243print BackPack.__name -- 打印 Backpack
244```
245<YueDisplay>
246
247```yue
248print BackPack.__name -- 打印 Backpack
249```
250
251</YueDisplay>
252
253&emsp;&emsp;基础对象被保存在一个名为 `__base` 的特殊表中。我们可以编辑这个表,以便为那些已经创建出来的实例和还未创建的实例增加新的功能。
254
255&emsp;&emsp;另外,如果一个类是从另一个类派生而来的,那么其父类对象则会被存储在名为 `__parent` 的地方。这种机制允许在类之间实现继承和功能扩展。
256
257## 类变量
258
259&emsp;&emsp;我们可以直接在类对象中创建变量,而不是在类的基对象中,通过在类声明中的属性名前使用 @。
260
261```yuescript
262class Things
263 @some_func: => print "Hello from", @__name
264
265Things\some_func!
266
267-- 类变量在实例中不可见
268assert Things().some_func == nil
269```
270<YueDisplay>
271
272```yue
273class Things
274 @some_func: => print "Hello from", @__name
275
276Things\some_func!
277
278-- 类变量在实例中不可见
279assert Things().some_func == nil
280```
281
282</YueDisplay>
283
284&emsp;&emsp;在表达式中,我们可以使用 @@ 来访问存储在 `self.__class` 中的值。因此,`@@hello` 是 `self.__class.hello` 的简写。
285
286```yuescript
287class Counter
288 @count: 0
289
290 new: =>
291 @@count += 1
292
293Counter!
294Counter!
295
296print Counter.count -- 输出 2
297```
298<YueDisplay>
299
300```yue
301class Counter
302 @count: 0
303
304 new: =>
305 @@count += 1
306
307Counter!
308Counter!
309
310print Counter.count -- 输出 2
311```
312
313</YueDisplay>
314
315&emsp;&emsp;@@ 的调用语义与 @ 类似。调用 @@ 时,会使用 Lua 的冒号语法将类作为第一个参数传入。
316
317```yuescript
318@@hello 1,2,3,4
319```
320<YueDisplay>
321
322```yue
323@@hello 1,2,3,4
324```
325
326</YueDisplay>
327
328## 类声明语句
329
330&emsp;&emsp;在类声明的主体中,除了键/值对外,我们还可以编写普通的表达式。在这种类声明体中的普通代码的上下文中,self 等于类对象,而不是实例对象。
331
332&emsp;&emsp;以下是创建类变量的另一种方法:
333
334```yuescript
335class Things
336 @class_var = "hello world"
337```
338<YueDisplay>
339
340```yue
341class Things
342 @class_var = "hello world"
343```
344
345</YueDisplay>
346
347&emsp;&emsp;这些表达式会在所有属性被添加到类的基对象后执行。
348
349&emsp;&emsp;在类的主体中声明的所有变量都会限制作用域只在类声明的范围。这对于放置只有类方法可以访问的私有值或辅助函数很方便:
350
351```yuescript
352class MoreThings
353 secret = 123
354 log = (msg) -> print "LOG:", msg
355
356 some_method: =>
357 log "hello world: " .. secret
358```
359<YueDisplay>
360
361```yue
362class MoreThings
363 secret = 123
364 log = (msg) -> print "LOG:", msg
365
366 some_method: =>
367 log "hello world: " .. secret
368```
369
370</YueDisplay>
371
372## @ 和 @@ 值
373
374&emsp;&emsp;当 @ 和 @@ 前缀在一个名字前时,它们分别代表在 self 和 self.\_\_class 中访问的那个名字。
375
376&emsp;&emsp;如果它们单独使用,它们是 self 和 self.\_\_class 的别名。
377
378```yuescript
379assert @ == self
380assert @@ == self.__class
381```
382<YueDisplay>
383
384```yue
385assert @ == self
386assert @@ == self.__class
387```
388
389</YueDisplay>
390
391&emsp;&emsp;例如,使用 @@ 从实例方法快速创建同一类的新实例的方法:
392
393```yuescript
394some_instance_method = (...) => @@ ...
395```
396<YueDisplay>
397
398```yue
399some_instance_method = (...) => @@ ...
400```
401
402</YueDisplay>
403
404## 构造属性提升
405
406&emsp;&emsp;为了减少编写简单值对象定义的代码。你可以这样简单写一个类:
407
408```yuescript
409class Something
410 new: (@foo, @bar, @@biz, @@baz) =>
411
412-- 这是以下声明的简写形式
413
414class Something
415 new: (foo, bar, biz, baz) =>
416 @foo = foo
417 @bar = bar
418 @@biz = biz
419 @@baz = baz
420```
421<YueDisplay>
422
423```yue
424class Something
425 new: (@foo, @bar, @@biz, @@baz) =>
426
427-- 这是以下声明的简写形式
428
429class Something
430 new: (foo, bar, biz, baz) =>
431 @foo = foo
432 @bar = bar
433 @@biz = biz
434 @@baz = baz
435```
436
437</YueDisplay>
438
439&emsp;&emsp;你也可以使用这种语法为一个函数初始化传入对象的字段。
440
441```yuescript
442new = (@fieldA, @fieldB) => @
443obj = new {}, 123, "abc"
444print obj
445```
446<YueDisplay>
447
448```yue
449new = (@fieldA, @fieldB) => @
450obj = new {}, 123, "abc"
451print obj
452```
453
454</YueDisplay>
455
456## 类表达式
457
458&emsp;&emsp;类声明的语法也可以作为一个表达式使用,可以赋值给一个变量或者被返回语句返回。
459
460```yuescript
461x = class Bucket
462 drops: 0
463 add_drop: => @drops += 1
464```
465<YueDisplay>
466
467```yue
468x = class Bucket
469 drops: 0
470 add_drop: => @drops += 1
471```
472
473</YueDisplay>
474
475## 匿名类
476
477&emsp;&emsp;声明类时可以省略名称。如果类的表达式不在赋值语句中,\_\_name 属性将为 nil。如果出现在赋值语句中,赋值操作左侧的名称将代替 nil。
478
479```yuescript
480BigBucket = class extends Bucket
481 add_drop: => @drops += 10
482
483assert Bucket.__name == "BigBucket"
484```
485<YueDisplay>
486
487```yue
488BigBucket = class extends Bucket
489 add_drop: => @drops += 10
490
491assert Bucket.__name == "BigBucket"
492```
493
494</YueDisplay>
495
496&emsp;&emsp;你甚至可以省略掉主体,这意味着你可以这样写一个空白的匿名类:
497
498```yuescript
499x = class
500```
501<YueDisplay>
502
503```yue
504x = class
505```
506
507</YueDisplay>
508
509## 类混合
510
511&emsp;&emsp;你可以通过使用 `using` 关键字来实现类混合。这意味着你可以从一个普通 Lua 表格或已定义的类对象中,复制函数到你创建的新类中。当你使用普通 Lua 表格进行类混合时,你有机会用自己的实现来重写类的索引方法(例如元方法 `__index`)。然而,当你从一个类对象做混合时,需要注意的是该类对象的元方法将不会被复制到新类。
512
513```yuescript
514MyIndex = __index: var: 1
515
516class X using MyIndex
517 func: =>
518 print 123
519
520x = X!
521print x.var
522
523class Y using X
524
525y = Y!
526y\func!
527
528assert y.__class.__parent ~= X -- X 不是 Y 的父类
529```
530<YueDisplay>
531
532```yue
533MyIndex = __index: var: 1
534
535class X using MyIndex
536 func: =>
537 print 123
538
539x = X!
540print x.var
541
542class Y using X
543
544y = Y!
545y\func!
546
547assert y.__class.__parent ~= X -- X 不是 Y 的父类
548```
549
550</YueDisplay>