加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
本帖最后由 墨凌羽 于 2015-12-1 07:52 编辑
更新:修改了一楼提到的错误
在编写脚本,尤其是在做一些底层库的时候,接口实现是很重要的一环。而方便调用的接口对于使用者来说亦是非常关键。而这篇帖子,就是探讨接口实现这一话题。
先看一个例子:
想想看,我们创建并显示一个精灵(sprite)的时候要经过几个步骤?
sprite = Sprite.new ( )
sprite.bitmap = bitmap
#一般创建之后还要调整坐标之类的
sprite.x = 12
sprite.y = 13
sprite.z = 100
sprite = Sprite.new ( )
sprite.bitmap = bitmap
#一般创建之后还要调整坐标之类的
sprite.x = 12
sprite.y = 13
sprite.z = 100
如果是MV会麻烦些:
ClassName.prototype .method .function ( ) {
//一些其他部分的代码
this .sprite = new Sprite( bitmap) ;
this .sprite .x = 12 ;
this .sprite .y = 13 ;
this .addchid ( this .sprite ) ;
//一些其他代码
}
ClassName.prototype .method .function ( ) {
//一些其他部分的代码
this .sprite = new Sprite( bitmap) ;
this .sprite .x = 12 ;
this .sprite .y = 13 ;
this .addchid ( this .sprite ) ;
//一些其他代码
}
在创建sprite的时候,要调整的属性和要调用的方法并不是很多,如果是一些需要在创建后进行大量调整的对象,整个过程会变得很麻烦。【想想那一堆需要打的sprite】
有一个办法可以让你的接口在调用的时候更方便。这种方法被称为级联(cascade JavaScript语言精粹 Douglas Crockford )。
级联
级联多用于一些进行属性调整或者修改对象的某个状态。这样的原因是这些方法一般都没有返回值。级联的关键是让这些方法返回this(ruby中就是self)从而在一条语句中一次调用同一个对象的多个方法。
//一个级联的例子
var button = new Button( )
.move ( 12 ,23 )
.width ( 45 )
.height ( 200 )
.text ( "开始游戏" )
.on ( 'mousedown' , function ( ) { Scene.goto ( MapScene) } ) ;
//一个级联的例子
var button = new Button( )
.move ( 12 ,23 )
.width ( 45 )
.height ( 200 )
.text ( "开始游戏" )
.on ( 'mousedown' , function ( ) { Scene.goto ( MapScene) } ) ;
ruby的差不多就不写了。
要说明的是级联算是js中的一个概念,在node、jQuery里都有应用。不过当你明白他到底是啥后,很快就能在ruby里实现一遍。
级联的实现
js中的实现
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' ] ( )
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' ] ( )
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
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
这个范例中除了级联外,顺便实现了函数参数和它的调用【js中实现很简单,ruby中大概就是&和lambda,这个后面还会提到】
在ruby的实现中,每一个参数都要写一个函数来实现接口,不过使用元编程的话,可以实现类似于attr_XXX系列方法的效果。
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
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
其他部分
除了级联外,要实现好的接口,首先得熟悉函数的带有默认值参数【可缺省参数】、不定参数以及函数参数的编写方法。
参数默认值:
在ruby中使用下面的方法:
js中即使函数调用不传参依然不会报错【这个参数的值会被设置为undefined】所以js中只需要判断参数的值是否为undefined,如果是则为缺省,设置默认值。
不定参数
不定参数是指传递给函数的参数个数是不确定。例如一个sum的方法,效果是返回传入的所有参数的和。
ruby:
def sum( *arg)
sum = 0
arg.each { |i| sum += i}
return sum
end
def sum( *arg)
sum = 0
arg.each { |i| sum += i}
return sum
end
js:
function sum( ) {
var sum = 0 ;
var length = arguments.length ;
for ( var i = 0 ; i < length; i++) {
sum += arguments[ i] ;
}
return sum;
}
function sum( ) {
var sum = 0 ;
var length = arguments.length ;
for ( var i = 0 ; i < length; i++) {
sum += arguments[ i] ;
}
return sum;
}
这里用到了js中的arguments。arguments是js中函数调用时自动传递给函数的一个参数,他是一个函数的实参"数组"。函数可以通过访问这个数组来得到调用是传递给他的参数列表。要注意的是他本身并不是一个真正的数组。而只是一个类似数组的对象。
函数参数:
js中函数参数没啥要说的。主要说说ruby。ruby中可以通过定义带&的参数来使得函数可以接受一个block作为参数【要注意的是这样得到的是个proc类的实例,所以这里也会在传递函数的时候把它的运行环境一并传递过去。另外,一个block只能算是代码片段,而不是函数,所以如果要将这个传递过来的函数放入变量中,需要先把他转换为函数。【可以使用lambda来转换为匿名函数】
总结
接口设计本质上就是对于用作接口的函数的编写。最基本的就是各种函数的具体实现(带缺省值、不定参数、函数/方法参数)。而级联技术则可以让你的接口调用起来更方便【尤其是对于在设计如GUI之类需要进行大量的属性调整和对象状态调整的系统时尤为重要。】
但这并不是接口设计的全部,只能说这只是些极为初级的技巧除此之外,在ruby中一些元编程的方法也极为重要【例如最常用到的method_missing】在js中还有回调柯里化等。然而这些也都只是纯粹技术方面的了。然而接口设计最重要的还是思想/范式方面的一些东西。不过我个人才学疏浅,这篇帖子也只能算是抛砖引玉。