<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>dogstar (dogstarhuang)</title>
    <link>https://ruby-china.org/dogstar</link>
    <description/>
    <language>en-us</language>
    <item>
      <title>[译] (上) 高级元编程指南：创建一个 Ruby DSL</title>
      <description>&lt;p&gt;&lt;u&gt;原文请见&lt;a href="https://www.toptal.com/ruby/ruby-dsl-metaprogramming-guide" rel="nofollow" target="_blank" title=""&gt;Creating a Ruby DSL: A Guide to Advanced Metaprogramming&lt;/a&gt;。&lt;/u&gt;  &lt;/p&gt;

&lt;p&gt;领域特定语言（DSL）是一个强大到令人难以置信的工具，因为它把编写或者配置复杂的系统变得更为简单。同时它们无处不在——作为一名软件工程师，你很可能在日常事务中使用了多种不同的 DSL。 &lt;/p&gt;

&lt;p&gt;&lt;img src="http://7xiz2f.com1.z0.glb.clouddn.com/20170730224347_4cf995b546f79c00c11a52854a35d448" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;在这篇文章，你将会学习到什么是领域特定语言，什么时候应该使用它们，以及如何使用 Ruby 的高级元编程技术创建你自己专属的 DSL。  &lt;/p&gt;

&lt;p&gt;此文章基于 Nikola Todorovic 发布在 Toptal 博客上关于 Ruby 元编程的&lt;a href="https://www.toptal.com/ruby/ruby-metaprogramming-cooler-than-it-sounds" rel="nofollow" target="_blank" title=""&gt;介绍&lt;/a&gt;，如果你是初次接触元编程，请先阅读一下那篇文章。  &lt;/p&gt;
&lt;h2 id="什么是领域特定语言？"&gt;什么是领域特定语言？&lt;/h2&gt;
&lt;p&gt;对于 DSL 的普遍定义是，它们是某一特定应用域或用例的专门语言。这意味着，你只能在指定的方面使用 DSL——它们不适合普遍目的的软件开发。如果这听起来有点虚，是因为——DSL 本来就千奇百怪。以下是一些重要的分类：  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;标记性语言，例如 HTML 和 CSS 设计用于描述类似结构、内容、网站页面风格这些指定的事物。标记性语言不能用于编写任何算法，所以它们适合 DSL 的描述。  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;在某个特定系统或者另一门编程语言之上的微型和查询语言（例如 SQL），并且通常受限于他们能做什么。所以它们明显被界定为领域特定语言。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;很多 DSL 没有自己的语法——相反，它们通过一种机智的方式使用了某一公认编程语言的语法，这感觉就像正在使用的是另一门迷你语言。 &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后这种分类称为内部 DSL，它也是我们即将作为示例而创建的 DSL 之一。但在开始之前，来看一下一些内部 DSL 著名的例子。在 Rails 中的路由定义就是其中一个：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"pages#main"&lt;/span&gt;

  &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="ss"&gt;:preview&lt;/span&gt;

    &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:destroy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得益于各种元编程技术，才造就了如此干净、易于使用的接口。这是 Ruby 代码，然而它感觉更像是一种客户端路由定义语言。注意到，此 DSL 的结构是通过 Ruby 的块来实现的，而诸如&lt;code&gt;get&lt;/code&gt;和&lt;code&gt;resources&lt;/code&gt;这要的方法调用则用于定义迷你语言的关键字。  &lt;/p&gt;

&lt;p&gt;元编程在 RSpec 测试类库中用得更为疯狂：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;UsersController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :controller&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:current_user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"GET #new"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns success"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_success&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这块代码还包含了&lt;u&gt;流式接口&lt;/u&gt;的例子，即声明可以像简明英语句子那样大声朗读出来，这样可以更容易理解代码正在做什么：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 对controller上的current_user方法进打桩，使其总是返回nil&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:current_user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 断言subject.success?为真&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_success&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;流式接口的另一个例子是 Arel ActiveRecord 的查询接口，对于构建复杂的 SQL 查询，它使用了&lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree" rel="nofollow" target="_blank" title=""&gt;抽象语法树&lt;/a&gt;：  &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Post.                               # =&amp;gt;
  select([                          # SELECT
    Post[Arel.star],                #   `posts`.*,
    Comment[:id].count.             #     COUNT(`comments`.`id`)
      as("num_comments"),           #       AS num_comments
  ]).                               # FROM `posts`
  joins(:comments).                 # INNER JOIN `comments`
                                    #   ON `comments`.`post_id` = `posts`.`id`
  where.not(status: :draft).        # WHERE `posts`.`status` &amp;lt;&amp;gt; 'draft'
  where(                            # AND
    Post[:created_at].lte(Time.now) #   `posts`.`created_at` &amp;lt;=
  ).                                #     '2017-07-01 14:52:30'
  group(Post[:id])                  # GROUP BY `posts`.`id`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尽管干净、富有表现力的语法以及天生的元编程能力使得 Ruby 非常适合于构建领域特定语言的，但是 DSL 也存在于其他语言中。以下是使用 Jasmine 框架进行 JavaScript 测试的一个例子：  &lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Helper functions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;helpers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;log error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logs error message to console&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;oops!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERROR: oops!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的语法可能没有 Ruby 例子那样干净，但这表明了通过命名和对语法的创造性使用，几乎可以使用任何语言构建内部 DSL。  &lt;/p&gt;

&lt;p&gt;&lt;a href="http://javieracero.com/blog/internal-vs-external-dsl" rel="nofollow" target="_blank" title=""&gt;内部 DSL&lt;/a&gt;的好处是不需要单独的解析器，要想正确实现解析器是众所周知的困难。此外，由于使用的是宿主语言的语法，还可以和代码库的其他部分实现无缝集成。  &lt;/p&gt;

&lt;p&gt;作为代价，我们需要放弃的是语法自由——内部 DSL 必须要符合宿主语言的语法。你要做出的妥协，更大程度上依赖于所选择的语言，在谱的一端是带有短语、静态类型的语言，诸如 Java 和 VB.NET，而另一端则是带有可扩展元编程能力的动态语言，例如 Ruby。  &lt;/p&gt;
&lt;h2 id="为类配置构建一个自己的Ruby DSL"&gt;为类配置构建一个自己的 Ruby DSL&lt;/h2&gt;
&lt;p&gt;我们准备用 Ruby 构建的示例是一个可重用的配置引擎，通过非常简单的语法即可指定一个 Ruby 类的配置属性。在 Ruby 世界中，为一个类添加配置的能力是很常见的需求，尤其是需要配置额外的 gem 和 API 客户端时。通常的解决方法是一个类似这样的接口：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my_app"&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"My App"&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cookie_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my_app_session"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们先来简单实现它——然后把它作为一个起点，通过添加更多特性、精简语法以及让成果可重用，进行逐步完善。  &lt;/p&gt;

&lt;p&gt;为了让这个接口可工作，我们需要做什么呢？&lt;code&gt;MyApp&lt;/code&gt;类应该要有一个&lt;code&gt;configure&lt;/code&gt;类方法，它接收一个 block 代码块然后通过 yield 来执行，传入的是一个拥有读、写配置值的访问器方法的配置对象：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;
      &lt;span class="vi"&gt;@config&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;configure&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;
    &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:app_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:cookie_name&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一旦运行了配置代码块，就能轻易访问和修改这些值： &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;MyApp::Configuration:0x2c6c5e0 @app_id="my_app", @title="My App", @cookie_name="my_app_session"&amp;gt;&lt;/span&gt;

&lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"My App"&lt;/span&gt;

&lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"not_my_app"&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"not_my_app"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到目前为止，此实现并不像是一种定制的语言而被看待是一个 DSL。但别急，一步步来。接下来，我们会把配置功能从&lt;code&gt;MyApp&lt;/code&gt;类中解耦出来，让它足够通用以便能在很多不同的场景下重用。  &lt;/p&gt;
&lt;h3 id="让它变得可重用"&gt;让它变得可重用&lt;/h3&gt;
&lt;p&gt;现在，如果想添加类似的配置能力到另一个不同的类，需要同时复制&lt;code&gt;Configuration&lt;/code&gt;类和其相关的初始化方法到那一个类，还要编辑&lt;code&gt;attr_accessor&lt;/code&gt;列表以便修改可接收的配置属性。为避免这样做，可先把配置特性移到另外一个叫做&lt;code&gt;Configurable&lt;/code&gt;的模块。这样的话，我们的&lt;code&gt;MyApp&lt;/code&gt;类看起来像是：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Configurable&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;任何与配置相关的都被移到了&lt;code&gt;Configurable&lt;/code&gt;的模块：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Configurable&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;included&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host_class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;host_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ClassMethods&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ClassMethods&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;
      &lt;span class="vi"&gt;@config&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;configure&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;
    &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:app_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:cookie_name&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里要改动的并不多，除了新的&lt;code&gt;self.included&lt;/code&gt;方法。我们需要这个方法是因为引入一个模块只会混入它的实例方法，所以默认情况下&lt;code&gt;config&lt;/code&gt;和&lt;code&gt;configure&lt;/code&gt;类方法不会被添加到宿主类。然而，如果在模块里定义了一个叫做&lt;code&gt;included&lt;/code&gt;的特别方法，Ruby 将会在类一引入模块时就调用此方法。这样我们就可以手动地让宿主类继承&lt;code&gt;ClassMethods&lt;/code&gt;的方法：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;included&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host_class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# 在MyApp类引入此模块时被调用&lt;/span&gt;
  &lt;span class="n"&gt;host_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ClassMethods&lt;/span&gt;  &lt;span class="c1"&gt;# 把我们的类方法添加到MyApp类&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到这里还没结束——下一步是让它能够指定引入&lt;code&gt;Configurable&lt;/code&gt;模块的宿主类支持哪些属性。一个不错的解决方案是：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Configurable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:app_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:cookie_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也许某种程度上会让人觉得奇怪，但实际上以上代码在语法上是正确的——&lt;code&gt;include&lt;/code&gt;不是一个关键字，而是一个简单的普通方法，它期待的参数是一个&lt;code&gt;Module&lt;/code&gt;对象。只要我们传给它的表达式返回的是一个&lt;code&gt;Module&lt;/code&gt;，Ruby 就会很乐意地引入它。所以，取代直接引入&lt;code&gt;Configurable&lt;/code&gt;的方式，我们需要一个名字叫做&lt;code&gt;with&lt;/code&gt;的方法来生成一个新的、可通过指定属性定制化的模块：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Configurable&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 使用配置属性定义匿名类&lt;/span&gt;
    &lt;span class="n"&gt;config_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# 为可混入的类方法定义匿名模块&lt;/span&gt;
    &lt;span class="n"&gt;class_methods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;define_method&lt;/span&gt; &lt;span class="ss"&gt;:config&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="vi"&gt;@config&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;config_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;configure&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# 创建并返回新的模块&lt;/span&gt;
    &lt;span class="no"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;singleton_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt; &lt;span class="ss"&gt;:define_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;host_class&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;host_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt; &lt;span class="n"&gt;class_methods&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的内容有点多。整个&lt;code&gt;Configurable&lt;/code&gt;模块现在只是包含了一个&lt;code&gt;with&lt;/code&gt;方法，并在这个方法内完成全部的事情。首先，通过&lt;code&gt;Class.new&lt;/code&gt;创建了一个新的匿名类来接收属性访问器方法。因为&lt;code&gt;Class.new&lt;/code&gt;将类的定义作为一个代码块来接收，而代码块又能访问外部变量，所以把&lt;code&gt;attr_accessor&lt;/code&gt;传递给&lt;code&gt;attrs&lt;/code&gt;变量是没有问题的。  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# attrs在这创建&lt;/span&gt;
  &lt;span class="c1"&gt;# ... &lt;/span&gt;
  &lt;span class="n"&gt;config_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;   &lt;span class="c1"&gt;# 类定义作为代码块传入&lt;/span&gt;
    &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;        &lt;span class="c1"&gt;# 在这可以访问到attrs &lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ruby 的代码块能够访问外部变量这一事实，也是为什么有时它们被叫做闭包的原因，因为它们包含了，或者说“沉浸在”所定义时的外部环境。注意，我使用的短语是“所定义时的”而非“所执行时的”。下面说法是对的——不管&lt;code&gt;define_method&lt;/code&gt;代码块最终何时、何地被执行，它们都能访问到变量&lt;code&gt;config_class&lt;/code&gt;和&lt;code&gt;class_methods&lt;/code&gt;，哪怕是在&lt;code&gt;with&lt;/code&gt;方法执行完毕并返回结果之后。以下示例演示了这一行为：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_block&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;
  &lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt;            &lt;span class="c1"&gt;# 定义局部变量&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;Proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;# 返回一个返回值为foo的新代码块&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_block&lt;/span&gt;       &lt;span class="c1"&gt;# 调用create_block接收代码块&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;                 &lt;span class="c1"&gt;# 尽管create_block已经返回&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt;                 &lt;span class="c1"&gt;#   代码块依然返回了foo给我们&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;既然我们已经知道代码块这一巧妙的行为，那么可以继续前进，在&lt;code&gt;class_methods&lt;/code&gt;里为引入生成的模块时将会添加到宿主类的类方法定义一个匿名模块。这里不得不使用&lt;code&gt;define_method&lt;/code&gt;来定义&lt;code&gt;config&lt;/code&gt;方法，因为需要访问来自于方法内的外部的&lt;code&gt;config_class&lt;/code&gt;变量。使用关键字&lt;code&gt;def&lt;/code&gt;来定义此方法将会没有访问权限，因为用&lt;code&gt;def&lt;/code&gt;定义的普通方法并不是闭包——然而，&lt;code&gt;define_method&lt;/code&gt;接收一个代码块，所以可以这么干：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;# ...               # 在这定义config_class&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;class_methods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;      &lt;span class="c1"&gt;# 使用代码块定义新模块&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;  &lt;span class="n"&gt;define_method&lt;/span&gt; &lt;span class="ss"&gt;:config&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;         &lt;span class="c1"&gt;# 通过代码块定义方法&lt;/span&gt;
    &lt;span class="vi"&gt;@config&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;config_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;   &lt;span class="c1"&gt;# 即使有两层代码，我们依然可以can still&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt;  &lt;span class="k"&gt;end&lt;/span&gt;                              &lt;span class="c1"&gt;#   访问config_class&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，调用&lt;code&gt;Module.new&lt;/code&gt;创建待返回的模块。这里需要定义我们的&lt;code&gt;self.included&lt;/code&gt;方法，不幸的是依然不能使用&lt;code&gt;def&lt;/code&gt;关键字，原因你懂的，因为此方法需要访问&lt;code&gt;class_methods&lt;/code&gt;以外的变量。所以再一次需要通过代码块来使用&lt;code&gt;define_method&lt;/code&gt;，但这一次在模块的单例类上，相当于在模块实例本身定义了一个方法。注意有坑！因为&lt;code&gt;define_method&lt;/code&gt;是单例类的私有方法，需要用&lt;code&gt;send&lt;/code&gt;来调度而不是直接调用：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;class_methods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;# ... &lt;/span&gt;
&lt;span class="c1"&gt;# ... &lt;/span&gt;
&lt;span class="no"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;
  &lt;span class="n"&gt;singleton_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt; &lt;span class="ss"&gt;:define_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;host_class&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;
    &lt;span class="n"&gt;host_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt; &lt;span class="n"&gt;class_methods&lt;/span&gt;  &lt;span class="c1"&gt;# 此代码块可以访问class_methods&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;恩……，这些硬编码的元编程已经算得是漂亮的了。但所添加的复杂性是否值得呢？来看一下好不好用，你再来决定：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeClass&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Configurable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:bar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;SomeClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"wat"&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"huh"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;SomeClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"wat"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但我们可以做到更好。下一步，将稍微精简配置代码块的语法，让使用更简便。  &lt;/p&gt;

&lt;p&gt;&lt;u&gt;译者注：下一篇章，继续阅读，请访问&lt;a href="http://www.itran.cc/2017/08/02/xia-gao-ji-yuan-bian-cheng-zhi-nan-chuang-jian-yi-ge-ruby-dsl/" rel="nofollow" target="_blank" title=""&gt;（下）高级元编程指南：创建一个 Ruby DSL&lt;/a&gt;。&lt;/u&gt;&lt;/p&gt;</description>
      <author>dogstar</author>
      <pubDate>Wed, 02 Aug 2017 23:46:33 +0800</pubDate>
      <link>https://ruby-china.org/topics/33712</link>
      <guid>https://ruby-china.org/topics/33712</guid>
    </item>
  </channel>
</rss>
