続・はじめてのPerl 11章

11章 オブジェクト入門

11.1 動物と話すことができたら
use strict;
use warnings;

sub Cow::speak
{
    print "a Cow goes moooo!\n";
}

sub Horse::speak
{
    print "a Horse goes neigh!\n";
}

sub Sheep::speak
{
    print "a Sheep goes baaah!\n";
}

my @pasture = qw/ Cow Cow Horse Sheep Sheep /;

{
    no strict 'refs';
    for my $beast ( @pasture ) {
        &{$beast . '::speak'};  # シンボリックコードレフ
    }
}
シンボリックコードレフは読みにくい
    他の方法はないのか?
11.2 メソッド呼び出しの矢印
Classとは
    同じような動作と特性を持つものの集まり

    Classパッケージ内のmethodサブルーチンを呼び出す方法
        Class->method

メソッドとは
    サブルーチンのオブジェクト指向バージョン
# メソッド呼び出しの矢印を使う
Cow->speak;
Horse->speak;
Sheep->speak;
# さらに以下のように書ける
my @pasture = qw/ Cow Cow Horse Sheep Sheep /;
for my $beast ( @pasture ) {
    # シンボリックコードレフを使う必要がなくなった
    $beast->speak;
}
OOPの原則
    共通コードを最小限に減らすこと
        コーディング量の削減による時間の節約
        テストとデバッグの効率化による時間の節約
11.3 メソッド呼び出しの特別な引数
矢印を使った、メソッド呼び出し
    Class->method( @args );

    これは、以下のようにClass::method を呼び出すこととなる
        Class:method( 'Class', @args );

        第一引数として、クラス名を受け取る
            @argsが指定されていない場合、クラス名だけを引数として受け取る
# 第一引数を使うことで、以下のようにメソッドを定義できる
use strict;
use warnings;

sub Cow::speak
{
    my $class = shift;
    print "a $class goes moooo!\n";
}

sub Horse::speak
{
    my $class = shift;
    print "a $class goes neigh!\n";
}

sub Sheep::speak
{
    my $class = shift;
    print "a $class goes baaah!\n";
}

my @pasture = qw/ Cow Cow Horse Sheep Sheep /;
for my $beast ( @pasture ) {
    $beast->speak;
}
11.4 第二のメソッドを呼び出して話を単純化する
前節のコードは、同じ構造のコードを多数含む
    どのように単純化するべきか
# use strict;

{
    # Animalクラスを作成
    #   speakメソッドを定義する
    package Animal;
    sub speak
    {
        my $class = shift;
        print "a $class goes ", $class->sound, "!\n";
    }
}

{
    package Cow;
    @ISA = qw/ Animal /;

    sub sound { 'moooo' }
}

Cow->speak;
Cow->speak; の処理の流れ
    1. 引数リストを作る
        上記の場合、Cowのみ

    2. Cow::speakを探す
        しかし、Cowにはspeakメソッドはない

    3. 継承配列の@Cow::ISAをチェックする
        @Cow::ISAは実在し、Animalという名前を持つ

    4. Animalの中に、speakメソッド(Animal::speak)があるかチェックする
        speakを見つけることができる

    5. 引数リストを使い、メソッドを呼び出す
        以下のコードを実行することと同じことになる
            Animal::speak( 'Cow' );

    6. Animal::speakメソッド内で、$classにCowが格納される

    7. $class->sound の呼び出しで、Cow->soundが呼び出される

    8. "a Cow goes moooo!" という出力が得られる
11.5 @ISAについての注意
@ISA
    「イズア」と発音
        Cow is a Animal ということを宣言する
            実際には、言語学用語

    複数の親クラスを検索することがありえるので、配列

    Animalも@ISAを持っている場合、それも検索対象
        検索は、再帰的で深さ優先
            @ISAの左の要素から右の要素へ検索が行われる

    一般に@ISA配列の要素は1つだけ
        多重継承がされている場合、複数になる

    use strictを有効にすると、@ISAについて警告される
        @ISAが、明示的なパッケージ名を含む変数でなく、レキシカル変数でもないため

    @ISAは、レキシカル変数にすることは不可能
        継承メカニズムにより見つけられるパッケージに属していなければならない

@ISAの宣言と設定方法
    最も簡単な方法:パッケージ名を書く
        @Cow::ISA = qw/ Animal /;

    Perl5.6以降では、our宣言を使うことができる
        package Cow;
        our @ISA = qw/ Animal /;

    暗黙に名前を与えたパッケージ変数として扱う
        package Cow;
        use vars qw/ @ISA /;
        @ISA = qw/ Animal /;

        これは、use base を使うことで以下のように書ける
            package Cow;
            use base qw/ Animal /;

            use baseはコンパイル時に実行される
11.6 メソッドのオーバーライド
use strict;

{
    # Animalクラスを作成
    #   speakメソッドを定義する
    package Animal;
    sub speak
    {
        my $class = shift;
        print "a $class goes ", $class->sound, "!\n";
    }
}

{
    package Mouse;
    use base qw/ Animal /;

    sub sound { 'squeak' }

    sub speak
    {
        my $class = shift;
        print "a $class goes ", $class->sound, "!\n";
        print "[but you can barely hear it!]\n";
    }
}

Mouse->speak;
Mouseは独自のspeakメソッドを持っている
    Animalクラスのspeakメソッドを呼び出すわけではない
        これをオーバーライドという

オーバーライド
    基底クラス(Animal)のメソッドの代わりとなる専用のメソッドを持っている場合に、
    派生クラス(Mouse)のメソッドを呼び出すために使われる
        しかし、上記のMouse::speakは、Animal::speakのコードと同じ箇所がある
            Animal::speakに変更が発生した場合、
            Mouse::speakにも手を加えなければならない可能性がある

OOPの原則
    コードの再利用はコピペではなく、継承で行う
# よりOOPらしいコードに
use strict;

{
    package Animal;
    sub speak
    {
        my $class = shift;
        print "a $class goes ", $class->sound, "!\n";
    }
}

{
    package Mouse;
    use base qw/ Animal /;

    sub sound { 'squeak' }

    sub speak
    {
        my $class = shift;
        Animal::speak( $class );    # 矢印によるメソッド呼び出しではない!!
        print "[but you can barely hear it!]\n";
    }
}

Mouse->speak;
矢印によるメソッド呼び出しを使わない理由
    矢印を使うと、Animal::speakの第一引数は、"Animal"になってしまう
        soundメソッドを呼び出すときに、オブジェクトに合ったメソッドが呼び出せない
            しかし、Animal::speakを直接呼び出すと、また別の問題が起こる

矢印によるメソッド呼び出しを使わなかった場合に起こる問題
    継承関係の解決が行われない
        呼び出そうとしたメソッドが継承されたメソッドだった場合、プログラムは異常終了する

よって、上記のコードの方法は正しくないやり方
11.7 異なる場所からの検索の開始
use strict;

{
    package Animal;
    sub speak
    {
        my $class = shift;
        print "a $class goes ", $class->sound, "!\n";
    }
}

{
    package Mouse;
    use base qw/ Animal /;

    sub sound { 'squeak' }

    sub speak
    {
        my $class = shift;
        $class->Animal::speak( @_ );
        print "[but you can barely hear it!]\n";
    }
}

Mouse->speak;
上記のコードは正しく動作する
    Animalでspeakを探す
        見つからない場合は、Animalの継承チェーンをたどりspeakを探す
            第一引数は、$classとなる

この方法の問題点
    @ISAと最初に検索するパッケージを同じものにする手作業が入る
    @ISAに複数のエントリがある場合、どちらがspeakを定義しているのか不明な場合がある
11.8 SUPERなやり方
use strict;

{
    package Animal;
    sub speak
    {
        my $class = shift;
        print "a $class goes ", $class->sound, "!\n";
    }
}

{
    package Mouse;
    use base qw/ Animal /;

    sub sound { 'squeak' }

    sub speak
    {
        my $class = shift;
        $class->SUPER::speak( @_ ); # SUPERクラスを使う
        print "[but you can barely hear it!]\n";
    }
}

Mouse->speak;
Animalクラスを、SUPERクラスに書き換えることで、
@INCにエントリーされているクラスから、speakが検索されるようになる
    @INCの中で複数のspeakが定義されている場合
        最初に見つかったものが呼び出される
11.9 @_の処理
親クラスの動作に何か処理を追加している場合
    @_を引数として渡すべき
        $class->SUPER::speak( @_ );

親クラスの動作を正確に制御するような場合
    明示的に引数リストを見定めて、渡すべき


続・初めてのPerl 改訂版

続・初めてのPerl 改訂版