如何設計出漂亮的 Ruby APIs

Python018

如何設計出漂亮的 Ruby APIs,第1张

1.Argument Processing

Ruby 使用了 Symbols 和 Hash 来达到虚拟关键字参数(Pseudo-Keyword Arguments)。这种技巧被广泛应用在 Ruby 的函式库和 Rails 中,增加了阅读性,也很容易使用。

def blah(options)

puts options[:foo]

puts options[:bar]

end

blah(:foo =>"test", :bar =>"test")

Ruby 也可以将参数列当成阵列使用:

def sum(*args)

puts args[0]

puts args[1]

puts args[2]

puts args[3]

end

sum(1,2,3)

如此就可以设计出不固定参数列、十分弹性的 API。类似於 C++ 的 function overloading。在 Rails 中也十分常见这样的 API 设计,例如 link_to 就支援了两种用法:

# USAGE-1 without block

<% link_to 'Posts list', posts_path, :class =>'posts' %>

# USAGE-2 with block

<% link_to posts_path, :class =>'posts' do %>

Posts list

<% end %>

搭配虚拟关键字参数使用的话,可以参考 ActiveSupport#extract_options! 这个小技巧取出 Hash 值。

2. Code Blocks

程式区块(Block)是 Ruby 最重要的特色,除了拿来做迭代(Iteration)之外,也可以包装前後置处理(pre- and Post-processing),一个最基本的例子就是开档了,一般程序式的写法如下:

f = File.open("myfile.txt", 'w')

f.write("Lorem ipsum dolor sit amet")

f.write("Lorem ipsum dolor sit amet")

f.close

使用 Block 之後,我们可以将 f.close 包装起来,不需要明确呼叫。只要程式区块结束,Ruby 就会自动关档。程式一来因为缩排变得有结构,二来也确定档案一定会关闭(不然就语法错误了)

# using block

File.open("myfile.txt", 'w') do |f|

f.write("Lorem ipsum dolor sit amet")

f.write("Lorem ipsum dolor sit amet")

end

另一个程式区块的技法,是用来当做回呼(Dynamic Callbacks)。在 Ruby 中,程式区块也是物件,於是我们可以将程式区块如透过”注册”的方式先储存下来,之後再依照需求找出来执行。例如在 Sinatra 程式中:

get '/posts' do

#.. show something ..

end

post '/posts' do

#.. create something ..

end

我们”注册”了两个回呼:一是当浏览器送出 GET ‘/posts’ 时,会执行 show something 的程式区块,二是 POST ‘/posts’ 时。

3. Module

模组(Module)是 Ruby 用来解决多重继承问题的设计。其中有一招 Dual interface 值得一提:

module Logger

extend self

def log(message)

$stdout.puts "#{message} at #{Time.now}"

end

end

Logger.log("test") # as Logger’s class method

class MyClass

include Logger

end

MyClass.new.log("test") # as MyClass’s instance method

Ruby 的 extend 作用是将模组混入(mix-in)进单件类别(singleton class),於是 log 这个方法除了可以像一般的模组被混入 MyClass 中使用,也可以直接用 Logger.log 呼叫。

要将 Ruby 模组的混入成类别方法(class method),也有一些常见的 pattern 模式,可以将模组设计可以同时混入实例方法(instance method)和类别方法,请参阅投影片范例。这在撰写 Rails plugin 时非常常用。

4. method_missing?

Ruby 的 Missing 方法是当你呼叫一个不存在的方法时,Ruby 仍然有办法处理。它会改呼叫 method_missing 这个方法,并把这个不存在的方法名称传进去当做参数。这个技巧在 Rails 的 ActiveRecord 中拿来使用:

class Person <ActiveRecord::Base

end

p1 = Person.find_by_name("ihower")

p2 = Person.find_by_name_and_email("ihower", "[email protected]")

其中 find_by_name 和 find_by_email 就是这样的方法。不过这个技巧不是万能丹,它的执行效率并不好,所以只适合用在你没办法预先知道方法名称的情况下。不过也不是没有补救之道,如果同样的方法还会继续呼叫到,你可以在 method_missing 之中用 define_method 或 class_eval 动态定义此方法,那麼下次呼叫就不会进来 method_missing,进而获得效能的改善。事实上,ActiveRecord::Base 的 method_missing 就是这麼做的。(感谢 BigCat 留言提醒我有此补救之道)

另一个 Missing 方法的绝妙 API 设计,是拿来构建 XML 文件:

builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2)

builder.person do |b|

b.name("Jim")

b.phone("555-1234")

b.address("Taipei, Taiwan")

end

# <person>

# <name>Jim</name>

# <phone>555-1234</phone>

# <address>Taipei, Taiwan</address>

# </person>

搭配了区块功能,就能用 Ruby 语法来写 XML,非常厉害。

5. const_missing

除了 method_missing,Ruby 也有 const_missing。顾名思义就是找不到此常数时,会呼叫一个叫做 const_missing 的方法。现实中的例子有 Rails 的 ActiveSupport::Dependencies,它帮助我们不需要先载入所有类别档案,而是当 Rails 碰到一个还不认识的常数时,它会自动根据惯例,找到该档案载入。

我们也可以利用这个技巧,针对特定的常数规则来处理。例如以下的程式会自动将 U 开头的常数,自动转译成 Unicode 码:

class Module

original_c_m = instance_method(:const_missing)

define_method(:const_missing) do |name|

if name.to_s =~ /^U([0-9a-fA-F]{4})$/

[$1.to_i(16)].pack("U*")

else

original_c_m.bind(self).call(name)

end

end

end

puts U0123 # ģ

puts U9999 # 香

6. Methods chaining

方法串接是一个很常见的 API 设计,透过将方法的回传值设成 self,我们就可以串接起来。例如:

[1,1,2,3,3,4,5].uniq!.reject!{ |i| i%2 == 0 }.reverse

# 5,3,1

7. Core extension

Ruby 的类别是开放的,可以随时打开它新增一点程式或是修改。即使是核心类别如 Fixnum 或是 Object(这是所有类别的父类别) 都一样。例如 Rails 就定义了一些时间方法在 Fixnum 里:

class Fixnum

def hours

self * 3600 # 一小时有多少秒

end

alias hour hours

end

Time.now + 14.hours

Ruby 的物件模型与元编程(Meta-programming)

在 Ruby 中,所有东西都是物件。甚至包括类别(class)本身也是物件。这个类别物件(class object)是一个叫做 Class 的类别所实例出来的物件。而所有的物件(当然也包括类别物件),都有一个 metaclass (又叫做 singleton, eigenclass, ghost class, virtual class 等名字)。定义在 metaclass 里的方法,只有该物件能够使用,也就是 singleton method (单件方法),只有该物件才有的方法。

了解什麼是 metaclass 是 Ruby 元编程的一个重要前提知识。Ruby 元编程最常用的用途,就是因应需求可以动态地定义方法,例如在 Rails ActiveRecord 中常见的 Class Macro 应用。

要能随心所欲动态定义方法的关键重点,就是 variable scope (变数的作用域) 了。例如以下我们透过 class_eval 和 define_method 帮 String 定义了一个 say 方法,注意到整个 variable scope 都是通透的,没有建立新的 scope:

name = "say"

var = "it’s awesome"

String.class_eval do

define_method(name) do

puts var

end

end

"ihower".say # it’s awesome

class_eval 可以让我们改变 method definition 区域(又叫做 current class)。除了本投影片,建议可以阅读 Metaprogramming in Ruby: It’s Allhe Self 和 Three implicit contexts in Ruby 这两篇文章深入了解 self 和 current class。

8. Class Macro (Ruby’s declarative style)

Class Macro 是 Ruby Meta-programming 非常重要的一个应用,例如在 Rails ActiveRecord 中:

class User <ActiveRecord::Base

validates_presence_of :login

validates_length_of :login,:within =>3..40

validates_presence_of :email

belongs_to :group

has_many :posts

end

学好ruby语法和rgss函数库,学习脚本的第一步是熟悉现有的脚本,了解它们都是用来干嘛的。然后是到网上搜更多有用的脚本,拿一本笔记本记下你所认为的重要的代码,标记它们的用途。

第三步是进阶,能够设计最简洁、最有效率的语句来解决现有脚本不能解决的问题。从零到高手需要这样学习1到2年。P.S每个版本所需的脚本库是不同的互相不兼容,所以在学之前先确定你所学的脚本类型。

Ruby

武器:Crescent Rose(新月玫瑰)

新月玫瑰平时通过折叠的方式收纳在Ruby的腰带背后,隐藏在斗篷里;在战斗时可以变化成枪或者镰刀两种形态,几乎比Ruby本人还高一点。在枪形态中,Ruby使用常规魔法弹,并可以通过操作在很短时间内切换到镰刀形态。

收纳形态下的新月玫瑰

镰刀形态

镰刀形态可以更细分为两种模式。

在第一种模式中,镰刀刀刃折回,整体呈现传统镰刀的“__\”形,且依然使用常规子弹。在这一模式中,Ruby巧妙的利用子弹击出时产生的后坐力和镰刀挥动的惯性快速的移动,并利用子弹和镰刀的挥斩制造伤害。同时,Ruby自身灵活性和近战基础也能使她在使用长柄武器的同时不在肉搏中失利;当敌人人数增加时,Ruby可以通过将刀尖插入冰中并向前奔跑的方式将镰刀的刀刃打开,使镰刀切换到“__ ∕”形的第二种模式。配合子弹更换为“重力”尘晶子弹所获得的更为强劲的后坐力,Ruby可以飞速的在空中移动,并施展“杀戮风暴”一般的技巧、通过展开的刀刃在敌方中高速旋转来斩落敌人。相比与常规子弹,她的后续运动更快,输出也更强大。处于第二种模式的镰刀在使用完毕后,可以通过将刀尖插入冰中并向内推动很方便的变回第一种模式。

步枪形态

新月玫瑰是一把威力强悍的武器,在任何距离都可以一击必杀且没有伤害衰减,只要击中目标的头部,躯干,手臂和大腿就足以致命。作为.50BMG(12.7 毫米)口径的狙击步枪,穿透力相当高,可以穿透较厚的物体,不过却无法击穿高硬度的戮兽外骨骼。作为一把栓动型狙击步枪,武器射速是非常低的,在预告片里的射速是80RPM,不过到了正片里被削弱到只有50RPM。该武器储备弹药量很少,仅仅八发子弹,后座力相当高,但是每次射击完毕以后狙击镜的十字准线会自动回复到初始位置,独特的后座力补偿器加装在镰刀上以发动威力更强的近战攻击。但是装填时间很长,需要4.57秒,在可以重新射击以前有将近5秒的时间毫无防备。瞄准时间是450毫秒,移动速度为基本速度的95%。

Weiss  武器:Myrtenaster(柳叶白菀)

柳叶白菀是一把多重尘晶突刺剑(Multi Action Dust Rapier,MADR),剑体呈银灰色。

剑刃的形状是四棱锥,四面分别开有血槽。和其他的刺剑相同,Myrtenaster有着华丽的柄部。柄部上端中心镂空并替以类似于左轮手枪中转轮弹仓的构造,握把处加装了左轮枪的击锤和扳机。弹舱中填埋六枚尘晶(Dust),分别对应了不同的魔法效果。而这把剑相当于一个魔导器。主要攻击方式是穿刺对手,也可用于挥砍等,近战距离伤害非常高,命中目标则一击致命,但是距离有限。穿透性能不佳,尤其是对于重甲单位(预告里的铁骑士)。此外,柳叶白菀在充能的时候会发出尖锐的声响。

柳叶白菀的弹舱中装备了黄、蓝、红、绿、紫、白六种不同颜色的尘晶,分别对应六种不同的魔法效果,如下:

火——红色尘晶:从地面上释放出火焰,攻击指定目标。

冰——蓝色尘晶:Weiss最常使用的一种尘晶,有多种使用方式:

①冰晶:在地面上制造出一堵冰墙,作为掩体或障碍物。

②冰域:在地面上制造一大片较厚的冰域,使对手滑倒。

③结合雕文:Weiss能够结合其雕文,随心所欲地塑造冰晶的形状,用于不同用途。在vol.2第4章,Weiss与Ruby Rose(鲁比·洛斯)发动“Iceflower(冰花)”组合技,将冰晶与新月玫瑰的子弹融合射击AP-290机甲,将机甲的足部和腿部冻住;在vol.2第11章,Weiss在列车上制造出一个“寒冰莲花”护罩,将自己和队友保护住,使得列车撞开隧道门时己方不受伤害 ;vol.2第12章,Weiss将冰晶塑造成一柄巨剑的形状,用以斩杀戮兽。

时间——黄色尘晶:在地面上生成一个巨大的黄色时钟雕文,可以将自身周围的时间放缓或加快,制造有利时机。

柳叶白菀和尘晶风——白色尘晶:制造出一股旋风攻击对手。

Blake  武器:Gambol Shroud(跃影飞绫)

跃影飞绫(Gambol Shroud)是布蕾克的武器,是一把以阔刃短刀为刀鞘,内装可变形打刀的复合型武器——变型弹道链镰刀(Variant Ballistic Chaain Scythe,VBCS),且带有枪击功能,适用于迂回至敌后,隐匿偷袭时使用。

刀鞘本身可以视作一把阔刃短刀,矩形。平时吸附在布蕾克背后的白色磁石上。未被抽出时握住内装打刀的刀柄进行攻击,抽出内装打刀后可扣住后端镂空孔洞进行攻击。刀鞘内部装有一把打刀,打刀可以近战攻击一击必杀,其范围可延伸至上半身。刀柄的末端延伸出一条橡胶带,可以近乎无限地拉伸延长并且十分柔韧。

打刀的刀镡下方有一个扳机,扣动后可使用刀镡上的枪口进行枪击。枪口外有一折叠盖,开火时会自动打开。枪镰的柄部可装填冲锋手枪专用的33发加长弹夹,由于其相当高的射速,33发子弹会很快耗尽,因此装填弹夹会比较频繁,但是跃影飞绫重新装填时间是RWBY系列已知所有武器里最快的,只要1.5秒就可以完成;也可替换为尘晶(Dust)能量夹,届时加持过的跃影飞绫的威力会大为增强,可结合布蕾克的外像力“残影”释放多种新技能。瞄准时间也相当快,只要100毫秒,几乎是一瞬间完成,而且使用者可保持100%基本移动速度。

在一定情况下,打刀还可以变形成为Glock-18C冲锋手枪和“Kyoketsu-shoge(距跋渉毛)”枪镰的结合体进行使用。打刀完成变形后,布蕾克可以通过甩出枪镰的方式进行攻击,会在空气中留下紫色划痕。

枪镰能在空中回旋并割伤敌人,枪镰刀在空中的运动轨迹由布蕾克原本绑在打刀末端的橡胶带控制。同时枪镰在橡胶带绷直到极限并回弹时会触动扳机并进行枪击。布蕾克利用枪击产生的后坐力加速枪镰的运动、使用刀刃击穿敌人、并且可以任意改变枪镰运动方向。枪击亦可以在布蕾克的枪镰插入敌人身体后脱离用,避免枪镰卡死。最后,未变形情况下也可进行枪击。枪击时,枪口会发出紫色闪光,近距离伤害较低,基本上对于目标是三发致命,射程较短,穿透力较弱;射速非常高,全自动模式射击可以到1200RPM,Hip-Fire(盲射)性能优良,后座力相对中等。

Yang  武器:Ember Celica(灰烬天堂)

灰烬天堂(Ember Celica)是Yang的武器,一对双重远程猎枪臂铠(Dual Ranged Shot Gauntlets,DRSG),平时以护腕形态收在Yang手腕上。在需要时,臂铠可在短时间内完成变形并覆盖Yang的左右前臂。臂铠具有枪击功能,单发弹药容量为12发子弹,总体弹药容量是24发。子弹有黄色和红色两种类型,其中红色子弹有明显的远程射击弹道轨迹。由于是泵动式射击武器的原因,重新装填时间较长,填充每一发子弹需要80毫秒,12发则需要长达9.6秒,双持则需要19.2秒。

灰烬天堂的运作方式是泵动式,所以射速相对较低,只有85RPM,不过由于灰烬天堂是默认双持的武器,射速可以翻倍至170RPM,因此近距离伤害非常高,几乎是一击致命,而且被击中目标会被击倒在地。另外由于是默认双持武器,使用者不需要瞄准目标射击,只需要200毫秒准备时间,盲射性能优良,使用者可保持100%移动速度;后座力相对适中,不过每一发射击后枪械后座力会向四周散布,另外后座力会传到动能至拳套上,方便使用者挥舞重拳。