Multiton

December 14, 2007 – 5:35 pm

Multiton 类似于 Singleton ,目的是要保证某种单一性。但是和 Singleton 的一个区别就是:Singleton 对于一个类只可能有一个对象,而 Multiton 则可能有多个对象,仅当用于构造的参数相同时,才保证唯一性。有许多地方都有这种模式的应用,例如 Lisp 或者 Ruby 里的 symbol ,同名的 symbol interned symbol 总是同一个对象。还有 Java 里的 String 也是这样的。

最近心血来潮在用 Ruby 做一个玩具级别的 Scheme 解释器,要表示 Scheme 里的 symbol 、number 和 string 的类,为了不过于浪费空间,决定把它们都做成 Multiton 这种模式。这里其实已经有了一个 Ruby 的 Multiton 实现,不过我决定自己实现一个简单一些的。

目标是尽量不改变原有的代码就能用上这个新特性,这代表要修改构造函数。我们都知道在 Ruby 里的构造函数是 initialize

class Symbol
  attr_reader :name
  def initialize(name)
    @name = name
  end
end

不过我们并不是要修改 initialize ,因为创建对象是使用的 Class 类的 new 方法。但是这个也不是我们需要修改的,否则岂不是所有的类都成了 Multiton 了?

幸好 Ruby 里每一个对象都有自己的一个 eigenclass (又叫做 metaclass 或者 singleton_class ,不知道 Ruby 1.9 出来之后会不会正名 ;) ),我在Ruby 里的元编程一篇文章中有过介绍。只要修改 Symbol 类的 eigenclass 的 new 方法就能把 Symbol 类变成一个 Multiton 而不影响其他类了。

基本思想就是做一个 Hash ,每次调用 new 的时候检查对应的对象是不是已经有了,如果没有就用原来的 new 方法创建一个,存在 Hash 中,最后返回这个对象。因为 Symbol 、Number 和 String 几个类都要用到,所以我把它包装成了一个 module :

# Multiton module ensure only one object allocated
# for a given argument. Implementation is simplified
# for +SchemeVM+'s +Symbol+, +String+
# class.
module Multiton
  def self.included(into)
    into.eigenclass.class_eval do
      alias_method :orig_new, :new
      instances = Hash.new do |h, k|
        k = k.dup.freeze
        h[k] = into.orig_new(k).freeze
      end
      define_method :new do |arg|
        instances[arg]
      end
    end
  end
end

现在只要在 Symbol 和 String 三个类里面加上一句:

include Multiton

就可以把它们变成 Multiton 了!include 的时候,Multiton module 的 included 方法会被调用,它在此时修改 include 它的那个类(比如 Symbol)的 new 方法,实现 Multiton 。大致思路就是这样了,不过我这里的代码是专门针对我这里的几个类做的:没有考虑线程安全问题(这似乎应该看 Ruby 里 Hash 的实现了),并且 new 方法只有一个参数等等。

总之大致思路就是这样了,要做的更加健壮和更具有可扩展性的话,只需要注意一些小细节就可以了,或者也可以参考 Ara 的实现

Update: 2007-12-15

  1. 修正拼写错误,应该是 Multiton 而不是 Multition (多谢 galilette :P )。
  2. 去掉 Number ,因为 Number 并不适合作为一个 Multiton 。
  1. 7 Responses to “Multiton”

  2. 不过 Number 实在不应该做成 Multiton :D ,最好是不要为 Number 创建对象,如果真要创建对象,让它自己被 GC 掉也好过放在 Hash 里一直被引用着,毕竟和 Symbol 不一样啊。

    By pluskid on Dec 14, 2007

  3. I’m kind of curious about the name multiTION since it’s not symmetric with singleTON. and yak…it’s multiTON from that link you gave, haha.

    keep on posting about ruby.

    cheers

    By galilette on Dec 15, 2007

  4. *^_^* shamed~~~
    fixed now, thank you!

    By pluskid on Dec 15, 2007

  5. Is Multiton just another name of interned symbols/strings?

    J2SE has interned strings, Common-Lisp/Emacs Lisp has interned symbols.

    > 例如 Lisp 或者 Ruby 里的 symbol ,同名的 symbol 总是同一个对象

    This is not always correct, ref my short explanation: http://pip.jottit.com/lisp_symbols

    By goncha on Dec 15, 2007

  6. Hi goncha!
    Yes, I mean the interned symbol. And thank you for your article. That’s interesting, I didn’t know there are uninterned symbols before.

    It is useful of uninterned symbols:

    1. uninterned symbols are not equal to interned symbols, so they may be used in macros without causing name conflict. (eq (make-symbol "foo") (make-symbol "foo")) evaluate to false, so nested macros are also OK!

    2. uninterned symbols are not stored in the global obarray, so it can be garbage collected when needed. That’s ideal for those symbols temporarily created in macros — they often have short lifetime.

    Hmm.. Is there any other interesting application of uninterned symbols? :)

    By pluskid on Dec 16, 2007

  7. Lisp纯属业余爱好。等我有机会实际应用了之后,我们可以探讨一下。

    By goncha on Dec 16, 2007

  8. :)

    By pluskid on Dec 16, 2007

Post a Comment