なひです。

> From: "Yukihiro Matsumoto" <matz / ruby-lang.org>
> Sent: Monday, October 13, 2003 8:32 PM

> |ちなみにJavaでは、dumperが、クラス階層内の各クラスの
> |writeObjectメソッドをそれぞれ呼んでくれるので(2種類あるserialize
> |インタフェイスの手軽なほう)、各クラスが、自分で定義した
> |インスタンス変数(とは呼ばないけど)を書き出すことで、救えます。
> |writeObjectメソッドが定義されてない場合、デフォルトで全ての
> |インスタンス変数(同)が書き出されます。
> 
> えーと、writeObjectメソッドはサブクラスでoverrideされていよ
> うがなんだろうが個別に呼ばれるんですか。

はい。writeObjectとreadObjectはそのように使われます(後述の
readResolveは異なります)。

ちなみに

> |writeObjectメソッドが定義されてない場合、デフォルトで全ての
> |インスタンス変数(同)が書き出されます。

と書いてしまいましたが、正しくは「〜定義されてない場合、デフォルトで
そのクラスで定義されている全てのインスタンス変数が〜」でした。

> Rubyではあんまりそう
> いうのはやってないんで考えてませんでしたけど、それが良いと言
> うなら検討の余地はあります。

夏にまつもとさんに直接お話した時には、Rubyではそのように呼び分けるのは
できないのかななんて思っていたのですが、最近、SOAPのmarshallingコードに
関連して、そのような方法を中田さんに教えてもらったのでした。
UnboundMethodなんて初めて使った。。。

良いことかどうかは、なひにもちょっとよくわからなくなってきています。
債務分割という点ではよさげなんですが、基本的にmarshal_dumpペアを
定義したクラスを継承する、なんてレアなケースだと思いますし、
複雑さを考えると避けるべき設計だと思います。ただ、それでもどうしても
継承したくなった場合、子が親のシリアライズフォーマットまで規定しないと
いけないというのは厳しい。。親クラスは他人が書いていて、いつ何時
インスタンス変数が増えたり、シリアライズフォーマットが変更されるかも。。

というわけでよくわかりません。苦労している方のコメントをお待ちします。

ちなみに、債務を分割する場合、各クラスにおいて、そのクラスで導入した
インスタンス変数が取り出せると便利です。例えばmarshal_dumpペアが
定義されていないクラス階層では、rubyが、デフォルトで、そのクラスで
導入されたインスタンス変数のみを書き出してあげるとか。

  class Foo
    def initialize
      @foo = STDERR
    end

    def marshal_dump
      # @fooは書き出さない
    end
  end

  class Bar < Foo
    def initialize
      super
      @bar = 123
    end
  end

  class Baz < Bar
    def initialize
      super
      @baz = STDOUT
    end

    def marshal_dump
      # @bazは書き出さない
    end
  end

  Marshal.load(Marshal.dump(Baz.new))  # @barのみ保存されて欲しい

無理でしょうか。

> |Rubyでは、インスタンス変数がどのクラスで定義されているか、
> |あまり意識してない人が多いような気がします(?)。よって咳さんの
> |書かれてるように、Javaみたいにクラス階層をたどることで債務分割を
> |するのでなく、一番下位のmarshal_dump/marshal_loadを定義するヤツが
> |気をつけて全部やれ、とするのもアリだと思います。
> 
> 現時点ではそうなってますね。

はい。

> |関連してひとつ、別の提案ですが、Javaでは、とあるメソッド(readResolve)を
> |定義しておくと、loadの後に呼んでくれて、そいつが返したオブジェクトを元の
> |オブジェクトとすりかえる、という機能があります。singletonやenumな
> |クラスで、loaderが生成した新規オブジェクトを、singletonオブジェクトと
> |すりかえられます。
> 
> すいません。Javaについてあまり知識が無いので、「loadの後に呼
> んでくれて」、「元のオブジェクトとすりかえる」という記述が具
> 体的にはイメージできませんでした。もうちょっと説明していただ
> けませんか?

えーと、readResolveはJDK/1.2から追加されたインタフェイスで、
つまり1.1までのフォーマットと実装しかよく知らないなひは
弱いのですが(言い訳)、

  class Foo implements Serializable {
    private static final Foo singleton = new Foo();

    public static final Foo getInstance() { return singleton; }

    private Foo() {}

    private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException {
      in.defaultReadObject();
    }

    private Object readResolve() {
      return singleton;
    }
  }

こんなことをしておくと、Fooのserialized formatを読み込む際、
readObjectで新たなFooインスタンスが生成されたあと、そいつの
readResolveメソッドが呼ばれ、返ってきたsingletonが、読み込む
オブジェクトグラフ中に代わりに埋め込まれます。readObjectで
作られた新たなFooインスタンスはすぐに捨てられます。
readResolveが呼ばれるタイミングは、serialize formatを読み込んで
いく最中、readObjectを呼んでオブジェクトの構築が終わった直後です。
http://java.sun.com/j2se/1.3/docs/guide/serialization/spec/input.doc6.html

という話ではなく、もしかして実装方法でしょうか。
(現時点では)なひも実装は見てません。^^;

readResolveについては、クラス階層を順に呼ぶのではなく、通常の
メソッド呼び出し相当で1つだけ呼ばれます。該当クラスで定義されて
いればそれが呼ばれ、該当クラスで定義されていない場合は、
親クラスでprivateとして定義されていれば呼ばれず、
protectedなら呼ばれます。