>> 連載の目次は こちら!
クラスの話の2回目は、クラスのメソッドと変数に対するアクセス制御について整理してみる
■ メソッドへのアクセス制御
[参考URL]
http://qiita.com/tbpgr/items/6f1c0c7b77218f74c63e
●概要
- メソッドに対して、 public / protected / private というキーワードを指定して、アクセス可能な範囲を指定する
- 指定しなければ public になるが、initializeメソッドだけはprivateになる
- public / protected / private の意味は以下
[public]
普通に、オブジェクトからインスタンスメソッドとして実行可能。
initializeメソッド以外のデフォルト。
[protected]
そのメソッドを持つオブジェクトが self であるコンテキスト(メソッド定義式や instance_eval )でのみ呼び出せる
==> そのクラスおよびサブクラスの中からしか呼び出せなくなる。
[private]
関数形式でしか呼び出せなくなる
==> レシーバを指定した呼び出し方( インスタンス.メソッド )ができなくなるため、インスタンスの外から実行できなくなる。
initializeメソッドは常にprivateになる。
●アクセス制御の指定方法
[シンボルで指定する]
class MyCls
def my_protected1; end
def my_protected2; end
def my_private1; end
def my_private2; end
protected :my_protected1, :my_protected2
private :my_private1, :my_private2
end
[ここからはprotectedです! みたいに指定する]
class MyCls
protected
def my_protected1; end
def my_protected2; end
private
def my_private1; end
def my_private2; end
end
●アクセス制御を確認してみる
class MyCls
# 確認用publicメソッド
def test
# 同一クラス内から self をレシーバとして public なメソッドを実行できる
puts 'inner MyCls: self.my_public ==> ' + self.my_public
# 同一クラス内から レシーバを省略して public なメソッドを実行できる
puts 'inner MyCls: my_public ==> ' + my_public
# 同一クラス内から self をレシーバとして protected なメソッドを実行できる
puts 'inner MyCls: self.my_protected ==> ' + self.my_protected
# 同一クラス内から レシーバを省略して protected なメソッドを実行できる
puts 'inner MyCls: my_protected ==> ' + my_protected
# クラス内で self をレシーバとして private なメソッドを実行...できない
# Uncaught exception: private method `my_private' called for 〜
self.my_private
# 同一クラス内から レシーバを指定せずに private なメソッドを実行できる
puts 'inner MyCls: my_private ==> ' + my_private
end
def my_public; __method__.to_s end
protected
def my_protected; __method__.to_s end
private
def my_private; __method__.to_s end
end
class SubClass < MyCls
# サブクラスからの確認用publicメソッド
def test
# サブクラスから self をレシーバとして 親クラスの public なメソッドを実行できる
puts 'from SubCls: self.my_public ==> ' + self.my_public
# サブクラスから レシーバを省略して 親クラスの public なメソッドを実行できる
puts 'from SubCls: my_public ==> ' + my_public
# サブクラスから self をレシーバとして 親クラスの protected なメソッドを実行できる
puts 'from SubCls: self.my_protected ==> ' + self.my_protected
# サブクラスから レシーバを省略して 親クラスの protected なメソッドを実行できる
puts 'from SubCls: my_protected ==> ' + my_protected
# クラス内で self をレシーバとして 親クラスの private なメソッドを実行...できない
# Uncaught exception: private method `my_private' called for 〜
self.my_private
# サブクラスから レシーバを指定せずに private なメソッドを実行できる
puts 'from SubCls: my_private ==> ' + my_private
end
end
mycls = MyCls.new
subcls = SubClass.new
# クラス内、サブクラス内からのアクセステスト
mycls.test; subcls.test
# クラスの外からのアクセステスト
puts mycls.my_public # 普通にできる
puts mycls.my_protected # できない
# Uncaught exception: protected method `my_protected' called for 〜
puts mycls.my_private # できない
# Uncaught exception: private method `my_private' called for 〜
puts my_private # もちろんできない(mainオブジェクトに対する定義を探しに行く)
# Uncaught exception: undefined local variable or method `my_private' for main:Object
■ 変数へのアクセス制御
前回の記事で書いたとおり、インスタンス変数、クラス変数は、メソッドを通してしか外部からアクセスできない。
●インスタンス変数へのアクセス用メソッドを定義する
[明示的にアクセス用のメソッドを書く]
class MyCls
# ゲッターに該当するメソッド
def name
@name
end
# セッターに該当するメソッド
def name=(name)
@name = name
end
end
# こんなふうに使える
cls = MyCls.new
cls.name = 'miro'
puts cls.name
[アクセサを自動定義する]
- 前述のようなお決まりのアクセサを、全部のインスタンス変数に手動で定義するのは大変だし、ソースが膨らんでしまう。
- なので、attr_xxx というキーワード(Moduleクラスのメソッド)で、自動生成することができる。
[attr_accessor] インスタンス変数を読み書きするためのアクセサメソッドを定義
[attr_reader] インスタンス変数を読み出すためのアクセサメソッドを定義
[attr_writer] インスタンス変数を書き込むためのアクセサメソッドを定義
class MyCls
attr_accessor :var1, :var2
attr_reader :var3, :var4
attr_writer :var5, :var6
def initialize(val1, val2, val3, val4, val5, val6)
@var1, @var2, @var3, @var4, @var5, @var6 = val1, val2, val3, val4, val5, val6
end
end
# 確認してみる
cls = MyCls.new(1, 2, 3, 4, 5, 6)
cls.var1 = 11; puts cls.var1 # ゲッター/セッターがあるので、値のセットと取得ができる
puts cls.var3 # ゲッターのみだから、cls.var3 = xxx とかはできない
cls.var5 = 55 # セッターのみだから、puts cls.var5 とかはできない
●クラス変数へのアクセス用メソッドを定義する
前述のアクセサの自動定義(attr_xxx)は、クラス変数に対しては使えない。
なので、クラス変数へのアクセスは、明示的にメソッドを定義する。
# クラス変数を attr_xxx に指定してもだめ、なことを確認するためのクラス
class MyTestCls
@@my_class_var = 999
attr_accessor :my_class_var # こんなことしてもだめ
def self.dump_class_var
p @@my_class_var
end
end
cls = MyTestCls.new
p cls # <MyTestCls:0x007f993d972038>
cls.my_class_var = 888 # 一見...
puts cls.my_class_var # 888 読み書きできているように見えるが...
MyTestCls.dump_class_var # 999 変わってない。
p cls # <MyTestCls:0x007f993d972038 @my_class_var=888> インスタンス変数が追加されているだけだな...
# クラス変数は明示的にメソッドを定義すればアクセスできる、ことを確認するためのクラス
class MyTestCls2
@@my_class_var = 999
def my_class_var
@@my_class_var
end
def my_class_var=(val)
@@my_class_var = val
end
def self.dump_class_var
p @@my_class_var
end
end
cls = MyTestCls2.new
p cls # <MyTestCls2:0x007fa4660c6720>
cls.my_class_var = 777 # 今度は...
puts cls.my_class_var # 777 どうかな...
MyTestCls2.dump_class_var # 777 うん、クラス変数の値が変わってる
p cls # <MyTestCls2:0x007fa4660c6720> インスタンス変数が追加されたりとかしてない