PHPオブジェクト指向

このレッスンで学ぶこと

ここでは、オブジェクト指向を使ってランダムにメッセージを表示するプログラムを作ります。オブジェクト指向を使うと、どんな良いことがあるのでしょうか。オブジェクト指向について、そして、クラスの定義方法など紹介します。

ここで学ぶポイント

  • オブジェクト指向について

ここで作るプログラムの例

オブジェクト指向を使って、ランダムにメッセージを表示するプログラムを作ってみます。
訪問するたびにメッセージが変わります。

画面
画面
画面
画面

オブジェクト指向を利用したランダムメッセージボード

以下が、オブジェクト指向を利用したメッセージボードのプログラムです。プログラムを実行するたびに、表示されるメッセージが変わります。

file: RandomMessage.php

<?php
// オブジェクトを作成
$msg = new RandomMessage();
// ランダムに表示するメッセージをセット
$msg->setMessage("今日も良い日になりそうですね!");
$msg->setMessage("来てくれてありがとうございます!");
$msg->setMessage("ゆっくりしていってくださいね!");
// ランダムに表示する
$msg->show();

// 独自クラスの定義
class RandomMessage {
    // メンバ変数
    private $list = array();
    // メソッドの定義
    public function setMessage($msg) { // メッセージの追加
        $this->list[] = $msg;
    }
    public function show() { // メッセージをランダムに表示
        $i = rand(0, count($this->list) - 1);
        $s = $this->list[$i];
        echo "<p>$s</p>";
    }
}

PHPでオブジェクト指向とは?

オブジェクト指向プログラミング(OOP、英語:Object-oriented Programming)とは何でしょうか。オブジェクト(Object)とは日本語で言うと「モノ」です。つまり「モノに注目して行うプログラミング」です。

オブジェクト指向を使わないプログラミングでは、関数をうまく使って、さまざまな物事を行っていました。しかし、オブジェクト指向では、操作手順よりも操作対象に注目していきます。

例えば、車で目的地まで行くまでの手順を関数で書くと次のようになるでしょう。

# 擬似コード(関数を使った手続き指向で記述)
$car = select_car("フェラーリ"); // 車種を選ぶ
start_engine($car);              // エンジンを始動
if (!check_oil($car)) { echo "オイルが足りません"; exit; } // オイル残量をチェック
set_navi($car, "東京タワー");    // 目的地をセット
start_car($car);                 // 出発

これをオブジェクト指向で書くなら以下のようになります。演算子の「->」はオブジェクトを操作したり、オブジェクトの持つパラメータにアクセスするのに使います。これは既にデータベースライブラリのPDOでも利用していますね。

# 擬似コード(オブジェクト指向で記述)
try {
  $car = new Car("フェラーリ"); // 車のオブジェクトを作り車種を選ぶ
  $car->startEngine();          // エンジンを始動
  $car->checkOil();             // オイル残量をチェック
  $car->setNavi("東京タワー");  // 目的地をセット
  $car->start();                // 出発
} catch (Exception $e) {
  echo "エラーがあります:" . $e->getMessage();
}

以上のように、PHPでオブジェクト指向は必須ではありません。なぜなら、オブジェクト指向を使わなくてもある程度のプログラムを作ることができるからです。では、なぜ、オブジェクト指向が用意されているのでしょうか。

それは、オブジェクト指向を使うことに大きなメリットがあるからです。主に3つの点をあげて長所を紹介しましょう。

  • (1)可読性が向上する
  • (2)安全に使える
  • (3)再利用性が簡単

(1)可読性の向上

まず、可読性が向上するのは、上記の疑似プログラムを見ることでも理解できるのではないでしょうか。車を操作する場面において、物事の中心となるのは「車」です。車を表すオブジェクト$carを中心にして、エンジンをスタートさせたり、オイルのチェックをしたり、目的地をセットします。

そして、オブジェクト指向を使うなら、クラス(設計図)とインスタンス(オブジェクトの実体)に役割を分けることができます。設計図さえしっかりしていれば、いくつも同じ製品を作成できるのと同じように、オブジェクト指向を使うなら、クラスを元に複数のインスタンス(オブジェクト)を作成することができます。

Carというクラスから、複数のインスタンスを作成する疑似コードを見てみましょう。一度クラスを定義しておけば、好きな場面で複数のインスタンスを作成し利用することができます。

// Carクラスのインスタンスを生成する
$car1 = new Car();
$car2 = new Car();
$car3 = new Car();
// Carのインスタンスを動かす
$car1->setNavi("東京タワー");
$car1->start();
$car2->setNavi("横浜中華街");
$car2->start();
$car3->setNavi("ディズニーランド");
$car3->start();
// ----------------------------
// Carクラスの定義
class Car {
  public function setNavi($place) { ... }
  public function  start() { ... }
}

本節の冒頭のプログラムでも、クラスの設計とオブジェクト(インスタンス)の利用を明確に分離することで、メイン処理の可読性が高くなっていることに気付いたことでしょう。クラスを定義するには、以下のように記述します。

[書式] クラスの定義
class クラス名 {
  // ここにメソッドやメンバ変数の定義を記述
}

(2)安全に使える(カプセル化/アクセス権)

そして、安全性に関してですが、これは特に複数人での開発を行う場合に威力を発揮する「カプセル化」の機構に秘密があります。ちょっと例を考えます。例えば、車のエンジンに搭載されている制御コンピューターは、コンピューターに詳しい整備士だけが操作する部分と言えるでしょう。もし、一般のユーザーが適当に触ってしまうなら、どうなるでしょうか。極端に燃費が悪くなったり、エンジンに負荷をかけ壊してしまうかもしれません。

プログラミングにおいても、同じ事が言える場合があります。ある機能を実装しようと思った時、既に便利なライブラリがないか探すことはよくあります。そして、ライブラリが見つかり、それを使う時には、多くの場合、ライブラリの使い方だけが分かれば目的が達せられます。そのがどのように実装されているのか調べたり、ライブラリを書き換えて使うことは少ないと言えます。

またライブラリの設計者になるなら、ライブラリを使うエンドユーザーの立場にたって、必要な操作だけをユーザーに提示したいと思うでしょう。これを実現するのがカプセル化です。

クラスを定義する時に、外部に公開したいメソッドにはpublicをつけ、ライブラリの内部でだけ使いたいメソッドにはprivateをつけます。これにより、privateなメソッドをライブラリの外部から呼ぶことができないようになります。

以下は、擬似コードですが、間違ったプログラム例です。クラス定義の外側で、privateなメソッド、setEngineParameter()を実行しようとしていますが、PHPのエラーがでます。このように、ライブラリの設計者が意図しないメソッドをユーザーが使えないように制限を加えることができるのです。

class Car {
  private function setEngineParameter($option) { ... }
  public function start() { ... }
}

$car = new Car();
$car->setEngineParameter(50); // ←privateなメソッドを呼ぶとエラーになる

再度、冒頭のプログラムに出てくるクラス定義を見てみましょう。

このプログラムでも、クラスの内部で使う変数(メンバ変数と呼ぶ)「$list」を、外部から勝手に書き換えられないように、private と宣言しています。

class RandomMessage {
    // メンバ変数
    private $list = array();
    // メソッドの定義
    public function setMessage($msg) { // メッセージの追加
        $this->list[] = $msg;
    }
    public function show() { // メッセージをランダムに表示
        $i = rand(0, count($this->list) - 1);
        $s = $this->list[$i];
        echo "<p>$s</p>";
    }
}

一般的な関数と、クラスの内部に定義した関数のことを区別するために、クラス内の関数を「メソッド(method)」と呼びますが、setMessage()メソッドやshow()メソッドには「$this->list」という表現が出てきます。メソッド内でメンバ変数を参照したり、メソッドを呼ぶ時には、自分自身を指す「$this」という特殊な変数を使ってアクセスを行います。

(3)再利用が簡単(オブジェクトの継承)

オブジェクト指向の重要な概念に、継承があります。これを正しく使うなら、再利用が簡単になり、生産性が向上します。プログラミングでは、似て非なるプログラムを作る場面が多くあります。クラスを設計する場面でも同じ事が言えます。

例えば、ペットショップなどで、動物を管理するプログラムを作ろうと思います。動物と言っても、犬や猫では管理する項目が異なります。しかし、同じ動物ですから、名前や体重など同じ共通の項目も存在します。継承の機能を使うなら、この共通の項目を括りだして、その差分だけをプログラムすることが可能になります。

犬(Dog)や猫(Cat)で共通する部分を動物(Animal)というクラスに括りだしておくのです。疑似クラスを作るなら、以下のようになります。

// 共通の項目を括りだした動物クラス
class Animal {
  public $type;      // 種別
  public $name;      // 名前
  public $weight;    // 体重
  public $birth;     // 誕生日
  public function calcAge() { ... }    // 年齢を計算するメソッド
  public function checkHelth() { ... } // 共通健康チェック
}
// 動物クラスを継承した犬特有の処理を加えたクラス
class Dog extends Animal {
  public $dogSize;   // 犬種によるサイズ
  public function checkHelth() { ... } // 犬独自の健康チェック
}
// 動物クラスを継承した猫特有の処理を加えたクラス
class Cat extends Animal {
  public function checkHelth() { ... } // 猫独自の健康チェック
}

その他

そのほかにも、保守性を高めることができるというメリットもあります。オブジェクト指向を使うなら、クラスごとに機能を明確に分離して部品化することができます。機能を分け部品と部品の依存度が低くなれば、保守性が高まります。機能がうまく分離していないなら、機能Aを修正したときに、機能Bにも影響が及び、それにより、機能Cでも不具合が生じるという事態が発生します。しかし、機能が分離しているなら、機能Aの修正のみに集中できます。ある程度の規模のプログラムであれば、頻繁に修正を繰り返すことになりますが、そのときに、できるだけ修正を最小限に止めるためにオブジェクト指向が役立ちます。また、ユニットテストと言って、クラスが正しく実装されているかを調べるためのテストツールも充実していますので、これを利用するなら、機能の修正によるバグを防ぐことが可能になります。

他には、クラスをインスタンス化しなくても利用できる静的クラスや、クラスを抽象化するために使うインターフェイスなどの機能があります。

以上、本節ではオブジェクト指向の触りの部分だけを紹介しました。より詳しい情報は、PHPのマニュアル「クラスとオブジェクト」の項をご覧ください。

PHPマニュアル > クラスとオブジェクト
http://www.php.net/manual/ja/language.oop5.php

オブジェクト指向活用のヒント

ところで、オブジェクト指向は強力なのですが、正しく使わないと逆効果になり、混沌とした状態を生み出すこともあります。そのため、正しく使うためのヒントとして、オブジェクト指向のよくあるパターンをまとめ上げた「デザインパターン」と呼ばれるパターン集があります。オブジェクト指向を本格的に使う場合には、このデザインパターンに精通しておくと良いでしょう。