- 🍒 はじめに
- 🍒 輪読会 第10週目まとめ
- 🍒 まとめ
🍒 はじめに
チェリー本輪読会の第10週目のエントリーになります。
輪読会の概要については第1週目にまとめています。
- 第1週目のエントリーはこちら
- 第2週目のエントリーはこちら
- 第3週目のエントリーはこちら
- 第4週目のエントリーはこちら
- 第5週目のエントリーはこちら
- 第6週目のエントリーはこちら
- 第7週目のエントリーはこちら
- 第8週目のエントリーはこちら
- 第9週目のエントリーはこちら
🍒 輪読会 第10週目まとめ
第7章7.1.1〜第7章7.5.3まで
期間:2021年07月26日〜2021年07月30日
クラスを使う場合と使わない場合の比較
オブジェクト指向でなぜつくるのかに書かれていたことですが、クラスを用いる理由にも該当するため紹介したいと思います。
オブジェクト指向はソフトウエアの保守や再利用をしやすくすることを重視する技術です。個々の部品により強く着目し、部品の独立性を高め、それらを組み上げてシステム全体の機能を実現することを基本にします。部品の独立性を高めることで、修正が起きた場合の影響範囲を最小限にし、他のシステムで容易に再利用できるようにします。 出典:オブジェクト指向でなぜつくるのか 第3版 22頁
上記のオブジェクト指向を元に、まずはクラスを用いることで堅牢なプログラムの作成の基本を学びます。プログラムが大規模になるほど、データとメソッドを一緒に持ち運べるクラスのメリットは大きくなります。
クラスを使わないプログラムの例
- 配列を準備し、欲しいコンピュータのハッシュデータを入れます。
- その後、配列にしたcomputers変数のデータをeachで取り出します。
computers = [] computers << {type: 'mac', cpu: 'M1', memory: 32, storage: 'SSD256' } computers << {type: 'win', cpu: 'intel', memory: 16, storage: 'HDD50' } computers.each do |c| puts "タイプ: #{c[:type]}、CPU: #{c[:cpu]}、メモリ: #{c[:memory]}、ストレージ: #{c[:storage]}" end => タイプ: mac、CPU: M1、メモリ: 32、ストレージ: SSD256 => タイプ: win、CPU: intel、メモリ: 16、ストレージ: HDD50
クラスを使ったプログラムの例
- Computerクラスを準備します。
- コンピュータのスペック表示用でメソッドを定義します。
※ attr_reader
initialize
は後ほど取り上げます。
class Computer attr_reader :type, :cpu, :memory, :storage def initialize(type, cpu, memory, storage) @type = type @cpu = cpu @memory = memory @storage = storage end def display_pc_spec puts "タイプ: #{@type}、CPU: #{@cpu}、メモリ: #{@memory}、ストレージ: #{@storage}" end end
Computer.new
で、インスタンスを生成します。その際、引数に欲しいコンピュータのデータを渡しておきます。- 定義したメソッドを呼び出します。
mac = Computer.new(:mac, :M1, 32, :SSD256) mac.display_pc_spec #=> タイプ: mac、 CPU: M1、メモリ: 32、ストレージ: SSD256 win = Computer.new(:win, :intel, 16, :HDD500) win.display_pc_spec => タイプ: win、 CPU: intel、メモリ: 16、ストレージ: HDD500
クラスを使わない場合、使った場合ともに、結果は同じになります。
オブジェクト指向を完全に表現できているわけではないと思いますが、2つの例から、後者がよりオブジェクト指向寄りのプログラムになります。
クラスを定義することで、コンピュータのスペック内容を(属性)を保持することのできる、設計図が出来上がります。class Computer
それから、動作や振る舞いを表すメソッドを定義します。def display_pc_spec
今回は、コンピュータのスペック情報を表示できる機能を持たせました。
ここまで出来たら、後はクラスに命を吹き込みます。インスタンスを作ります。Computer.new
引数に欲しいコンピュータの情報を渡してあげることで、希望するインスタンスを生成することができます。
最後に、メソッドを呼び出しますmac.display_pc_spec
。レシーバ(先ほど生成したインスタンス)に対して、メソッドを実行することができます。mac変数(レシーバ)に対して、「スペックを表示して」という指示を出しています。
オブジェクト指向プログラミング関連の用語
クラス、インスタンス
上記で取り上げたクラスのことですが、クラスは「オブジェクトの設計図」と表現されることが多いです。
クラスはインスタンス(オブジェクト)と対になる要素です。上記の例では、コンピュータ(クラス)、Mac(インスタンス)という例で書いてみましたが、コンピュータクラスを使って、いろいろなインスタンスを生成することができます。Winのコンピュータもあれば、Macの超ハイスペックモデルもあるかもしれません。予め容易した属性に、好きなデータを入れ込んで作ることができます。
Compute.new(:好きなOS, :好きなCPU, 希望するメモリ, :希望するストレージ)
クラスは、英語でclass「分類、種類」「同種のものの集まり」を意味します。インスタンスは、英語でinstance「具体的なモノ、実例」を意味します。クラスという種類を元に、そこから具体的なモノを生成するしくみは、プログラムをより堅牢で再利用しやすくすることに繋がります。
オブジェクト、インスタンス、レシーバ
3つとも同じ意味です。
文脈によって使い分けられているようです。
インスタンスという言葉はオブジェクトとほとんど同じ意味で使われています。一方、あるオブジェクトが、あるクラスに属していることを強調する場合には、「インスタンス」のほうがよく使われます。 出典:たのしいRuby 79頁
レシーバは英語で書くと"receiver"で、「受け取る人」や「受信者」という意味です。なので、「レシーバ」は「メソッドを呼び出された側」というニュアンスを出したいときによく使われます。 出典:チェリー本 210頁
メソッド、メッセージ
オブジェクトが持つ「動作、振る舞い」をメソッドと表現します。
ここでは、メソッド名をdisplay_pc_spec
と定義し、Computerクラスから、インスタンスを生成するときに、コンピュータのスペックを表示する振る舞いを持つメソッドを書きました。
def display_pc_spec puts "タイプ: #{@type}、CPU: #{@cpu}、メモリ: #{@memory}、ストレージ: #{@storage}" end
メッセージは、レシーバと組み合わせて使われます。
変数のmac
をレシーバとし、display_pc_spec
をメッセージとして呼んでいます。「コンピュータのスペックを表示しろ」と、レシーバに対してメッセージを送っているイメージです。
mac = Computer.new(:mac, :M1, 32, :SSD256) mac.display_pc_spec
状態(ステート)
オブジェクトごとに保持される状態のことを指します。ここでは、引数で渡されたデータによってインスタンスが生成されます。生成されたインスタンスは、「macタイプで、M1のCPUを搭載した、メモリ32でストレージSSD256のコンピュータ」になります。このコンピュータの持つデータが、オブジェクト指向の考え方で言う状態(ステート)です。
mac = Computer.new(:mac, :M1, 32, :SSD256) mac.display_pc_spec => タイプ: mac、 CPU: M1、メモリ: 32、ストレージ: SSD256
属性(アトリビュート、プロパティ)
attr_reader :type, :cpu, :memory, :storage
として、オブジェクトに設定することができる値を属性と呼びます。
mac = Computer.new(:mac, :M1, 32, :SSD256)
クラス.newをする際に引数を渡していますが、これにより属性:type, :cpu, :memory, :storage
へ希望する値を指定してオブジェクトを生成することができます。
属性とは、オブジェクトに属していて、取得、設定が可能な値のことです。
class Computer attr_reader :type, :cpu, :memory, :storage # 省略 mac = Computer.new(:mac, :M1, 32, :SSD256) mac.display_pc_spec => タイプ: mac、 CPU: M1、メモリ: 32、ストレージ: SSD256
initializeメソッド
インスタンスを初期化するために使われます。クラス名.new
を実行することにより、オブジェクトが生成されますが、その際、真っ先に実行されるメソッドです。
class Computer def initialize(type, cpu, memory, storage) puts "タイプ: #{type}、CPU: #{cpu}、メモリ: #{memory}、ストレージ: #{storage}" end end Computer.new(:mac, :M1, 32, :SSD256) => タイプ: mac、CPU: M1、メモリ: 32、ストレージ: SSD256
引数の数が合わないとき、エラーになります。
実引数へ4つ与えられるべきですが、3つですよ。足りないですよ!と、怒られています。
class Computer def initialize(type, cpu, memory, storage) # 仮引数 puts "タイプ: #{type}、CPU: #{cpu}、メモリ: #{memory}、ストレージ: #{storage}" end end Computer.new(:mac, :M1, 32) # 実引数 (irb):30:in `initialize': wrong number of arguments (given 3, expected 4) (ArgumentError)
アクセサメソッド
インスタンス変数の値を読み書きするメソッドのことです。
- attr_reader 読み取り専用(ゲッターメソッド)
- attr_writer 書き込み専用(セッターメソッド)
- attr_accessor 読み書き両方に対応
Module#attr_accessor (Ruby 3.0.0 リファレンスマニュアル)
ゲッターメソッド(読み取り専用部分)
attr_readerを使わない場合のプログラム
def computer @type end
セッターメソッド(書き込み専用部分)
attr_writerを使わない場合のプログラム
computer=
メソッドは、computerとしていますが、fooでもbarでも何でも使えます。このメソッドは、@typeを外部から変更するためのメソッドです。computer=(value)
の仮引数としてvalue
としていますが、これもcomputerと同じく何でも大丈夫です。慣例的にvalueにすることが多いそうです。
Rubyは、=
で終わるメソッドを定義すると、変数に代入するような形式でそのメソッドを呼び出すことができます。個人的にはこの項目はかなり躓きました。こちらは、『ゼロからわかるRuby超入門』の198頁の説明が分かりやすくてオススメなのでぜひご一読ください。
def computer=(value) @type = value end
実際にプログラムに書き込んでみます。
読み取り、書き込み共に成功します。
class Computer def initialize(type) @type = type end def computer @type end def computer=(value) @type = value end end # インスタンスを生成する mac = Computer.new(:mac) #=> #<Computer:0x00007ffb288ff400 @type=:mac> # 読み取り mac.computer => :mac # 書き込み # 変数に代入しているように見えるが、メソッドのcomputer=(value)を呼び出している。 mac.computer = :マックブック => :マックブック
ゲッターメソッドだけ削除してみます。
読み取りはエラーが出ます。書き込みは成功します。
class Computer def initialize(type) @type = type end def computer=(value) @type = value end end win = Computer.new(:win) => #<Computer:0x00007fd9520ade48 @type=:win> # 読み取りはエラーが出る。 win.computer (irb):18:in `<main>': undefined method `computer' for #<Computer:0x00007fd9520ade48 @type=:ウィンドウズ> (NoMethodError) # 書き込みは成功。 win.computer = :ウィンドウズ => :ウィンドウズ
attr_accessor
メソッド(読み取り、書き込み)
attr_accessorメソッドを使ってプログラムを呼び出してみます。
このメソッドは、読み取り、書き込みの両方に対応しています。
class Computer attr_accessor :type def initialize(type) @type = type end end # インスタンスを生成する。 linux = Computer.new(:linux) => #<Computer:0x00007fd955a23ce0 @type=:linux> # 読み取り linux.type => :linux # 書き込み linux.type = :リナックス => :リナックス
attr_reader
メソッド 読み取り専用(別名:ゲッターメソッド)
読み取り専用にしたい時は、こちらのメソッドでも対応できます。
class Computer attr_reader :type def initialize(type) @type = type end end # インスタンスを生成する。 linux = Computer.new(:linux) => #<Computer:0x00007fd955a23ce0 @type=:linux> # 読み取り linux.type => :linux # 書き込みエラー linux.type = :リナックス (irb):10:in `<main>': undefined method `type=' for #<Computer:0x00007f8a9610b008 @type=:linux> (NoMethodError)
attr_writer
メソッド 書き込み専用(別名:セッターメソッド)
書き込み専用にしたい時は、こちらのメソッドでも対応できます。
class Computer attr_writer :type def initialize(type) @type = type end end # インスタンスを生成する。 linux = Computer.new(:linux) => #<Computer:0x00007f8a9b2133b0 @type=:linux> # 読み取りエラー linux.type (irb):9:in `<main>': undefined method `type' for #<Computer:0x00007fe0640e1108 @type=:linux> (NoMethodError) # 書き込み linux.type = :リナックス => :リナックス
クラスメソッドの定義
クラスメソッドとは、オブジェクトを作らずに呼び出せるメソッドのことです。レシーバがクラスになるので、クラスに対して呼び出せます。ひとつひとつの「インスタンスに含まれるデータは使わない」メソッドを定義したい場合もあるので、そのような場合はクラスメソッドを定義した方が良いです。
クラスメソッドを定義する方法は2つありますが、ここでは、よく使われるメソッドの前にself
を付ける手法を例に取り上げます。
class クラス名 def self.クラスメソッド # クラスメソッドの処理 end end
プログラムの例が無理やり感ありますが、インスタンスを生成しなくてもメソッドを呼び出すことができています。
class Computer def initialize(type) @type = type end def self.upgrade(type) type.upcase end end Computer.upgrade(:big_sur) => :BIG_SUR
メソッド名の表記法について
メソッドの表記についてです。るりまや、他ドキュメント等では下記のように記載されています。
- クラス名#メソッド名
#
はインスタンスメソッドであることを表しています。
- クラス名.メソッド名 または、クラス名::メソッド名
.
::
はクラスメソッドであることを表しています。
このマニュアルのヘルプ (Ruby 3.0.0 リファレンスマニュアル)
定数
一般に定数という言葉は、書き換えが不可能なことを表します。
しかし、Rubyにおける定数は、書き換えが可能です。
Rubyの定数は「みんなわざわざ変更するなよ」と念押しした変数のようなものです。定数という言葉に惑わされないようにしてください。ミュータブル(変更可能)なオブジェクトの場合、定数の中身を変更できます。(String, Array, Hashなど)
定数を使用する理由は、 マジックナンバー をなくすためというのもあります。
ハードコーディングされた値のことをマジックナンバーと呼びます(本来、別の場所に保存しておくべき値をソースコードの中に直接記述してしまうこと)
また、書き方についてですが、定数は大文字で始める必要があります。最初の一文字が大文字であればOKですが、慣習的には全部大文字で書くことが多いです。
COMPUTER = 'MacBookPro' => "MacBookPro" COMPUTER = 'WindowsMachine' warning: already initialized constant COMPUTER warning: previous definition of COMPUTER was here => "WindowsMachine"
selfキーワード
Rubyでは、インスタンスメソッドの中で、メソッドのレシーバ自身を参照するために、selfという特別な変数を使います。selfを付けても付けなくても挙動は変わりませんが、ここでのポイントは、selfがレシーバであることを理解することです。ちなみに、selfは省略して書かれることが多いとの事です。
以下、メソッドを呼び出す3パターンを用意しました。
- selfなしで、
greet_kiyoshiro
メソッドを呼び出す - self付きで、
greet_hiroto
メソッドを呼び出す - インスタンス変数
@greeting
を直接参照して、greet_yusuke
メソッドを呼び出す
class RocknrollHero attr_accessor :greeting def initialize(greeting) @greeting = greeting end def greet_kiyoshiro "#{greeting}!!、愛しあってるかい!?" end def greet_hiroto "#{self.greeting}!!、我々はクロマニヨンズだ!!" end def greet_yusuke "#{@greeting}!!、俺たちが日本のザ・ミッシェルガンエレファントだ!!" end end hero = RocknrollHero.new('ロックンロール') => #<RocknrollHero:0x00007f9cdd97df70 @greeting="ロックンロール"> hero.greet_kiyoshiro => "ロックンロール!!、愛しあってるかい!?" hero.greet_hiroto => "ロックンロール!!、我々はクロマニヨンズだ!!" hero.greet_yusuke => "ロックンロール!!、俺たちが日本のザ・ミッシェルガンエレファントだ!!
selfが省略できない場合
name=
メソッドのように、=
で終わるメソッドを呼び出す場合は、selfの省略ができません。メソッド内で、セッターメソッド(書き込み専用)を呼び出す際には注意が必要です。
class RocknrollHero attr_accessor :greeting def initialize(greeting) @greeting = greeting end def greet_other_kiyoshiro greeting = 'ハローベイベー' end def greet_other_yusuke self.greeting = 'ハローベイベー' end end hero = RocknrollHero.new('ロックンロール') => #<RocknrollHero:0x00007f9cda3e71e0 @greeting="ロックンロール"> hero.greet_other_kiyoshiro hero.greeting => "ロックンロール" # 書き込み(変更)できない hero.greet_other_yusuke hero.greeting => "ハローベイベー"
輪読会「オブジェクト指向でなぜつくるのか」へ参加
クラスの章に入るタイミングで、下記の輪読会が開催されると聞き「これは良きタイミングだ!」と思い参加してみました。(7/26に開催)
ここ最近よく耳にする「オブジェクト指向」
わかるようでわからない。そんな、奥の深いオブジェクト指向を学べる良書ということで、ワクワクした気持ちで本を読みました。週一ペースの開催になるため、まだ第2章までしか読み進めていませんが、こちらで学んだことも一部備忘録として残しておきたいと思います。
オブジェクト指向はソフトウエア開発の総合技術
「オブジェクト指向でなぜソフトウエアを作るのですか?」誰かにこんな質問をされたなら、著者はこう答えます。「その理由はソフトウエアを楽に作りたいからです。」 出典:オブジェクト指向でなぜつくるのか 第3版 21頁
冒頭の一文です。
当初、私はオブジェクト指向とは、よく見かけるたい焼きや動物などで表現されている、単にクラスやインスタンスの話しだけだと思っていました。しかし、そんな単純なことではなく、今やオブジェクト指向をカバーする範囲は広く、ソフトウエア開発の総合技術として、プログラマが「ソフトウエアを楽につくるため」に取られる開発手法全般を指していることがわかりました。
オブジェクト指向が難しい理由3つ
また、難しいと思われている理由が3つ述べられており、特に3つ目の理由にも当てはまるであろう「全てがオブジェクト」という概念。このあたりの、難しいと思っている理由を少しでも紐解き、今後の章を読み進めるなかで理解を進めていきたと思います。
参考書籍
- 伊藤淳一 著/『プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで』/技術評論社/2017年https://gihyo.jp/book/2017/978-4-7741-9397-7
- 五十嵐邦明,松岡浩平 著/『ゼロからわかる Ruby 超入門』/技術評論社/2018年https://gihyo.jp/book/2018/978-4-297-10123-7
- 高橋征義、後藤裕蔵 著/『たのしいRuby第6版』/SBクリエイティブ/2019年https://tanoshiiruby.github.io/6/index.html
- プログラミング言語 Ruby リファレンスマニュアル https://docs.ruby-lang.org/ja/
- 小餅良介 著/ 『独習Ruby on Rails』https://www.shoeisha.co.jp/book/detail/9784798160689
- 平澤章 著/『オブジェクト指向でなぜつくるのか 第3版 』https://www.nikkeibp.co.jp/atclpubmkt/book/21/S00180/
🍒 まとめ
今週からついにクラスの章に入りました!
輪読会で一番読みたかった章です。一度、通読してはいますが、クラスの章は「手強い...」という印象で、前回は完敗とまではいきませんが、負けを認めざるえない散々な結果でした...。輪読会メンバーの多くもこの章で苦労している方が多かったです。今回はそんな強敵クラスへリベンジすべく!疑問点は輪読会内で積極的に質問したり、復習にも時間をかけたりと気合を入れて取り組みました。
また、『オブジェクト指向でなぜつくるのか第3版』の輪読会も始まり、クラスの章を学ぶ上で、オブジェクト指向の考えが助けになっています。
学習を進めれば進めるほど、色々な壁が立ちふさがりますが、ひとつひとつ積み重ねて乗り越えていきたいと思います。
では、また来週!(次回、第11週目)