Rubyのクラス、特異クラスの話。
本日も寒いですね。
突然甘くてほかほかなたい焼きが恋しくなって、四ツ谷のわかばさんに並んでしまいました。
甘さ控えめ、あずき本来の味と塩気を生かした粒あん、尾までぎっしりあんこ + 注文を受けてから一丁焼きで生誕するたい焼きたち。
Ruby Goldの対策をしながら並んでいたら、なんとかあり着くまでに並び続ける事が出来ました。笑(平日の14時ごろ並んで、20分強でした。参考までに。)
個人的にはくりこ庵のカスタードも大好きです🐟
今日はクラスとモジュールと特異クラスの話をします。
だいたいオブジェクト指向の話を知っていて、Javaなんかを触っている人は聞くまでもない話かもしれません。ふんわりオブジェクト指向についてご存知の方が今回の記事の読者の対象です。
Rubyのクラスは定数であり、再代入が可能です。
従って、クラス自体の定義を再オープンしてメソッドの追加をする事が可能です。
他の言語とは違って単一継承する仕様ですが、moduleという部品を輸入したり、クラス内に展開する事でいろんな事ができるようになります。
クラス内にはinitializeというメソッドが存在し、クラスが評価される(初期化される)ときに呼ばれます。私はJavaでいうコンストラクタのように捉えています。
コンストラクタはattr_accessorというメソッドで定義する事ができ、インスタンス変数を使って表現することもできます。
Class Taiyaki
PRICE = 150
def nakami @nakami end def nakami=(str) @nakami = str end
end
このコードはTaiyakiクラスのnakamiを設定しています。
上からnakamiを保存しておくメソッド(ゲッターと呼びます)
nakamiを設定しているメソッド(この方はセッター)です。
また名前空間という特性があり、クラスのネスト状態を定義する事が出来ます。
Class Taiyaki::ShiroiTaiyaki
PRICE = 200
end
こいつはたい焼きの亜種の白いたい焼きです。
ぱっと見Taiyakiを継承しているかのように見えますが、違います。別物です。
こいつは便宜上Taiyaki内でネストして宣言され、定数PRICEが200で初期化されている他人です。親子関係にはありません。
Class Taiyaki
PRICE = 150
def nakami @nakami end def nakami=(str) @nakami = str end
end
# あっ、中身詰めるの忘れてた。
Class Taiyaki
PRICE = 150
def nakami=(str)
@nakami = "tsubuann"
end
end
これをクラスの再オープンと言います。
これからTaiyakiのnakamiはこのプログラムが動作している上では初期値が全てtsubuannです。
何が言いたいかというと、クラスを再オープンして定義を変更した場合には、基本的にはその設定が全体に適用されます。
また、Nikumann < Manjyuのようにスーパークラスを継承している場合は、きちんと同じお名前で開いて中身を詰めてあげないとTypeErrorを起こしてしまうので気をつけましょう。
標準の中身の設定はあんこでいいけれど、クラスを操作せずにカスタード味も作ってみたいなあ…なんて思った人は、下で説明する特異クラスを使ってみてもいいかもしれませんね。
特異クラスのお話
インスタンスについてですが、これはクラスを分身させ、汎化(出生所よりもちょっとキャラを薄くした感じ)したものたちのことです。
Rubyのインスタンスは限られたスコープの中でしか生存できません。インスタンス変数しかり。宣言された箇所から~endまでが生きれる範囲です。儚い。
さて、特異クラスですが、これは指定したインスタンスに対して適用されるクラスです。
クラスメソッド、クラス変数は一切噛みません。
def object_name.method_name
end
これでこっそりと邪道の白いたい焼きカスタード味を作成するんです。 へっへっへ。
Class Taiyaki
PRICE = 150
def nakami=(str)
@nakami = "tsubuann"
end
def self.change_shape_to_white
@nakami = "custard"
p "白い#{self}、#{@nakami}味。"
end
end
taiyaki = Taiyaki.new
taiyaki.change_shape_to_white #=> 白いTaiyaki、custard味。"
無理矢理感はあるんですが、白いたい焼きができましたよっと。
あ、家族にお土産買っていきたいなあ。お母さんはあんこが苦手なので、
家族別に注文を分けておきましょー。
my_taiyaki = Taiyaki.new("zunda")
mother_taiyaki = Taiyaki.new
def mother_taiyaki.change_shape_to_white
@nakami = "custard"
p "白い#{self}、#{@nakami}味。"
end
father_taiyaki = Taiyaki.new
father_taiyaki.change_shape_to_white #=> NoMethodError
おぅ、間違ってお父さんのたい焼きを白いやつにしてしまいました。
この注文はお母さん用なので、お母さん以外を指定すると "白いたい焼き欲しがっとるお母さん存在せんやないけ" という苦情を申し立てられます。
ちょっとプログラムよりな話をすると、一時的にmother_taiyakiというインスタンス自体が特異クラス(親クラス)となっているので、change_shape_to_whiteはレシーバから本来の実行対象であるmother_taiyakiを探しに行くのですね。
しかし、father_taiyakiのsuper classはTaiyakiであるものの、Taiyakiの中にはmother_taiyakiは存在しないんです。なので、NoMethodErrorが返ってきます。
上の例で白いたい焼きの注文が成功しているのは、レシーバがselfだからであり、
クラス内で定義されているselfはクラス自身を指します。
でも…ベースのあんこにカスタードを投入したいなあ…なんていう邪道も邪道なたい焼きも下のように実現させることが可能です。
<<~Taiyakiクラスの中~>>
class << self
def half_anko_with_custard
@nakami << " half_custard"
end
end
end
<<~Taiyakiクラスの外側~>>
my_taiyaki = Taiyaki.new
p my_taiyaki.half_anko_with_custard #=> "tsubuann half_custard"
はい。これであんことカスタードの魔融合を楽しみたい他の人も注文できるようになりました。
これはさっきのように特定のインスタンスではなくて、Taiyaki自体に注文を追加しています。Taiyakiクラス内のselfはTaiyaki自身なので、これからもインスタンスとして新メニューの注文は可能です!
しかし、Taiyakiクラスの本来の中身はankoのままなので、うっかりTaiyaki.half_anko_with_custardなんてクラスからの呼出をすると、職人に怒られてしまいます。
half_anko_with_custardはTaiyaki本来の味を乱してしまわぬよう、特異クラスに追加してあるからです。
このように、クラスの外側でインスタンスに対してメソッドを作ることができてしまうのはちょっと恐ろしいですね。
Taiyaki職人のあずかり知らぬところで、ピザ味のたい焼きや、あまつさえ米の詰まったたい焼きも登場してしまうかもしれない…
それを防ぐためにもたい焼きのカテゴリー毎にレシピブックを作っておいたり(クラス設計)、instance_methodsなどのメソッドたちを活用することは重要ですね。
Taiyakiのゲシュタルト崩壊
たい焼きもいいのですが、あったかくてしょっぱいものも食べたくなってきますね〜。
おでんなんてどうでしょうか?しっかり赤だしが染み込んだ大きな大根…
お酒が進むと尚いいですよね!
渋谷の居心地もコスパも良い居酒屋さんをご紹介しまーす🍢
土日来店時予約は必須です!半個室・カウンターがあって適度に薄暗く、賑やかですよ🐶
私は居酒屋に行くとポテサラをすぐに頼むポテサラ廃人なのですが、
こちらのポテサラはフライドオニオンやサラミが細かく投入されていて、歯ごたえが楽しいやつでした!
刺し盛りが2000円弱で5点くらいいけたはずですが、遅いと売り切れてしまうので行くなら早い方がいいですよー!私はハマチが食べたかった…