「コーディングを支える技術」p.196〜p.198の補足

拙著「コーディングを支える技術」の11章では、オブジェクト指向が必須ではない言語を使って、一歩ずつオブジェクト指向に近づいていくことでオブジェクト指向を理解しようとしています。

ところが一番難しいところが終わってホッとしたのか、最後の一歩 p.196〜p.198 の説明が雑になってしまったようです。p.196〜p.198では階段に例えると3つの段差があります。しかし拙著では1段飛ばして2つしか説明していません。これはつまずきのもとなので、ここで補足説明をします

1段目

p.196では以下のコードにたどりつきました。これが階段の1段目です。このコードを、振る舞いを変えずに書き換えてみましょう。

# Perl
{
    package Counter;
    sub new{
        return {"value" => 0};
    }
    sub push{
        my $values = shift;
        $values->{count}++;
        print "$values->{count}匹\n";
    }
}

{
    # 初期化の処理をパッケージに入れた
    my $counter = Counter::new;
    my $c2 = Counter::new;

    # 引数にハッシュを渡す
    Counter::push($counter);  #-> 1匹
    Counter::push($counter);  #-> 2匹
    Counter::push($c2);       #-> 1匹
    Counter::push($counter);  #-> 3匹
    Counter::push($c2);       #-> 2匹
}

2段目

p.197ではblessを使うことで、pushのたびに「Counter::」を書く手間が省けることを解説しました。
次にこのblessを使う初期化処理をnewの中に入れたコードをp.198で紹介しました。
ところが、ここで階段を1段飛ばしています。そこで、まずは1段だけ進んでみましょう。

# Perl
# 本書にないサンプルコード。p.196とp.198の間に相当。
{
    package Counter;
    sub new{
        my $values = {count => 0};
        bless $values, "Counter";
    }
    sub push{
        my $values = shift;
        $values->{count}++;
        print "$values->{count}匹\n";
    }
}

{
    # 初期化の処理をパッケージに入れた
    my $counter = Counter::new;
    my $c2 = Counter::new;

    # 引数にハッシュを渡す
    $counter->push;  #-> 1匹
    $counter->push;  #-> 2匹
    $c2->push;       #-> 1匹
    $counter->push;  #-> 3匹
    $c2->push;       #-> 2匹
}

1段目のコードとの差分はこの2箇所です。

5c6,7
<         return {"value" => 0};
---
>         my $values = {count => 0};
>         bless $values, "Counter";
20,24c22,26
<     Counter::push($counter);  #-> 1匹
<     Counter::push($counter);  #-> 2匹
<     Counter::push($c2);       #-> 1匹
<     Counter::push($counter);  #-> 3匹
<     Counter::push($c2);       #-> 2匹
---
>     $counter->push;  #-> 1匹
>     $counter->push;  #-> 2匹
>     $c2->push;       #-> 1匹
>     $counter->push;  #-> 3匹
>     $c2->push;       #-> 2匹

前のコードではCounter::push($counter);と書いていたことが、$counter->push;と書けるようになりました。

しかし、my $counter = Counter::new;ではまだ「::」を使っています。これは大した問題では無いのですが、「::」と「->」を使い分けるのは嫌だなぁ、と思ったとしましょう。これも「->」に変えることができるでしょうか?

できます。それが階段の3段目になります。

3段目

p.198のコードは、newの中だけしか掲載していませんでした。全体はこうなります。

# Perl
{
    package Counter;
    sub new{
        my $class = shift;
        my $values = {count => 0};
        bless $values, $class;
    }
    sub push{
        my $values = shift;
        $values->{count}++;
        print "$values->{count}匹\n";
    }
}

{
    # 初期化の処理をパッケージに入れた
    my $counter = Counter->new;
    my $c2 = Counter->new;

    # 引数にハッシュを渡す
    $counter->push;  #-> 1匹
    $counter->push;  #-> 2匹
    $c2->push;       #-> 1匹
    $counter->push;  #-> 3匹
    $c2->push;       #-> 2匹
}

2段目のコードとの差分は3箇所です。

5a5
>         my $class = shift;
7c7
<         bless $values, "Counter";
---
>         bless $values, $class;
18,19c18,19
<     my $counter = Counter::new;
<     my $c2 = Counter::new;
---
>     my $counter = Counter->new;
>     my $c2 = Counter->new;

まずCounter::newではなくCounter->newで呼び出すようにしました。そうするとnewの第1引数としてCounterが渡されるようになります。そこでshiftでそれを取り出して、blessの際に使っています。

3段目は必要なのか?

「この3段目は必要なのか?newとpushで呼び出し方が違っても良いのでは?」という質問が出るかもしれませんね。その通り、呼び出し方が同じである必要はありません。「オブジェクトに結びついているメソッドと、そうでないメソッドは別のもの、だから呼び方も違う」という設計にしてもよかったのです。実際、C++ではそうなっています。以下のサンプルで、オブジェクトに結びついているmy_instance_methodはx.my_instance_methodと呼び出されていますが、結びついていないmy_static_methodはMyClass::my_static_methodと呼び出されています。

#include<iostream>

class MyClass {
public:
  static void my_static_method(){
    std::cout << "I'm static method!" << std::endl;
  }
  void my_instance_method(){
    std::cout << "I'm instance method!" << std::endl;
  }
};

int main(){
  MyClass::my_static_method();
  // -> I'm static method!

  MyClass x;
  x.my_instance_method();
  // -> I'm instance method!
}

一方、JavaやPythonではどちらもピリオドで呼び出します。

// Java
class MyClass {
    static void my_static_method(){
        System.out.println("I'm static method!");
    }
    void my_instance_method(){
        System.out.println("I'm instance method!");
    }

    public static void main(String[] args){
        MyClass.my_static_method();
        // -> I'm static method!

        MyClass x = new MyClass();
        x.my_instance_method();
        // -> I'm instance method!
    }
};
# Python
class MyClass(object):
    @staticmethod
    def my_static_method():
        print "I'm static method!";

    def my_instance_method(self):
        print "I'm instance method!";

MyClass.my_static_method()
# -> I'm static method!

x = MyClass()
x.my_instance_method()
# -> I'm instance method!

プログラミング言語の設計者は、それぞれ自分が良いと思うようにルールを設計します。なので、メソッドの呼び出し方をどうするかは、言語によって異なるわけです。

Perlでは慣習的に、後者の「同じ方法で呼び出す」が選ばれています。参考文献: http://perldoc.perl.org/perlobj.html

さいごに

このエントリーではp.196〜p.198の内容を解説しました。

拙著「コーディングを支える技術」の読者のみなさんから頂いたご質問・ご感想には、このような感じで補足記事を書いて行きたいと思っています。おきがねなくご質問・ご感想をお寄せ下さい。

拙著に関する他のエントリーは「「コーディングを支える技術」著者公式ページ」からたどれるようにします。