{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 6.1 オブジェクトとクラス定義\n", "\n", "Rubyに存在する値は、すべてオブジェクトである。オブジェクトは、**状態(フィールド)**と**振る舞い(メソッド)**を持つ。オブジェクトを作るには、オブジェクトの型を定義するために**class**(クラス)と呼ばれる雛形を記述する必要がある。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "classの雛形は、以下のような形式をしている。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```ruby\n", "class クラス名\n", " def initialize(引数)\n", " # @フィールド = 引数\n", " end\n", " \n", " def foo_method\n", " # @フィールドを使った処理\n", " end\n", " ...\n", "end\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "classの定義は、class + クラス名のキーワードから、endキーワードまでのブロックが範囲である。定義範囲には、0個以上の関数を定義することができる。このclass定義に定義される関数のことがメソッドである。メソッドには、いくつか特殊なメソッドが存在する。そのうちの一つが、**initializeメソッド**(別名コンストラクタ)と呼ばれるメソッドである。inializeメソッドは、クラス(雛形)からオブジェクト(インスタンス)が生成(インスタンス化)されるときに呼ばれるメソッドで、主にフィールドを初期化するために使われる。また、すべてのメソッドでは、@ (アットマーク)が先頭に付いた変数を共有で使用することが出来る。この変数のことをフィールドと呼ぶ。次に簡単なクラス定義の例を示す。" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":greet" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Hello\n", " def initialize(name)\n", " # 同名であっても、@nameとnameは別物。\n", " @name = name\n", " end\n", " \n", " def greet()\n", " # コンストラクタで初期化された@name(フィールド)が、このメソッドでも呼び出すことができる。\n", " \"Hello, #{@name}\"\n", " end\n", "end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "定義されたクラスは、newメソッドを呼び出すことで、オブジェクトを生成することが出来る。生成されたオブジェクトは、他のオブジェクトと同じように変数に代入したり、代入しないままメソッドを続けざまに呼び出すことも出来る。" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hello = Hello.new(\"OOP\")" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"Hello, OOP\"" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hello.greet" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"Hello, hello\"" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Hello.new(\"hello\").greet" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### コラム: 全ての値はオブジェクト\n", "\n", "チャプター冒頭で、Rubyの存在する全ての値がオブジェクトであると述べた。それらを確かめる為に、一般的な値と演算子がオブジェクトとメソッド呼び出しにより実現出来ることを以下に示す。" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "1 + 1" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "1.+(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "同様のことをpushした値を2倍にする*DoubleArray*クラスを自分で定義してみる。このクラスは、値を配列の末尾に格納する*push*メソッドの他に、同じ意味を持つ*<<*メソッドを持つ。これは、整数型の演算子(+)と同様に、*push*の意味を持つ演算として定義している。Rubyでは、**クラス自体もオブジェクト**である。そのため、自身のメソッドを呼び出すには、自身を表す値(オブジェクト)**self**を使い、メソッドを呼び出す。但し、普段は省略可能である。Rubyが普段トップレベル関数(クラス定義されていないメソッド)が記述出来ているように見える(手続き型のように見える)のは、トップレベルオブジェクトのメソッドとして定義し、かつ、selfが省略されているからである。メソッドを呼び出す対象のオブジェクト(ドットの左側)のことを**レシーバ**と呼ぶ。" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":<<" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class DoubleArray\n", " def initialize\n", " @array = []\n", " end\n", " \n", " def push(val)\n", " @array.push(val * 2)\n", " end\n", " \n", " def <<(val)\n", " self.push(val)\n", " # push(val) でも可 (レシーバの省略)\n", " end\n", "end" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dblArr = DoubleArray.new()" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[2]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dblArr.push(1)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[2, 4]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dblArr << 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## なぜオブジェクト指向か?\n", "\n", "オブジェクト指向を導入する理由は、コードを責任分割し、管理・再利用・拡張が行い易くするためである。具体的にはどういうことだろうか。オブジェクト指向を使用しない例と使用した例を比較してみよう。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "人の名前を表したハッシュを定義し、イニシャルを表示する関数*initial*を定義しよう。以下のように書けるはずだ。" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{:first=>\"Barack\", :family=>\"Obama\"}" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "name1 = {first: 'Barack', family: 'Obama'}" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "{:first=>\"George\", :middle=>\"W\", :family=>\"Bush\"}" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "name2 = {first: 'George', middle: 'W', family: 'Bush'}" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":initial" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def initial(name)\n", " if(name[:middle].nil?)\n", " \"#{name[:first][0]}.#{name[:family][0]}.\"\n", " else\n", " \"#{name[:first][0]}.#{name[:middle][0]}.#{name[:family][0]}.\" \n", " end\n", "end" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"B.O.\"" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "initial(name1)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"G.W.B.\"" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "initial(name2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "このときに、ミドルネームを判断する条件式が直感的ではないと思い、ミドルネームがあるかどうかを判断する関数*middle?*に切り出したとする。" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":middle?" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def middle?(name)\n", " name[:middle].nil? == false\n", "end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "すると、initialが以下のように再定義出来るはずだ。" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":initial" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def initial(name)\n", " if(middle?(name))\n", " \"#{name[:first][0]}.#{name[:middle][0]}.#{name[:family][0]}.\"\n", " else\n", " \"#{name[:first][0]}.#{name[:family][0]}.\"\n", " end\n", "end" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"B.O.\"" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "initial(name1)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"G.W.B.\"" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "initial(name2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "以上が名前を定義するハッシュと、それに関する関数の組み合わせである。それでは、この場合の問題点はなんだろうか。大きな問題は、このハッシュと関数を使うユーザは、関数がどのように使われるか関数の仕様を知らなければならない点だ。*initial*は、関数名からイニシャルを返す関数と判断出来るかもしれない。しかし*middle?*は、ミドルネームが存在することを判断する関数か、ベクトルの中間点を返す関数なのか、別な意味を持つ関数なのか判断することが出来ない。Rubyは動的型付けであるため、関数に適切な値が渡されたかどうかは、実行時に判断するしかないため。以下のようなエラーが起こることが想定される。" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "ename": "NoMethodError", "evalue": "undefined method `[]' for nil:NilClass", "output_type": "error", "traceback": [ "\u001b[31mNoMethodError\u001b[0m: undefined method `[]' for nil:NilClass", "\u001b[37m
:4:in `initial'\u001b[0m", "\u001b[37m
:in `
'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/backend.rb:8:in `eval'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/backend.rb:8:in `eval'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/kernel.rb:104:in `execute_request'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/kernel.rb:64:in `run'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/command.rb:76:in `run_kernel'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/command.rb:33:in `run'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/bin/iruby:5:in `'\u001b[0m", "\u001b[37m/usr/local/bundle/bin/iruby:23:in `load'\u001b[0m", "\u001b[37m/usr/local/bundle/bin/iruby:23:in `
'\u001b[0m" ] } ], "source": [ "initial({hoge: \"foo\", bar: \"test\"})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "さらにこのエラーが恐ろしいところは、エラー内容から何が間違いであったか想定できないことだ。この場合、この関数を仕様するユーザは、関数の中身を見なければならない。他にも、このハッシュが年齢、身長、体重などの要素が付き、それに関する関数が増えた時に、この問題の複雑度を爆発的に増すことになるだろう。また、nameという対象となるハッシュの参照回数が増えて煩雑なコードになっていると言う小さな問題もある。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "オブジェクト指向は以下のような問題を解決するのに役立つ。先程までの問題をオブジェクト指向を用いて書き直してみる。コンストラクタでは、ファーストネーム、ミドルネーム、ファミリーネームのフィールドを初期化している。ミドルネームはデフォルト引数を用いることで省略することが出来る。*initial*や*middle?*メソッドでは、初期化されたフィールドを参照することで、コードが煩雑にならずに済んでいる。つまり、フィールドやメソッドはクラス*Name*に閉じ込められていると考えることが出来る。メソッドは、クラス*Name*に紐付いているため、引数を渡す必要が無く(フィールドだけで、処理が完結する場合に限る)、中身の処理を気にすることなく呼び出すことが可能になる。" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":middle?" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Name\n", " def initialize(first, middle=nil, family)\n", " @first = first\n", " @middle = middle\n", " @family = family\n", " end\n", " \n", " def initial()\n", " if(middle?)\n", " \"#{@first[0]}.#{@middle[0]}.#{@family[0]}.\"\n", " else\n", " \"#{@first[0]}.#{@family[0]}.\"\n", " end\n", " end\n", "\n", " def middle?()\n", " @middle.nil? == false\n", " end\n", "end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "クラス名が付くことにより、コードに意味が付き見やすくなり、" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "name1 = Name.new('Barack', 'Obama')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "メソッドは、オブジェクトと紐付いているため、おかしな値を渡しエラーになることもなくなる。" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"B.O.\"" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "name1.initial" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "name2 = Name.new('George', 'W', 'Bush')" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"G.W.B.\"" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "name2.initial" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "オブジェクトは、オブジェクトを持つことが可能で、役割はクラスごとに分割されているため、複雑になりにくい。以下の例では、クラス*Human*が、Nameオブジェクトと、Fixnumのageを持っている(*HAS-Aの関係*)。" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#, @age=55>" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Human\n", " def initialize(name, age)\n", " @name = name\n", " @age = age\n", " end\n", " \n", " def initial\n", " @name.initial\n", " end\n", " \n", " def middle?\n", " @name.middle?\n", " end\n", " \n", " def greater(target)\n", " @age > target\n", " end\n", "end\n", "\n", "Human.new(Name.new('Barack', 'Obama'), 55)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "このように、Humanは、Nameオブジェクトとage(整数)によってインスタンス化されると、自然に読むことが出来る。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 演習" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- *to_s* というフルネーム(文字列)を返すメソッドを作り、またインスタンス化時に、どのようになるか確認せよ。\n", "- 以下に示す配列を使い、60歳より大きく、ミドルネームを持つイニシャルの配列を生成せよ。" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#, @age=55>" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human1 = Human.new(Name.new('Barack', 'Obama'), 55) " ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#, @age=70>" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human2 = Human.new(Name.new('George', 'W', 'Bush'), 70)" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#, @age=70>" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human3 = Human.new(Name.new('Donald', 'Trump'), 70)" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[#, @age=55>, #, @age=70>, #, @age=70>]" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[human1, human2, human3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## getter, setter, accessor\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "前節ではオブジェクト指向の利点を示すために、*Human*と*Name*クラスの例を挙げた。Humanクラスのいくつかのメソッドは、Nameクラスのメソッドをただ呼び出すだけの無駄な形になってしまう。このような場合は、*@name*フィールドを参照するための、いわゆる*getter*と呼ばれるメソッドを定義する。getterを定義し、Humanクラスを再定義する。" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":greater" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Human\n", " def initialize(name, age)\n", " @name = name\n", " @age = age\n", " end\n", " \n", " def name # @nameフィールドを参照するための、getterメソッド \n", " @name\n", " end\n", " \n", " def greater(target)\n", " @age > target\n", " end\n", "end" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#, @age=55>" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human = Human.new(Name.new('Barack', 'Obama'), 55) " ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"B.O.\"" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human.name.initial # nameメソッドを参照し、initialメソッドを呼び出す。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "これによりHumanに、Nameのメソッドを再定義する必要が無く、スッキリとした定義を保つことが出来る。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "さらにインスタンスの状態を参照するだけでなく、状態が書き換わることを想定しよう。その場合は、状態の書き換えをするための*setter*メソッド定義する必要がある。仮に*@age*フィールドが書き換え可能な形でHumanクラスを定義する。" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":greater" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Human\n", " def initialize(name, age)\n", " @name = name\n", " @age = age\n", " end\n", " \n", " def name # @nameフィールドを参照するための、getterメソッド \n", " @name\n", " end\n", " \n", " def age=(a) # @ageフィールドを書き換えるための、setterメソッド。\n", " @age = a\n", " end\n", " \n", " def greater(target)\n", " @age > target\n", " end\n", "end" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#, @age=55>" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human = Human.new(Name.new('Barack', 'Obama'), 55) " ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "56" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human.age = 56 # 誕生日!" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human.greater(55) # 55より上なのでtrueを返す。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "getter, setterの定義方法を説明したが、この2つのメソッドは、良く定義されるためRubyの*accessor*と呼ばれる機能から自動的に定義することが可能である。accessorは、以下の3つの定義方法が可能である。\n", "\n", "- attr_accessor\n", "\n", "フィールドに対して、getterとsetterを自動定義する。\n", "\n", "- attr_reader \n", " \n", "フィールドに対して、getterを自動定義する。\n", "\n", "- attr_writer \n", "\n", "フィールドに対して、setterを自動定義する。\n", "\n", "全てのフィールドに対して、attr_accessorを使用し、getter, setterを定義すれば良いと思うかもしれないが、不用意にgetter, setterを定義してしまうのは危険である。オブジェクト指向では、なるべくインスタンスの状態や不必要なメソッドへのアクセスを避け(隠蔽)、状態を意識すること無く、不用意な値の書き換えによるバグを避ける。このような考え方を*カプセル化*と呼ぶ。クラス定義では、そのとき必要最低限のaccessorのみを定義することで、不必要必要なアクセスを避けカプセル化を守っている。accessorを用いて、Humanの定義を再定義すると以下のようになる。" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":greater" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Human\n", " attr_reader :name # フィールド名のシンボルで指定する。 @name getter\n", " attr_writer :age # @age setter\n", " def initialize(name, age)\n", " @name = name\n", " @age = age\n", " end\n", " \n", " def greater(target)\n", " @age > target\n", " end\n", "end" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#, @age=55>" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human = Human.new(Name.new('Barack', 'Obama'), 55)" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "false" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human.name.middle?" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "57" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "human.age = 57" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "collapsed": false }, "outputs": [ { "ename": "NoMethodError", "evalue": "undefined method `age' for #", "output_type": "error", "traceback": [ "\u001b[31mNoMethodError\u001b[0m: undefined method `age' for #", "\u001b[37m
:in `
'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/backend.rb:8:in `eval'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/backend.rb:8:in `eval'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/kernel.rb:104:in `execute_request'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/kernel.rb:64:in `run'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/command.rb:76:in `run_kernel'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/command.rb:33:in `run'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/bin/iruby:5:in `'\u001b[0m", "\u001b[37m/usr/local/bundle/bin/iruby:23:in `load'\u001b[0m", "\u001b[37m/usr/local/bundle/bin/iruby:23:in `
'\u001b[0m" ] } ], "source": [ "human.age # ageは、attr_writer なので、getterは用意されない。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## privateメソッドによるカプセル化\n", "\n", "前節では、カプセル化を「インスタンスの状態や不必要なメソッドへのアクセスを避け(隠蔽)、状態を意識すること無く、不用意な値の書き換えによるバグを避ける。」と説明した。*accessor*を用いて、**外部からフィールドを守る事のみをカプセル化と呼ぶには不十分である**。\n", "\n", "要素が増える毎に格納する値が指数関数的に増加する配列クラス*ExponentialArray*を定義してみる。*<<*演算子では、メソッドが呼び出されるタイミングで、indexである*@count*フィールドが増加することと、追加される値valのべき乗を計算するとことと、配列本体(*@array*)に挿入する3つの操作がある。そのうち最初の2つの操作を抽象的に表すために、*countUp*メソッドと*power*メソッドとして切り分けている。一見すると、読みやすいコードになり、問題は無いかのように見える。しかし、実際には、*countUp*操作が*push(<<)*操作と分離してしまっているため、オブジェクト外部から呼び出された場合、indexが増加されてしまい想定した結果と配列の値がことなってしまう。また、べき乗の値を計算方法を不必要に外部に公開し、ユーザに混乱を与えてしまうことも想定される。" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":power" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class ExponentialArray\n", " def initialize\n", " @array = []\n", " @count = 1\n", " end\n", " \n", " def <<(val)\n", " countUp\n", " @array.push(power(val))\n", " end\n", " \n", " def countUp\n", " @count += 1\n", " end\n", " \n", " def power(val)\n", " val ** @count\n", " end\n", "end" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "#" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expArray = ExponentialArray.new" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[4]" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expArray << 2" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[4, 8]" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expArray << 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "想定していないindexの増加。" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expArray.countUp" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expArray.countUp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "結果、想定した値と実際の値がズレてしまう。" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[4, 8, 64]" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expArray << 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "べき乗の計算方法を公開しても、ユーザに混乱を与えるだけとなってしまう。" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "15625" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expArray.power(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "そこで、メソッドをprivate(非公開)にすることで、上のような事態を避けることができる。" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":_power" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class ExponentialArray\n", " def initialize\n", " @array = []\n", " @count = 1\n", " end\n", " \n", " def <<(val)\n", " _countUp\n", " @array.push(_power(val))\n", " end\n", " \n", " # 以下は、privateメソッド\n", " private\n", " \n", " def _countUp\n", " @count += 1\n", " end\n", " \n", " def _power(val)\n", " val ** @count\n", " end\n", " \n", " # private :_countUp, :_power でも可。\n", "end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*private*メソッドには、クラス外部からはアクセス出来ない。" ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "collapsed": false }, "outputs": [ { "ename": "NoMethodError", "evalue": "private method `_countUp' called for #", "output_type": "error", "traceback": [ "\u001b[31mNoMethodError\u001b[0m: private method `_countUp' called for #", "\u001b[37m
:in `
'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/backend.rb:8:in `eval'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/backend.rb:8:in `eval'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/kernel.rb:104:in `execute_request'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/kernel.rb:64:in `run'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/command.rb:76:in `run_kernel'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/command.rb:33:in `run'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/bin/iruby:5:in `'\u001b[0m", "\u001b[37m/usr/local/bundle/bin/iruby:23:in `load'\u001b[0m", "\u001b[37m/usr/local/bundle/bin/iruby:23:in `
'\u001b[0m" ] } ], "source": [ "ExponentialArray.new._countUp" ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "collapsed": false }, "outputs": [ { "ename": "NoMethodError", "evalue": "private method `_power' called for #", "output_type": "error", "traceback": [ "\u001b[31mNoMethodError\u001b[0m: private method `_power' called for #", "\u001b[37m
:in `
'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/backend.rb:8:in `eval'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/backend.rb:8:in `eval'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/kernel.rb:104:in `execute_request'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/kernel.rb:64:in `run'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/command.rb:76:in `run_kernel'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/lib/iruby/command.rb:33:in `run'\u001b[0m", "\u001b[37m/usr/local/bundle/gems/iruby-0.2.1/bin/iruby:5:in `'\u001b[0m", "\u001b[37m/usr/local/bundle/bin/iruby:23:in `load'\u001b[0m", "\u001b[37m/usr/local/bundle/bin/iruby:23:in `
'\u001b[0m" ] } ], "source": [ "ExponentialArray.new._power" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "オブジェクト指向は、ある仕組みをオブジェクトに集約し、名前を付け意味を与え、ユーザにとって有益なインターフェースのみを提供するプログラミング手法だと言える。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 演習\n", "\n", "以下に示す関数をColorクラスにより再定義せよ。" ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":rgb2hex" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def rgb2hex(rgb)\n", " rgb.map { |e| sprintf(\"%02x\", e) }.join # sprintf -> \"%002x\" % e に書き換え可\n", "end" ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ ":hex2rgb" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def hex2rgb(hex)\n", " r, g, b = hex[0..1], hex[2..3], hex[4..5]\n", " [r, g, b].map { |e| e.to_i(16) }\n", "end" ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\"6419fe\"" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rgb2hex([100, 25, 254]) # RGB値(Red Green Blue) を16進数表示(HTMLカラー)へ" ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[100, 25, 254]" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hex2rgb(\"6419fe\") # HTMLカラーをRGB値へ" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```ruby\n", "color = Color.new(100, 25, 254) # RGB値をコンストラクタに渡す。\n", "puts color\n", "=> 6419fe#(100, 25, 254)\n", "p color.rgb\n", "=> [100, 25, 254]\n", "p color.hex\n", "=> \"6419fe\"\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "また、Colorインスタンスと*id*属性を持つHTML要素を表すクラス*Header*を定義せよ。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```ruby\n", "header = Header.new(\"header\", Color.new(100, 25, 254), \"id1\")\n", "p header.html\n", "=> \"

header

\"\n", "p header.style\n", "=> \"header#id1 {color: #6419fe;}\"\n", "p header.color.rgb\n", "[100, 25, 254]\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## チェックリスト" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- オブジェクト指向を利用する利点は何か\n", "- カプセル化とは何か" ] } ], "metadata": { "kernelspec": { "display_name": "Ruby", "language": "ruby", "name": "ruby" }, "language_info": { "file_extension": "rb", "mimetype": "text/ruby", "name": "ruby", "version": "2.2.2" } }, "nbformat": 4, "nbformat_minor": 0 }