

以下はArrayを継承したクラスのインスタンスに対してArrayのインスタンスメソッドを使用するサンプルコードです。

[code]

    class MyArray < Array
    end
    
    m = MyArray.new
    n = MyArray.new
    added = m + n
    
    puts m.class #MyArray
    puts n.class #MyArray
    puts added.class #Array
    
[/code]

ここでは`+`を使用しています。  
上記のように、`added`はArrayクラスのインスタンスとなっています。

この時に、サブクラスのインスタンスを得たいと考えています。何か方法はありますか？

* * *

追記

皆さま丁寧なご回答ありがとうございます。勉強になりました。  
既存のメソッドを上書きし、戻り値がsuper classだった場合はsubclassのインスタンスを生成し、戻り値とする実装としました。  
sendを使用する必要があり、不恰好ではありますが、gemにしてみました。

<https://github.com/mmmmmavo/subper_class/blob/master/spec/subper_class_spec.rb>


CC BY-SA 4.0
2018-09-17T12:17:44.400
48486
2018-09-21T17:58:46.050
2018-09-21T17:58:46.050
30143
30143
question
ruby
ArrayのサブクラスでArrayのインスタンスメソッドを使用するときの問題


@metropolis さんも仰っていますが、 `Array#+` のメソッドをオーバーライドする必要があると思います。

元々の `Array#+` メソッドは、以下の処理を実行しています。

  1. Array のインスタンスを作成
  2. 自身の要素すべてを 1 の `Array` インスタンスに対して追加
  3. 引数配列の要素すべての 1 の `Array` インスタンスの末尾に追加

これを、 MyArray 用の処理にすると、次のようになります。

  1. MyArray のインスタンスを作成
  2. 自身の要素すべてを 1 の `MyArray` インスタンスに対して追加
  3. 引数配列の要素すべての 1 の `MyArray` インスタンスの末尾に追加

コードにすると次です。(初期化配列を利用することで、 1,2 を同時に行っている)

[code]

    class MyArray < Array
      def +(x)
        MyArray.new(self).concat(x)
      end
    end
    
[/code]


CC BY-SA 4.0
2018-09-18T04:23:10.327
48495
2018-09-18T04:23:10.327
754
48486
answer


`Array#+`は`Array`を返しますので、親クラスの`+`を呼び出せば`Array`のままです。これを変更するには`+`メソッドを上書き(オーバーライド)するしかありませ(Rubyでは演算子もただのメソッドであるため、「演算子のオーバーロード」という表現は正しくありません)。ただ、問題は`+`だけではなく`-`や`&`等も同じことが言えます。それを踏まえて、上書きする方法を考えました。

[code]

    class MyArray < Array
      def +(_)
        MyArray.new(super)
      end
      def -(_)
        MyArray.new(super)
      end
      def &(_)
        MyArray.new(super)
      end
      def |(_)
        MyArray.new(super)
      end
    end
    
[/code]

`super`とだけ書いた場合は同じ引数で親のメソッドをそのまま呼び出します。戻り値は`Array`になっていますので、それを単純に`MyArray.new()`することで、`MyArray`のインスタンスになります。

さて、同じコードが4つも出てきました。[DRY](https://ja.wikipedia.org/wiki/Don%27t_repeat_yourself)が大好きな私達にはこのコードは似合いません。ということでまとめてみましょう。

[code]

    class MyArray < Array
      def self.new_creation_method(*methods)
        methods.each do |method|
          define_method(method) do |*args, &block|
            MyArray.new(super(*args, &block))
          end
        end
      end
      new_creation_method :+, :-, :&, :|, :uniq, :sort
    end
    
[/code]

`define_method`はインスタンスメソッドを作成する`Module`のメソッドです。Rubyではこのような方法で動的にインスタンスメソッドを作成できます。詳しくは[Module#define_method](https://docs.ruby-
lang.org/ja/latest/method/Module/i/define_method.html)を見てください。こちらの`super`は引数を省略することは出来ません。

上のコードのように、`+`や`-`だけではなく`uniq`のような新しい`Array`を返すようなメソッドが同じように定義可能で、そればかりか、`sort`のようなブロックを渡すメソッドも可能です。ただ、引数の種類や結果によって`Array`以外を返すようなメソッドには対応できませんので、それらはまた個別に対応が必要になります。


CC BY-SA 4.0
2018-09-18T13:18:31.623
48513
2018-09-18T13:18:31.623
7347
48486
answer