设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 3536|回复: 5
打印 上一主题 下一主题

[交流讨论] 【闲谈】【ruby|JavaScript】更富表现力的接口编写

[复制链接]

Lv1.梦旅人

梦石
0
星屑
48
在线时间
784 小时
注册时间
2013-1-4
帖子
1102
跳转到指定楼层
1
发表于 2015-11-19 23:06:08 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
本帖最后由 墨凌羽 于 2015-12-1 07:52 编辑

更新:修改了一楼提到的错误


在编写脚本,尤其是在做一些底层库的时候,接口实现是很重要的一环。而方便调用的接口对于使用者来说亦是非常关键。而这篇帖子,就是探讨接口实现这一话题。

先看一个例子:
想想看,我们创建并显示一个精灵(sprite)的时候要经过几个步骤?
RUBY 代码复制
  1. sprite = Sprite.new()
  2. sprite.bitmap = bitmap
  3. #一般创建之后还要调整坐标之类的
  4. sprite.x = 12
  5. sprite.y = 13
  6. sprite.z = 100

如果是MV会麻烦些:
JAVASCRIPT 代码复制
  1. ClassName.prototype.method.function () {
  2. //一些其他部分的代码
  3. this.sprite = new Sprite(bitmap);
  4. this.sprite.x = 12;
  5. this.sprite.y = 13;
  6. this.addchid(this.sprite);
  7. //一些其他代码
  8. }

在创建sprite的时候,要调整的属性和要调用的方法并不是很多,如果是一些需要在创建后进行大量调整的对象,整个过程会变得很麻烦。【想想那一堆需要打的sprite】
有一个办法可以让你的接口在调用的时候更方便。这种方法被称为级联(cascade JavaScript语言精粹  Douglas Crockford )。

级联
级联多用于一些进行属性调整或者修改对象的某个状态。这样的原因是这些方法一般都没有返回值。级联的关键是让这些方法返回this(ruby中就是self)从而在一条语句中一次调用同一个对象的多个方法。
JAVASCRIPT 代码复制
  1. //一个级联的例子
  2. var button = new Button()
  3.   .move(12,23)
  4.   .width(45)
  5.   .height(200)
  6.   .text("开始游戏")
  7.   .on('mousedown', function(){Scene.goto(MapScene)});

ruby的差不多就不写了。
要说明的是级联算是js中的一个概念,在node、jQuery里都有应用。不过当你明白他到底是啥后,很快就能在ruby里实现一遍。
级联的实现
js中的实现
JAVASCRIPT 代码复制
  1. A = function (id) {
  2.   this._id = id;
  3.   this._x = 0;
  4.   this._y = 0;
  5.   this.event = {};
  6.   return this;
  7. }
  8.  
  9. A.prototype.constructor = A;
  10.  
  11. A.prototype.x = function (val) {
  12.   if(val === undefined) return this._x;
  13.   this._x = val;
  14.   return this;
  15. }
  16.  
  17. A.prototype.y = function (val) {
  18.   if(val === undefined) return this._y;
  19.   this._y = val;
  20.   return this;
  21. }
  22.  
  23. A.prototype.method1 = function (val) {
  24.   this._x = this._x + val;
  25.   return this;
  26. }
  27.  
  28. A.prototype.method2 = function (val) {
  29.   this._y = this._y + val;
  30.   return this;
  31. }
  32.  
  33. A.prototype.set_event = function (name, func) {
  34.   Object.defineProperty(this.event, name, {value: func})
  35.   return this;
  36. }
  37.  
  38. a = new A(1)
  39.     .x(12)
  40.     .y(13)
  41.     .method1(12)
  42.     .set_event('click', function () {console.log(a.x(),a.y())})
  43.     .method2(12);
  44. a.event['click']()

ruby中等同效果的实现
RUBY 代码复制
  1. class A
  2.   attr_reader :id
  3.   attr_accessor :x,:y
  4.   def initialize(id)
  5.     @id = id
  6.     @x = 0
  7.     @y = 0
  8.     @event = {}
  9.   end
  10.  
  11.   def x(x = nil)
  12.     return @x if x == nil
  13.     @x = x
  14.     return self
  15.   end
  16.  
  17.   def y(y = nil)
  18.     return @y if y == nil
  19.     @y = y
  20.     return self
  21.   end
  22.  
  23.   def method1(x)
  24.     @x += x
  25.     self
  26.   end
  27.  
  28.   def method2(y)
  29.     @y += y
  30.     self
  31.   end
  32.  
  33.   def event(name = nil, &block)
  34.     return @event if(name == nil)
  35.     @event[name] = lambda &block
  36.     self
  37.   end
  38. end
  39.  
  40. a = A.new(1)
  41.   .x(12)
  42.   .y(15)
  43.   .method1(12)
  44.   .method2(13)
  45.   .event(:click){p a.x,a.y}
  46. a.event[:click].call

这个范例中除了级联外,顺便实现了函数参数和它的调用【js中实现很简单,ruby中大概就是&和lambda,这个后面还会提到】
在ruby的实现中,每一个参数都要写一个函数来实现接口,不过使用元编程的话,可以实现类似于attr_XXX系列方法的效果。
RUBY 代码复制
  1. class Class
  2.   def attr_cascade(*arg)
  3.     arg.each do |i|
  4.     attr_accessor i
  5.       define_method(i) do |val = nil|
  6.         return instance_variable_get('@'+i.id2name) if val == nil
  7.         instance_variable_set('@'+i.id2name, val)
  8.         self
  9.       end
  10.     end
  11.   end
  12. end


其他部分
除了级联外,要实现好的接口,首先得熟悉函数的带有默认值参数【可缺省参数】、不定参数以及函数参数的编写方法。

参数默认值:
在ruby中使用下面的方法:
RUBY 代码复制
  1. def foo(arg = nil)

js中即使函数调用不传参依然不会报错【这个参数的值会被设置为undefined】所以js中只需要判断参数的值是否为undefined,如果是则为缺省,设置默认值。

不定参数
不定参数是指传递给函数的参数个数是不确定。例如一个sum的方法,效果是返回传入的所有参数的和。
ruby:
RUBY 代码复制
  1. def sum(*arg)
  2.   sum = 0
  3.   arg.each{|i| sum += i}
  4.   return sum
  5. end

js:
JAVASCRIPT 代码复制
  1. function sum() {
  2.   var sum = 0;
  3.   var  length = arguments.length;
  4.   for (var i = 0; i < length; i++) {
  5.     sum += arguments[i];
  6.   }
  7.   return sum;
  8. }

这里用到了js中的arguments。arguments是js中函数调用时自动传递给函数的一个参数,他是一个函数的实参"数组"。函数可以通过访问这个数组来得到调用是传递给他的参数列表。要注意的是他本身并不是一个真正的数组。而只是一个类似数组的对象。

函数参数:
js中函数参数没啥要说的。主要说说ruby。ruby中可以通过定义带&的参数来使得函数可以接受一个block作为参数【要注意的是这样得到的是个proc类的实例,所以这里也会在传递函数的时候把它的运行环境一并传递过去。另外,一个block只能算是代码片段,而不是函数,所以如果要将这个传递过来的函数放入变量中,需要先把他转换为函数。【可以使用lambda来转换为匿名函数】

总结
接口设计本质上就是对于用作接口的函数的编写。最基本的就是各种函数的具体实现(带缺省值、不定参数、函数/方法参数)。而级联技术则可以让你的接口调用起来更方便【尤其是对于在设计如GUI之类需要进行大量的属性调整和对象状态调整的系统时尤为重要。】
但这并不是接口设计的全部,只能说这只是些极为初级的技巧除此之外,在ruby中一些元编程的方法也极为重要【例如最常用到的method_missing】在js中还有回调柯里化等。然而这些也都只是纯粹技术方面的了。然而接口设计最重要的还是思想/范式方面的一些东西。不过我个人才学疏浅,这篇帖子也只能算是抛砖引玉。

点评

不要自己发明术语,流畅API很常见的事情……  发表于 2015-11-22 15:26

评分

参与人数 2星屑 +39 收起 理由
orzfly + 20 塞糖
余烬之中 + 19 没糖了

查看全部评分

RM-GUI延期。。。最近被黑心老板压迫T_T
二次元少女的shitake,长着长脸,身高165,蓝色卷双马尾,FCUP,瞳色黑色,病气和御宅属性,是天才少女。

Lv6.析梦学徒

Fuzzy Ginkgo
Taciturn Knight

梦石
0
星屑
60475
在线时间
1933 小时
注册时间
2010-6-26
帖子
1605

烫烫烫开拓者

2
发表于 2015-11-21 11:33:39 | 只看该作者
本帖最后由 orzfly 于 2015-11-21 16:00 编辑
  1. A.prototype.x = function (val) {
  2.   if(!val) return this._x;
复制代码
Ruby 和 JavaScript 的一点显著不同,就是 Ruby 里除了 false 和 nil 是假的,都是真的……
而 JavaScript 里,0 和 ""(空字符串) 甚至都是假的。

需要指出的是 if (!val) 这么个写法是危险的,尤其是这个属性如果预期是布尔值的时候。
作为一个例子,建议写的严谨一点,比如 if (val === undefined)。

这样的错误在不少 npm 上一些看起来是专业选手的代码里都可以找到呢→_→

点评

void(0)  发表于 2015-11-22 20:14
undefined都可以重定义的,不是typeof好一点么  发表于 2015-11-22 20:07
反正 coffee 里面习惯性加个 ?  发表于 2015-11-21 12:46
正想来更正代码来着呢 js里面太多的东西等于false。。。。ORZ  发表于 2015-11-21 12:02
我的言论只代表我个人的观点,不代表雇主及/或任何第三方的立场。
Opinions expressed are solely my own and do not express the views or opinions of my employer and/or any third parties.
捐赠 | GitHub
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
29 小时
注册时间
2015-10-28
帖子
30
3
发表于 2015-11-21 21:59:43 | 只看该作者
orzfly 发表于 2015-11-21 11:33
Ruby 和 JavaScript 的一点显著不同,就是 Ruby 里除了 false 和 nil 是假的,都是真的……
而 JavaScript  ...

养成初始化习惯可以忽视这个问题
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
108 小时
注册时间
2012-7-3
帖子
44
4
发表于 2015-11-21 22:45:08 | 只看该作者
本帖最后由 Sylvania 于 2015-11-21 23:24 编辑

私以为目前 js 最优雅的还是 object,ruby 是关键字参数,同一接口不同语言相似的写法,似乎表现力更强一些

```coffee
sprite = new Sprite
  bitmap: bitmap
  x: 12
  y: 13
  z: 100
```

```js
sprite = new Sprite({
  bitmap: bitmap,
  x: 12,
  y: 13,
  z: 100
});
```

```ruby
sprite = Sprite.new
  bitmap: bitmap,
  x: 12,
  y: 13,
  z: 100
```


补一个 Button 的实现

```coffee
class Button
  constructor: (@args) ->
    this.x = @args.x || 0
    this.y = @args.y || 0
    this.z = @args.z || 0
    this.text = @args.text || 'button'
    for key, value of (@args.event || {})
      this[key] = value

button = new Button
  x: 12
  y: 13
  text: 'start'
  event:
    click: ->
      Scene.goto MapScene

button.click()
```

PS: 作为曾经的 Ruby 粉,现在已经无可救药地爱上了 coffee (*´ω`*)

点评

级联不仅仅是调整属性还可以做方法调用。还有ruby那样写真的大丈夫?  发表于 2015-11-22 00:02
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
48
在线时间
784 小时
注册时间
2013-1-4
帖子
1102
5
 楼主| 发表于 2015-11-22 00:08:27 | 只看该作者
Sylvania 发表于 2015-11-21 16:45
私以为目前 js 最优雅的还是 object,ruby 是关键字参数,同一接口不同语言相似的写法,似乎表现力更强一些 ...

实际上由于ruby语法要灵活的多,他的写法可以是各种各样的。比如下面这种‘js’式的写法:
  1. s = S.new {
  2.   this.x = 12
  3.   this.y = 23
  4. }
复制代码
实现方法差不多这样:
  1. def this
  2.   $this
  3. end

  4. class S
  5.   attr_accessor :x,:y
  6.   def initialize(&block)
  7.     @x = 0
  8.     @y = 0
  9.     $this = self
  10.     block.call
  11.   end
  12. end
复制代码
如果是黑method_missing的话你可以做到更多【之前显得蛋疼把一段js的代码在ruby里顺利执行。主要就是靠黑method_missing
RM-GUI延期。。。最近被黑心老板压迫T_T
二次元少女的shitake,长着长脸,身高165,蓝色卷双马尾,FCUP,瞳色黑色,病气和御宅属性,是天才少女。
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
108 小时
注册时间
2012-7-3
帖子
44
6
发表于 2015-11-22 14:00:21 | 只看该作者
本帖最后由 Sylvania 于 2015-11-22 14:08 编辑
墨凌羽 发表于 2015-11-22 00:08
实际上由于ruby语法要灵活的多,他的写法可以是各种各样的。比如下面这种‘js’式的写法:实现方法差不多 ...


Ruby 不定参数的处理还是关键字参数多一些,2.0 以前用的是 hash
前面 ruby 的写法是我的疏忽,和 block 一样,如果不写在一行上,加一个 ```\``` 即可


```ruby
class Button
  attr_accessor :x, :y, :z, :text
  def initialize *args
    hash = args[0]
    @x = hash[:x] || 0
    @y = hash[:y] || 0
    @z = hash[:z] || 0
    @text = hash[:text] || 0
    @event = hash[:event] || {}
  end

  def method_missing name, *args
    return @event[name].call *args if @event[name]
    super name, *args
  end
end

button = Button.new \
  x: 12,
  y: 13,
  text: 'start',
  event: {
    click: -> { puts button.text }
  }

button.click
```
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-4-25 19:58

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表