查看: 4328|回复: 5

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

[复制链接]
梦石
0
星屑
48
在线时间
784 小时
注册时间
2013-1-4
回帖
1057
发表于 2015-11-19 23:06:08 | 显示全部楼层 |阅读模式

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

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

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

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


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

先看一个例子:
想想看,我们创建并显示一个精灵(sprite)的时候要经过几个步骤?
[pre lang="ruby"]sprite = Sprite.new()
sprite.bitmap = bitmap
#一般创建之后还要调整坐标之类的
sprite.x = 12
sprite.y = 13
sprite.z = 100
[/pre]
如果是MV会麻烦些:
[pre lang="javascript"]ClassName.prototype.method.function () {
//一些其他部分的代码
this.sprite = new Sprite(bitmap);
this.sprite.x = 12;
this.sprite.y = 13;
this.addchid(this.sprite);
//一些其他代码
}[/pre]
在创建sprite的时候,要调整的属性和要调用的方法并不是很多,如果是一些需要在创建后进行大量调整的对象,整个过程会变得很麻烦。【想想那一堆需要打的sprite】
有一个办法可以让你的接口在调用的时候更方便。这种方法被称为级联(cascade JavaScript语言精粹  Douglas Crockford )。

级联
级联多用于一些进行属性调整或者修改对象的某个状态。这样的原因是这些方法一般都没有返回值。级联的关键是让这些方法返回this(ruby中就是self)从而在一条语句中一次调用同一个对象的多个方法。
[pre lang="javascript"]//一个级联的例子
var button = new Button()
  .move(12,23)
  .width(45)
  .height(200)
  .text("开始游戏")
  .on('mousedown', function(){Scene.goto(MapScene)});[/pre]
ruby的差不多就不写了。
要说明的是级联算是js中的一个概念,在node、jQuery里都有应用。不过当你明白他到底是啥后,很快就能在ruby里实现一遍。
级联的实现
js中的实现
[pre lang="javascript"]A = function (id) {
  this._id = id;
  this._x = 0;
  this._y = 0;
  this.event = {};
  return this;
}

A.prototype.constructor = A;

A.prototype.x = function (val) {
  if(val === undefined) return this._x;
  this._x = val;
  return this;
}

A.prototype.y = function (val) {
  if(val === undefined) return this._y;
  this._y = val;
  return this;
}

A.prototype.method1 = function (val) {
  this._x = this._x + val;
  return this;
}

A.prototype.method2 = function (val) {
  this._y = this._y + val;
  return this;
}

A.prototype.set_event = function (name, func) {
  Object.defineProperty(this.event, name, {value: func})
  return this;
}

a = new A(1)
    .x(12)
    .y(13)
    .method1(12)
    .set_event('click', function () {console.log(a.x(),a.y())})
    .method2(12);
a.event['click']()[/pre]
ruby中等同效果的实现
[pre lang="ruby"]class A
  attr_reader :id
  attr_accessor :x,:y
  def initialize(id)
    @id = id
    @x = 0
    @y = 0
    @event = {}
  end
   
  def x(x = nil)
    return @x if x == nil
    @x = x
    return self
  end
  
  def y(y = nil)
    return @y if y == nil
    @y = y
    return self
  end
  
  def method1(x)
    @x += x
    self
  end
  
  def method2(y)
    @y += y
    self
  end
  
  def event(name = nil, &block)
    return @event if(name == nil)
    @event[name] = lambda &block
    self
  end
end

a = A.new(1)
  .x(12)
  .y(15)
  .method1(12)
  .method2(13)
  .event(:click){p a.x,a.y}
a.event[:click].call[/pre]
这个范例中除了级联外,顺便实现了函数参数和它的调用【js中实现很简单,ruby中大概就是&和lambda,这个后面还会提到】
在ruby的实现中,每一个参数都要写一个函数来实现接口,不过使用元编程的话,可以实现类似于attr_XXX系列方法的效果。
[pre lang="ruby"]class Class
  def attr_cascade(*arg)
    arg.each do |i|
    attr_accessor i
      define_method(i) do |val = nil|
        return instance_variable_get('@'+i.id2name) if val == nil
        instance_variable_set('@'+i.id2name, val)
        self
      end
    end
  end
end[/pre]

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

参数默认值:
在ruby中使用下面的方法:
[pre lang="ruby"]def foo(arg = nil)[/pre]
js中即使函数调用不传参依然不会报错【这个参数的值会被设置为undefined】所以js中只需要判断参数的值是否为undefined,如果是则为缺省,设置默认值。

不定参数
不定参数是指传递给函数的参数个数是不确定。例如一个sum的方法,效果是返回传入的所有参数的和。
ruby:
[pre lang="ruby"]def sum(*arg)
  sum = 0
  arg.each{|i| sum += i}
  return sum
end[/pre]
js:
[pre lang="javascript"]function sum() {
  var sum = 0;
  var  length = arguments.length;
  for (var i = 0; i < length; i++) {
    sum += arguments;
  }
  return sum;
}[/pre]
这里用到了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,瞳色黑色,病气和御宅属性,是天才少女。

Fuzzy Ginkgo

梦石
0
星屑
61105
在线时间
1933 小时
注册时间
2010-6-26
回帖
1535

烫烫烫开拓者

发表于 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
回复

使用道具 举报

梦石
0
星屑
50
在线时间
29 小时
注册时间
2015-10-28
回帖
27
发表于 2015-11-21 21:59:43 | 显示全部楼层
orzfly 发表于 2015-11-21 11:33
Ruby 和 JavaScript 的一点显著不同,就是 Ruby 里除了 false 和 nil 是假的,都是真的……
而 JavaScript  ...

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

使用道具 举报

梦石
0
星屑
50
在线时间
108 小时
注册时间
2012-7-3
回帖
44
发表于 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
回复

使用道具 举报

梦石
0
星屑
48
在线时间
784 小时
注册时间
2013-1-4
回帖
1057
 楼主| 发表于 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,瞳色黑色,病气和御宅属性,是天才少女。
回复

使用道具 举报

梦石
0
星屑
50
在线时间
108 小时
注册时间
2012-7-3
回帖
44
发表于 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
```
回复

使用道具 举报

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

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部