例として、TeleVision という、単純なクラスを考えてみます。
TeleVision クラスは channel という一つのメンバ変数を持っています。
もう一つ、volume というメンバ変数も持たせたいところですが、自分で理解しやすいようにするため、一つのメンバ変数で。
channelUp, channelDown でチャンネルをかえます。(ダイアル式のチャンネルの比喩)
セッターでもチャンネルをかえられます。(デジタル式のチャンネルの比喩)
>Ruby におけるクラスの定義例
#!/usr/bin/ruby
class TeleVision
def initialize(settingChannel = 1) #インスタンス変数の定義及び初期化。
@channel = settingChannel #new に渡した引数をそのまま new から initialize が呼ばれる。
end
def channelUp
@channel = @channel + 1
if(@channel > 12)
@channel = 1
end
end
def channelDown
@channel = @channel - 1
if(@channel < 1)
@channel = 12
end
end
attr_accessor :channel
#インスタンス変数と同じ名前を持つ channel 及び、channel= というメソッドを定義。
#以下の二つのメソッド定義を上の一行でやってくれる。
#
#def channel()
# return @channel
#end
#
#def channel=(settingChannel)
# @channel = settingChannel
#end
end
Ruby ではメンバ変数は、インスタンス変数として定義する。
変数の頭に @ をつけるとインスタンス変数と定義される。
Ruby ではインスタンス変数として定義されたメンバ変数にアクセスするには、メソッドを経由する以外に
方法がない。
Java でいうと、常に private で宣言される。
だからなのか、attr_accessor :channel とか書くと、インスタンス変数に触れるためのアクセサを簡単に書ける。
>使用例
#!/usr/bin/ruby
require "television"
myRoomTV = TeleVision.new(3)
print "I am displaying a program on ", myRoomTV.channel, " channel\n"
myRoomTV.channel = 12
print "I am displaying a program on ", myRoomTV.channel, " channel\n"
myRoomTV.channelUp
print "I am displaying a program on ", myRoomTV.channel, " channel\n"
>実行例
$> ./main.rb
I am displaying a program on 3 channel
I am displaying a program on 12 channel
I am displaying a program on 1 channel
$>
>Perl におけるクラスの定義例
#!/usr/bin/perl
package TeleVision;
use strict;
use warnings;
sub new{
my $thisPackage = shift; #インヴォカントというしくみによって第一引数に入っている、パッケージ名 TeleVision をゲット。
my $hashReferenceOfTeleVision = {
channel => shift #メンバ変数名を無名ハッシュのキー値、メンバ変数の値をそのキー値に
#対応する値として定義するというのを知ったとき、少し違和感がありました。
};
bless $hashReferenceOfTeleVision, $thisPackage;
}
# 理解しやすいように、省略をなくして、明示的に書いてみる。
# sub new{
# my ($thisPackage, $settingChannel) = @_;
#
# my $hashReferenceOfTeleVision = {
# channel => $settingChannel
# };
#
# bless $hashReferenceOfTeleVision, $thisPackage;
# return $hashReferenceOfTeleVision;
# }
sub channelUp{
my $self = shift;
$self->{channel}++;
if($self->{channel} > 12){
$self->{channel} = 1;
};
}
sub channelDown{
my $self = shift;
$self->{channel}--;
if($self->{channel} < 1){
$self->{channel} = 12;
};
}
sub setChannel{
my $self = shift;
$self->{channel} = shift;
}
sub getChannel{
my $self = shift;
return $self->{channel};
}
1;
見よう見まねで同じような構成のクラスを自分なりに Perl で作ってみる。
bless されたハッシュリファレンスがオブジェクトの正体というのが Perl におけるクラスの肝なのでしょうか。
また、コメントに書いたけれど、Ruby や、Java では普通に変数として定義するメンバ変数名をハッシュのキー値として
定義するというのが、ちょっと違和感がありました。
インヴォカントは要するに、メンバ関数の中で、オブジェクトとしてのハッシュリファレンスを利用するに決まってるから、第一引数がオブジェクトというデフォルトになっているという理解でよいのかな。。
例えば毎回、$myRoomTV->setChannel($myRoomTV, 3) とかもしやるとしたら見た目にも悪いし、面倒だからデフォルトになっているという理解でよいのかな。
ちなみに、インヴォカント invocant は、invoke + ant で invoke するものという意味のようです。
bless とか、invocant とか、Perl は命名に遊び心があっていいです。
>使用例
#!/usr/bin/perl
use TeleVision;
$myRoomTV = TeleVision->new(3);
print "I am displaying a program on ", $myRoomTV->getChannel, " channel\n";
$myRoomTV->setChannel(12);
print "I am displaying a program on ", $myRoomTV->getChannel, " channel\n";
$myRoomTV->channelUp();
print "I am displaying a program on ", $myRoomTV->getChannel, " channel\n";
>実行例
$> ./main.pl
I am displaying a program on 3 channel
I am displaying a program on 12 channel
I am displaying a program on 1 channel
$>
>理解まとめ
・Perl におけるクラスのメンバ変数は、無名ハッシュのキー値が名前、value 値が値、という感じで実現する。
・Perl におけるオブジェクトの正体は、パッケージ名と bless された、ハッシュリファレンス。
・その bless されたリファレンスから、パッケージの中にあるメンバ関数にアクセスできる。
・Ruby でのコンストラクタ new の内部で処理を行っていると思われることを、Perl では自分で組み上げている。
>おもったこと
Perl におけるクラスのコンストラクタの中に存在する bless は、メモリ上に用意されたオブジェクトに、perl 内部で目印をつけるようなことをやっているようだとはなんとなくわかる。
ここからは想像だけれど、
引数に、クラス定義自身のリファレンスと、生成されたオブジェクトの二つを渡していることから想像するに、
生成されたオブジェクトが配置されたメモリ上の先頭から、クラス構造のなかのメンバ関数へのオフセットをつけるような処理をしているきがする。
bless されたパッケージ構造に従って、オブジェクトの先頭 $myRoomTV から何バイト目がメンバ関数の channelUp みたいな。
だから $myRoomTV->channelUpで、関数を呼べるのだとおもう。だって、オブジェクト(ただのハッシュのリファレンス)の $myRoomTV が channelUp という関数をもっているということを、コンパイラが認識できないから。
逆に言うと、bless をするのは、オブジェクトが channelUp という関数をもっているとコンパイラが認識できるようにするためってことだとは思う。
多分、Ruby でのそれは、new の中で(組み込み関数だから、見えないところで)bless とおなじようなことをやった上で、initialize に移るのだと思う。
あと、ふと思ったけど、TeleVision->new(3)とかやるように、TeleVision->channelUp とかやったら(メンバ関数にオブジェクトじゃなくて、パッケージ名を渡したら。)どうなるんだろう。。宿題だな。
ともかく、Perl においては、オブジェクト指向を実現するために、リファレンスを駆使して自分でひとつひとつ組み立てあげているというイメージだけは何となくしることができたかもしれない。