• 2012-07-14

    一段Ruby代码的思考

    Tag:ruby rails dci

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://www.blogbus.com/dreamhead-logs/219662282.html

    在一个Rails项目中,我们遇到了这样一个问题。

    这是一个新闻(News)权限的处理。如果是一个新建的新闻,缺省的情况下,管理员和普通用户都是可见的,创建者可以根据需要设置权限;如果是一个已创建的新闻,则权限是创建时设定的。我们要在页面上显示一个form,供人编辑,按照通常的处理方法,页面上应该有所有角色,根据这个新闻当前的权限设置选择框。

    从代码上讲,我们可能希望这样判断:

      news.checked_for? role

    问题来了,这样的代码写着很漂亮。但是,现实的问题是,这是一段与View相关的逻辑,我们要把它放到Model里吗?如果把这些代码都放到Model里,可想而知,我们就会得到一个非常庞大的Model,事实上,很多项目里就是有这样一个庞大的Model。

    对于庞大的东西,第一直觉是拆。感谢Ruby,它给了我们强大的mixin,只要把不同方面的东西写入到不同的module里面,似乎问题就迎刃而解了。

    moudle NewsForNewOrEdit
      def checked_for? role
        ...
      end
    end

    class News ...
      include NewsForNewOrEdit
      ...
    end

    稍微仔细想一下,似乎还有继续优化的空间。我们的checked_for?其实只在这种情况下才起作用,而按照上面的做法,所有使用News的地方都可以使用这个方法,貌似它的作用域增大了。

    一般来说,我们是希望代码的作用域越小越好,这样,才不会牵一发而动全身。

    有了想法,便不难产生做法。在Ruby里面,我们可以给对象extend一个module,这样,只有在需要的时候,才这么做,避免了方法作用域的扩大:

    @news = News.new.extend(NewsForNewOrEdit)
    或是
    @news = News.find(param[:id]).extend(NewsForNewOrEdit)

    从功能实现和设计的角度,这样的代码是没有什么问题的。从Clean Code的角度看,这样的代码略显啰嗦,表达性有所欠缺,不能很好地反映我们的意图,这对于Ruby代码来说,有些暴殄天物。稍加封装,这段代码就会不一样。下面给出一种实现:

    def with_new_or_edit_context
      yield.extend(NewsForNewOrEdit)
    end

    基于这个实现,上面的代码可以实现成这样:

    @news = with_new_or_edit_news_context { News.new }
    或是
    @news = with_new_or_edit_news_context { News.find(param[:id]) }

    当然,这样的代码写多了也是一种负担,所以,我们可以将其进一步泛化,这是后话了。

    其实,这种问题已经有人做了很多的探索,从架构设计的层面上进行新的思考,引入一种新的架构模式:DCI(Data、Context and Interation),这里给出的是在Ruby上的初步探索。更多的讨论参见《The DCI Architecture: A New Vision of Object-Oriented Programming》。

    分享到:

    历史上的今天:

    人来人往 2011-07-14
    文化的差异 2005-07-14
    引用地址: