Webプログラマの夏休み PHPでNゲージ模型を自由自在に動かそう[動画つき]

第2回オブジェクト化しよう

画像

はじめに

オブジェクト指向プログラミングの入門書では、動物の鳴き声を使ったサンプルをよく見ますね。

リスト1 オブジェクト指向でよく見かけるサンプル
 class cat {
   function mew() {
     return "ニャー";
   }
   function hasNekomimi() {
     return TRUE;
   }
 }
 // kitten is a cat.
 class kitten extends cat {
   function mew() {
     return "ミー";
   }
   // hasNekomimiメソッドは、catクラスから継承される
 }
 // phpはダックタイピングなので、catクラスのかわりに渡せる
 class catgirl {
   function mew() {
     return "にゃんっ";
   }
   function hasNekomimi() {
     return TRUE;
   }
 }

これは一見わかりやすく思えますが、どう実際のプログラムにあてはめればよいか、初心者の方はわかりにくいのではないかという気もしました。そこで本稿では、現実の物をオブジェクトとして扱うことで、オブジェクト指向について別の面から見ることに挑戦してみたいと思います。

オブジェクト指向の基礎

まず、サンプルから見ていきましょう。前回の往復プログラムを、railクラスを使って書き換えてみましたリスト2⁠。

リスト2 railクラスを使う
#! /usr/local/bin/php
<?php
$url = "http://192.168.0.100/";

class	rail {
    var    $number;
    function    rail($number = "00") {
        $this->number = $number;
    }
    function    drive($speed = 0) {
        global    $url;
        
        if ($speed >= 0)
            $command = substr("ABCDEF", min($speed, 5), 1);
        else
            $command = substr("abcdef", min(-$speed, 5), 1);
        file_get_contents($url."?".$this->number."=".$command);
    }
    function    sense() {
        global    $url;
        
        return file_get_contents($url."?".$this->number."=S") + 0;
    }
}

#
# |---r4---|---r3---|---r2---|---r1---|---r0---|
#

$r0 =& new rail("00");
$r1 =& new rail("01");
$r2 =& new rail("02");
$r3 =& new rail("03");
$r4 =& new rail("04");

for (;;) {
    $r0->drive(2);
    $r1->drive(3);
    $r2->drive(3);
    $r3->drive(2);
    while ($r4->sense() == 0)
        ;
    $r0->drive(0);
    $r1->drive(0);
    $r2->drive(0);
    $r3->drive(0);
    
    $r4->drive(-2);
    $r3->drive(-3);
    $r2->drive(-3);
    $r1->drive(-2);
    while ($r0->sense() == 0)
        ;
    $r4->drive(0);
    $r3->drive(0);
    $r2->drive(0);
    $r1->drive(0);
}

?>

「class rail {」のところがクラスの定義なのですが、今回は説明を割愛します。かわりに、railクラスの取扱説明書を書くとしたら、どうなるでしょうか。

  • (1)あらかじめ「$r0 =& new rail("00");」としておきます。"00"はレールの番号です。
  • (2)⁠$r0->drive(3);」とすると、レールに電圧がかかります。数字が大きいと、スピードも速くなります。ゼロだと停止で、マイナスだと逆方向になります。
  • (3)⁠$r0->sense();」とすると、レールに車両があれば1、なければ0が返ります。

これにより、後半の制御部分ではレールの番号に依存しなくて済むようになりました。たとえば配線ミスがあってレールの番号を変えたくなったら、⁠new rail("00")」のところだけ変えれば、残りはそのままで大丈夫です。

さて、これだけなら配列変数でも良さそうですが、クラスを使うとコード部分も入れ替えることができます。

継承

たとえば、レールのプラスマイナスを逆に配線してしまい、プログラムの方で対応したいときは、どうしたらよいでしょう。このままでは車両が逆向きに走ってしまいますから、普通なら「$r0->drive(3);」の部分を全部マイナスにしないといけません。

こんなときオブジェクト指向では、railクラスの後に、以下のような新しいクラスを追加します。

リスト3 revrailクラス
 class revrail extends rail {
   function drive($speed) {
     parent::drive(-$speed);
   }
 }

短いわりにはわかりにくいリストですが、やっていることはシンプルです。⁠class revrail extends rail」の部分で、railクラスを拡張してrevrailにすることを宣言しています。内容はdriveメソッドの変更で、引数$speedをマイナスにして、親であるrailクラスのdriveメソッドに渡しています。

使い方は簡単で、さきほどの「$r0 =& rail("00");」のところを「$r0 =& revrail("00");」にするだけです。これで、$r0を使っているところは全部逆向きになります。もちろん、今まで通りrailクラスも使用でき、こちらは何も変わりません。

呼び出し方もrailクラスのときと同じです。⁠$r0->drive(3);」とすれば、$r0がrevrailクラスなら、-3にしてrailクラスに渡されることになります。使う側は、$r0がrailクラスなのかrevrailクラスなのかを知らなくてもまったく問題ありません。

これを応用すると、あるレールは別のコントローラにつながっているためコマンドが違うといった場合でも、スマートに対応することができます。新しいクラスであっても、railクラスと同じメソッドが用意されていれば、railクラスのかわりに使うことかできるためです。コントローラに送るコマンドはrailクラスの中だけで完結しているため、コマンドが変更されても、railクラスの定義を変えるだけで済むのです。

なお、オブジェクト指向の用語では、⁠revrailクラスはrailクラスを継承している」⁠revrailクラスではdriveメソッドをオーバーライドしている」などと呼んでいます。また、Javaのような厳密な型宣言を必要とする言語では、たとえrailクラスと同じメソッドを持っていても、railクラスを継承していない限りrailクラスのかわりに利用することはできません。

同時走行

説明が長くなってしまいました。では、実際にオブジェクト指向を使って、2つの車両を同時走行させてみましょう。とはいっても、それぞれのレールに通電すればよいだけなので、特にオブジェクト指向でないとダメということではありません。

画像
図1 大小のトラックで、半分は共有しているレイアウト
図1 大小のトラックで、半分は共有しているレイアウト

ポイントで大きいループと小さいループが切り替わるようになっています。それぞれの車両が、小さいループと大きいループを交互に走ります。ダイヤグラムで書くと、図2のようになります。

図2 ダイヤグラム
図2 ダイヤグラム

プログラムはリスト4のようになりました。

リスト4 大小のトラックで2両が交互にループを通るプログラム
#! /usr/local/bin/php
<?php

$url = "http://192.168.0.100/";

class    rail {
    var    $number;
    function    rail($number = "00") {
        $this->number = $number;
    }
    function    drive($speed = 0) {
        global    $url;
        
        if ($speed >= 0)
            $command = substr("ABCDEF", min($speed, 5), 1);
        else
            $command = substr("abcdef", min(-$speed, 5), 1);
        file_get_contents($url."?".$this->number."=".$command);
    }
    function    sense() {
        global    $url;
        
        return file_get_contents($url."?".$this->number."=S") + 0;
    }
}

class    revrail extends rail {
    function    drive($speed = 0) {
        parent::drive(-$speed);
    }
}

class    point {
    var    $number, $number2;
    var    $fork = -1;
    function    point($number = null, $number2 = null) {
        $this->number = $number;
        $this->number2 = $number2;
    }
    function    fork($fork = 0) {
        global    $url;
        
        if ($fork == $this->fork)
            return;        # not change
        
        $this->fork = $fork;
        if ($this->number !== null)
            file_get_contents($url."?".$this->number."=".(($fork)? "P" : "p"));
        if ($this->number2 !== null)
            file_get_contents($url."?".$this->number2."=".(($fork)? "P" : "p"));
    }
}

#
#                    -------|---r0---+  -3 <--> +3
# +--------|---r4---<p0              |
# |                  |               |
# +---r5---|--------<p0              |
#                    --r1---|--------+  +3 <--> -3
#

$r0 =& new revrail("00");
$r1 =& new rail("01");
$r4 =& new revrail("04");
$r5 =& new rail("05");
$p0 =& new point("02", "03");

for (;;) {
#
#                    >>>>>>>|---r0---+
# +--------|>>>r4>>><p0              |
# |                  |               |
# +---r5---|<<<<<<<<<p0              |
#                    <<r1<<<|--------+
#
    $p0->fork(0);
    $r1->drive(3);
    $r4->drive(3);
    sleep(5);    # make sure of leaving $r5
    while ($r0->sense() == 0)
        ;
    while ($r5->sense() == 0)
        ;
    $r1->drive(0);
    $r4->drive(0);
    sleep(5);    # wait in station

#
#                    -------|---r0---+
# +>>>>>>>>|---r4---<p0              |
# |                  |               |
# +<<<r5<<<|--------<p0              |
#                    --r1---|--------+
#
    $p0->fork(1);
    $r5->drive(3);
    sleep(5);    # make sure of leaving $r4
    while ($r4->sense() == 0)
        ;
    $r5->drive(0);
    sleep(5);    # wait in station

#
#                    -------|>>>r0>>>+
# +--------|>>>r4>>><p0              |
# |                  |               |
# +---r5---|<<<<<<<<<p0              |
#                    --r1---|<<<<<<<<+
#
    $p0->fork(1);
    $r0->drive(3);
    $r4->drive(3);
    sleep(5);    # make sure of leaving $r5
    while ($r1->sense() == 0)
        ;
    while ($r5->sense() == 0)
        ;
    $r0->drive(0);
    $r4->drive(0);
    sleep(5);    # wait in station

#
#                    -------|---r0---+
# +>>>>>>>>|---r4---<p0              |
# |                  |               |
# +<<<r5<<<|--------<p0              |
#                    --r1---|--------+
#
    $p0->fork(0);
    $r5->drive(3);
    sleep(5);    # make sure of leaving $r4
    while ($r4->sense() == 0)
        ;
    $r5->drive(0);
    sleep(5);    # wait in station
}

?>

ここではポイントもクラスにしてみました。ポイントは2つを対で動かすことが多いので、2つのポイントを登録できるようにしてあります。

今回も実行結果を動画で見てみましょう。

大小トラックプログラムの実行
ニコニコ動画:https://www.nicovideo.jp/watch/sm7913893

次回は、それぞれの車両をオブジェクトにしてみます。

おすすめ記事

記事・ニュース一覧