Define the Missing

在编写程序时,我给自己设立这样的一个限制: 所有的程序都只可以编写一次,当你认为程序写完并运行后,便不能再次修改并重启了,然后,程序要尽可能对需求的扩展做出正确的回应。

场景1:

需要编写一个名为 Config 的类,通过传入 Hash 对象来实例化,传入的 Hash 中规定了两个键值对来代表程序所用的时间和空间复杂度。并且,实例可以用 "点" 的方式调用 Hash 中的值,即:

config = Config.new({time: "O(1)",  "space" => "O(N)"})
config.time # => "O(1)"
config.space # => "O(N)"

如果用静态的眼光来考虑这个问题,可以把 Config 写成这样:

  • 方案一
class Config
  attr_reader :time, :space
  def initialize(hash)
    @time = hash[:time] || hash['time']
    @space = hash[:space] || hash['space']
  end
end

这样做当然没问题,但假如这时 config 要增加一个名为 version 的属性来存储语言的版本,依照上述这种方法就得在 attr_accessor 后加上 :version, 对应的初始化方法再加上一行。如果再有更多的新属性要添加,那就要不停地重复这样的过程。需要注意的是,每次执行这个过程程序是需要被重启的,所以这种方案不符合我们的编写目标,当然,也不符合 DRY 的原则。

使用 method_missing 来实现。

  • 方案二
class Config
  attr_reader :hash_data

  def initialize(hash={})
    @hash_data = hash
  end

  def method_missing(method)
    # 可以通过正则检查方法名称是否携带 '=' 来生成 set 方法 
    # 本处只演示 get 方法
    hash_data[method.to_s] || hash_data[method]
  end
end

这段代码也达成了场景1的需求,而在属性值增长时,使用 method_missing 代码量始终可以维持不变,并且,在这一过程中,程序可以保持不重启。

相对于 method_missing 在 Ruby 的名气, const_missing 这个方法就显得默默无闻了,当然也因为使用的场景的确不多。这个方法是在当前命名空间找不到对应的常量名时会触发的hook 方法,一般来说,若没有做任何处理,解释器便会返回 uninitialized constant,如:

module Asd
  A = 1
  class C
  end
end
Asd::A # => 1
Asd::C # => Asd::C
Asd::B #= uninitialized constant Asd::B

通过覆写对应命名空间的 const_missing 方法便可以对不存在的常量进行操作,比如在文件变动时,通过 load 新文件来加载新的类(只是我这么用过)。

但 missing 方法其实不仅仅是方法,我认为也是一种理念,就是用发展的眼光来看待程序,对未发生但可能发生的事件做统一的处理,以不变应万变。

场景2 :

编写一个 HTTP 的 API,使得 '.../xx/a' 作为客户a提交的地址, '..../xx/b' 作为客户b提交的地址(假设无法规定客户提交的参数所以如此设计)。

方案一, 依然先以只解决现有问题的静态策略写出这个 API :

# Use Rack
class MyApi
  def call(env)
    req = Rack::Request.new(env)
    case req.path_info
    when '/xx/a'
      [200, {"Content-Type" => "text/html"}, ["Hello a!"]]
    when '/xx/b'
      [200, {"Content-Type" => "text/html"}, ["Hello b!"]]
    else
      [404, {"Content-Type" => "text/html"}, ["Can't find!"]]
    end
  end
end

run MyApi.new

大多 API 都会考虑这样的设计: 写好特定的路由给与调用,否则的话就返回 404。但在这个场景中,有个潜在的需求,客户(即a,b)的数量并不是不变的,可能会增加也会减少,而我们希望程序启动一次后就能适应这些改变,该怎么做呢?

不妨按照上文中的 missing 理念,在找不到路由的时候去动态的生成路由。而在这边代码中所谓的“找路由”,其实就是匹配 req.path_info 而已。我们可以在数据库存储每个客户提交的路由地址,通过每次调用得到的 path_info, 寻找对应的客户是否存在,若存在,就可以给与对应的响应。

方案二:

class MyApi
  # 数据库连接
  DB.connect! 
  def call(env)
    # 假设使用了 ActiveRecord 并建立了 Customer 的模型
    customer = Customer.find_by(path: req.path_info) 

   # 返回的内容都可以在数据库读取,这样更加灵活
    if customer
     [200, {"Content-Type" => customer.content_type}, [customer.response]]
    else
     [404, {"Content-Type" => "text/html"}, ["Can't find!"]]
    end
  end
end

run MyApi.new

这样,现在这个 API 便可以根据数据库中客户的信息‘动态的产生路由’了。顺便提及一下,使用 Grape 框架应该怎么做到这点,当然思路还是一样的,我们需要覆写捕获找不到路由的方法

# Rescue 404 Route In Grape 
route :any, '*path' do
  # do anything by req.path, database, etc..
end

结语: 对缺失的定义,可以很大程度提高程序、系统的适应能力,减少代码的数量。不仅只用在元编程中,在系统的各个环节都应引入这种思想。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,051评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,497评论 18 399
  • 最近部门有几名年轻人陆续跳槽,问其原因,他们说自己怀才不遇,能力不比别人差,凭什么别人的待遇就比自己好呢? ...
    April会飞的猪阅读 189评论 0 2
  • 我欠缺的就是这个,最近这段时间挺烦的,归根说还是自己能力不够,处事还不够好,总能把很好的想法表达出来成一种让人讨厌...
    徒步旅人阅读 255评论 0 0