maruvi
@maruvi (maruvi)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

[ruby] self.replaceの使い方の是非

Q&A

Closed

解決したいこと

rubyにて文字列を扱うクラスを作成しようと考えています。
インスタンスの値としては文字列だけです。
文字列オブジェクトを独自のクラスに変更できるのでしょうか。

下記の方法ではインスタンス変数を呼び出す必要があります。

class NewString < String
  attr_accessor :value
  def initialize(buf)
    @value = buf
  end
end
a = NewString.new('test data')
p a.value

インスタンスメソッドを記述する際、selfのように扱えるようにしたいので下記のようにしてみました。

class NewString < String
  def initialize(buf)
    self.replace buf
  end
end
a = NewString.new('test data')
p a

この方法は正しいのでしょうか。
他にいい方法等がありましたら教えてください。
よろしくお願いいたします。

0

1Answer

replace を使ってもいいですが、 super を呼んだ方がシンプルだと思います。

class NewString < String
  def initialize(*) # 0個以上の任意個の引数を受け取る
    super # 引数すべてを親クラスの initialize に渡す
  end
end

ところで、 Ruby の組み込みクラス StringArrayHash のサブクラスを作るときは注意が必要です。最適化の都合で、一部のメソッドは親クラスのインスタンスを返すからです。

p NewString.new('xxx').to_s.class # => String
p (NewString.new('xxx') + NewString.new('yyy')).class # => String
p NewString.new('xxx').split.first.class # => NewString (これは普通)

詳しくはこの記事で解説されています。

挙動をよく調べて設計するか、単に普通のクラスを作ってインスタンス変数に文字列を保持するといいと思います。

1Like

Comments

  1. @maruvi

    Questioner

    @uasiさま、ご回答ありがとうございました。
    望んでいたことを教えていただきました。
    また、注意点もいただき感謝いたします。

    既存のクラスに「メソッドを追加する」のは
    「汚しているのでは」という感覚になってしまい、
    独自のクラスにした方がいいと考えていました。

    メソッドが返すのは自身のオブジェクトが多いと思いますので、
    親のメソッドを呼んだら親のオブジェクトになることが多いのですね。
    StringならStringのまま、インスタンス変数で保持することを検討いたします。

    それにしても、superはシンプルでrubyらしく(?)、
    使ってみたいという衝動に駆られています。
  2. NewString の initialize で他にやりたい処理がなければ initialize と super を省略してしまっても構いません。

    class NewString < String
    end
    p NewString.new("xxx").class # => NewString

    > 既存のクラスに「メソッドを追加する」のは「汚しているのでは」

    そうですね。既存のクラスにメソッドを追加するとプログラムのどこからでも使えます。そうするとメソッドを変更・修正したときの影響範囲が広がって保守しづらいプログラムになってしまいます。基本的に既存クラスの拡張は避け、明らかにメリットがデメリットを上回る場合のみ慎重に検討するといいでしょう。

    もう少し扱いやすい手段としては、メソッド呼び出しを委譲できる SimpleDelagator や、あるスコープ内でだけクラスを拡張する refinements 機能があります。ご興味があれば調べてみてください。

    > 親のメソッドを呼んだら親のオブジェクトになる

    いえ、一般的には逆で、親のメソッドを子から呼ぶと子のインスタンスになるほうが多いです。 たとえば NewString.new('xxx').upcase.class は NewString です。組み込みクラスの一部のメソッド String#to_s や Array#to_a だけ、子から呼んでも親のインスタンスを返すのでややこしいのです。
  3. @maruvi

    Questioner

    なるほど!!
    initializeを省略しても実現できるのですね。
    rubyの奥深さは開発者と利用者の双方の知識の深さと感じます。

    一点、私の環境(windows7, ruby3.0)では .split.first や .upcase はStringとなりました。
    どちらにしろ、レシーバからメソッドを呼ぶので、不安定なオブジェクトは避けた方がいいと考えています。

    SimpleDelagatorとrefinements機能は少し調べてみましたが、理解できたとまではいえません。
    ただ、refinements機能には心が動かされました。
    クラスを(モジュールの一種なので)引数としてrefineメソッドに渡す。
    クラスは大元というイメージがあり、その想像のなかでクラスは大きなものでした。
    その大きなものを引数として渡してしまう。年甲斐もなくワクワクしてきます。笑

    独学なので、どのやり方が一般的かというのはつかみづらいですが、@uasiさまの誘導(?)に任せて、オブジェクトは既存のものを使い、拡張機能はrefinementsを使うという方向で考えてみようと思っています。
  4. > 一点、私の環境(windows7, ruby3.0)では .split.first や .upcase はStringとなりました。

    失礼しました。 Ruby 2.7 では NewString だったのですが Ruby 3.0 だと確かに String になりますね。

    一般的な(あるいは素直な、様々な局面で使える)書き方は、 文字列をインスタンス変数に持つクラスを作る = 文字列を受け取って処理するただの関数を作る > delegator > refinements > 既存クラスを拡張 といったところになるかと思います。他にも特異メソッドといって特定のインスタンスにだけメソッドを生やすこともできたりします。トリッキーなので使いどころは限られますが。

    s1 = 'abc'
    s2 = 'def'
    s1.define_singleton_method :add_x do
    self + 'x'
    end
    p s1.add_x # => 'abcx'
    p s2.add_x # エラー

    Ruby はクラスもオブジェクトも柔軟に拡張できるので色々なやり方を試してみると面白いと思いますよ。どれを選ぶかは忘れた頃に読み返して理解できるかどうかで判断するといいでしょう。
  5. @maruvi

    Questioner

    Ruby2.7と3.0では違いがでるのですね。私の環境でrailsを使う場合はRuby2.6となるのでサブクラスでメソッドを呼ぶときは注意が必要ですね。

    特異メソッド。インスタンスにメソッドを生やす。こんなことができるんですね。
    作ったインスタンスを使い回すことが多いので、これも面白いですね!!

    オブジェクト指向ではデータだけでなくメソッドも付随しているので、データの持ち方が重要になってくると思っています。
    yieldやprocなど全然使いきれていないのですが、これらをうまく使えるようになればデータの持ち方もスマートになるかなと勝手に予想しています。
    特異メソッドのように、代入一回でその後メソッドを使い回すなんてこともできるんですから。

    > どれを選ぶかは忘れた頃に読み返して理解できるかどうか
    なるほど!!その場のノリで決めず、時間軸も考慮すべきですね。

    @uasiさまの引き出しの広さ、深さに驚きと感謝の念でいっぱいです。
    ありがとうございます。
    本当、uasiさんはすごい人ですね。

Your answer might help someone💌