アクセス修飾子を完全攻略!自動販売機と銀行の例で学ぶpublic・private・protected!

この記事では、プログラミングのオブジェクト指向で出てくるpublicprivateprotectedの違いについて、分かりやすく解説します!

アクセス修飾子について

OZ
OZ

プロ太先生、前の授業のコードで、publicって出てきたけど、これってなんなん?

↓↓前回の授業内容はコチラです↓↓

プロ太
プロ太

publicはアクセス修飾子といいます。簡単に言うと、どこからクラスのプロパティやメソッドにアクセスできるかを決める仕組みのことです。

どこからアクセスできるかどうかの仕組み?う〜ん、イメージが沸かないな〜。

アクセス修飾子を理解するのに、「自動販売機の仕組み」を思い浮かべると分かりやすいですよ。まず、以下のコードと、コードのイメージ画像を見てください。

//自動販売機のクラス(設計図)
class VendingMachine {
    private $price = 150;
    private $stock = 10;
    private $drink = "コーラ";

    // 外からでも見える部分1:飲み物の名前と価格を取得する関数
    public function getDrinkInfo() {
        return "{$this->drink} - {$this->price}円";
    }

    // 外からでも見える部分2:飲み物を購入する関数
    public function buyDrink($payment) {
        if ($this->checkPayment($payment) && $this->stock > 0) {
            $this->stock--;
            return "飲み物をゲット!";
        } else {
            return "お金が足りないか売り切れです。";
        }
    }

    // 内部専用:支払いが正しいかを確認する
    private function checkPayment($payment) {
        return $payment >= $this->price;
    }
}
// 設計図をもとに、実際に自動販売機1というインスタンスを生成
$vendingMachine1 = new VendingMachine()
// buyDrinkというボタンを押して、実行しているイメージ
echo $vendingMachine1->buyDrink(200); // 飲み物をゲット!

上記のコードのイメージ画像です。※あくまで私のイメージです。

「カプセル化」の力

ここで大事なポイントは『カプセル化』という考え方です。上記の画像で言うと、$price,$stock,$drinkやfunction checkPayment($payment)はカプセル化(機械の中に入れて設定を外から変えれないように)しています。カプセル化とは、必要な部分だけを公開(public)し、他の部分は隠して守る(private)ことです。

なるほどな〜!自動販売機の金額を外から変えることができたら、大変なことになるもんな〜。

もう1度、上のイメージ画像の自動販売機を思い浮かべてください。この自動販売機には:

  • 金額(データ)※$price
  • 商品数(データ)※$stock
  • 商品名(データ)※$drink
  • 商品の情報を確認する機能 ※function getDrinkInfo()
  • 金額を入力して商品を買う機能 ※function buyDrink($payment)
  • 入力された金額で商品が買えるかチェックする機能 ※function checkPayment($payment)

があります。
でも、私たちは自動販売機の機械の中の金額や商品名、商品数、入力された金額で商品が買えるかどうかをチェックするプログラムを変更することはできません。つまり、privateですよね?こうやって、privateにして、隠すのが「カプセル化」の考え方なんです!
逆に、商品の情報を確認する機能や、金額を入力して買う機能は、外からでも使えるようにしないといけないので、publicに設定します。

注意!よくあるpublicの勘違い!!

その後、授業は終了し、プロ太先生より、「今日学習したコードを自分でも書いて、提出」という宿題が出されましたが、OZくんは少しミスをしてしまいます。以下のコードを見て、どんなミスか分かりますか?

class VendingMachine {
    private $price = 150;
    private $stock = 10;
    private $drink = "コーラ";

    public function getDrinkInfo() {
        return "{$this->drink} - {$this->price}円";
    }

    public function buyDrink($payment) {
        if ($this->checkPayment($payment) && $this->stock > 0) {
            $this->stock--;
            return "飲み物をゲット!";
        } else {
            return "お金が足りないか売り切れです。";
        }
    }

    private function checkPayment($payment) {
        return $payment >= $this->price;
    }
}

echo $VendingMachine->buyDrink(200); 

プロ太先生〜♪昨日の授業で出された宿題を見てください〜♪

おぉ!OZくん♪よく頑張りましたね。ただ、ものすご〜く良い間違えをしています。どこだか分かりますか?

えぇ〜〜マジか〜〜(泣)昨日のコードと見比べてみます、、、
あ!!!!下から2行目の部分に、$vendingMachine1 = new VendingMachine()
がないのと、1番最後の行がecho $vendingMachine1->buyDrink(200); で答えと違う!!

そうです!最後の部分ですね。でもOZくんの気持ちも分かります。OZくんは、publicという名前がついているから、「どこからでも」呼び出せると思ったんじゃないですか??

そうです、、、。そう思っていました、、、。違うんですか?

確かに、publicは「どこからでも呼び出せる」と説明されることが多いんですが、正しくは、「インスタンスを生成すれば、どこからでも呼び出せる」が正解です。

この記事を書いている私は、始めてpublicを学習したときに、「どこからでも呼び出せるんだから、コードのどこに書いてもいいんじゃないの?」と思っていました。
実はこれは完全に間違った認識で、しつこいですが、publicは、インスタンスを生成すれば、どこからでも呼び出せるという説明が正しいです。上記の自動販売機の例で言えば、クラス(設計図)の状態では、実体がないのですから、当然、ボタンを押して商品名と料金の確認や商品の購入はできないですよね??

なるほどな~!!すごくスッキリしました!!今後は、インスタンス化を忘れないように気を付けます!

publicprivateについてのまとめ

つまり、クラス設計では、必要な部分だけをpublicにして公開し、それ以外はprivateで隠すことで、プログラムが安全で柔軟になります。publicprivateを上手に使い分けることで、コードが壊れにくくなるということを理解しておきましょう。

上記を理解した上で、アクセス修飾子について、再度まとめておきます。
アクセス修飾子とは、どこからプロパティやメソッドにアクセスできるかを制御するものです。
アクセス修飾子の主な種類は次の3つです:

  • public: インスタンス化すれば、どこからでもアクセスできる
  • private: クラスの内部からしかアクセスできない
  • protected: クラス内部およびその子クラスからアクセス可能

ちなみに、デフォルトでアクセス修飾子を省略した場合はpublicとして扱われます。ただ、つけた方がコードが読みやすくなるので、原則、省略せずにつけるようにした方がいいでしょう。

クラス(設計図)の継承とprotectedについて

では、今日の授業では、クラス(設計図)の継承と、さらに3つ目の修飾子でもあるprotectedについて勉強しましょう!

名前からして、固そうな感じやな~。

大丈夫!イメージを掴めば簡単ですよ!まずは、クラス(設計図)の継承から教えていきます。今回は、銀行に例えて説明していきますね。以下に設定を記します。

今回、作成したいプログラムの要件は4つです。

  • 「本部銀行」と「支店銀行」の2つのインスタンスをつくる
  • 「本部銀行」でできるのは、①入金する ②残高を確認する という操作の2つのみとする
  • 「支店銀行」でできるのは、①入金する ②残高を確認する ③引き出す という操作3つが可能
  • 残高は、「本部銀行」で管理し、「支店銀行」から入金しても、本部銀行で管理するものとする

おぉ〜〜!!「本部銀行」と「支店銀行」の2つのインスタンスを作るから、その設計図であるクラスも2つ作成しないといけないですよね!

そのとおりです!!では、実際に要件を満たしたコードを見ていましょう!

※以下のコードでは分かりやすさを重視し、クラス名や変数名を日本語にしています

// 本部銀行クラス
class 本部銀行 {
    protected $残高 = 0;  // 支店からもアクセスできる残高

    // 本部銀行への入金メソッド
    public function 入金する($金額) {
        if ($金額 > 0) {
            $this->残高 += $金額;
        }
    }

    // 残高を確認するメソッド
    public function 残高を確認する() {
        return "残高: " . $this->残高 . "円";
    }
}

// 支店銀行クラス(本部銀行を継承)
class 支店銀行 extends 本部銀行 {
    // 支店から残高を引き出すメソッド
    public function 引き出す($金額) {
        if ($this->残高 >= $金額) {
            $this->残高 -= $金額;
            return "{$金額}円を引き出しました!";
        } else {
            return "残高が不足しています。";
        }
    }
}

// インスタンスの生成
$本部 = new 本部銀行();
$支店 = new 支店銀行();

// 本部から本部銀行に1000円を入金
$本部->入金する(1000);
echo $本部->残高を確認する(); //残高 1000円

// 支店銀行から800円を入金
$支店->入金する(800);

// 支店銀行から残高を確認
echo $支店->残高を確認する(); //残高:1800円

// 本部銀行から残高を確認
echo $本部->残高を確認する(); //残高 1800円

// 支店銀行から500円引き出す
echo $支店->引き出す(500);

// 引き出し後の残高を支店で確認
echo $支店->残高を確認する(); //残高:1300円

//引き出し後の残高を本部で確認
echo $本部->残高を確認する(); //残高: 1300円

あれ??支店銀行のクラス(設計図)のところが、
【 class 支店銀行 extends 本部銀行 】って新しいコードがついてる。

そうです!この部分が、クラスの継承になります。この場合、継承された支店銀行でも、継承元の本部銀行にアクセスして、【 public function 入金する($金額) 】や 【 public function 残高を確認する() 】を使うことができるようになります。

へぇ〜なるほど!イメージ的には、本部から離れた場所にある支店銀行だけど、PCでアクセスして、本部のPCを動かしてるみたいだな〜♪

おお!そのイメージで大丈夫ですよ!飲み込みが早いですね!

じゃあ、先生!あとは、
protected $残高 = 0; 】ってあるけど、このprotectedは、何なんですか?
さっきの自動販売機の例と一緒で機械の中に隠しておいて、外から変更できないようにしたいから、privateじゃダメなんですか〜?

いい質問です!確かに、$残高は、外から変更されたら大変なので、privateでもいいんですが、そうすると、支店銀行から、この残高を変更することができないんです。

privateは、そのクラスからしかアクセスできないので、今回の例であれば、privateにすると、本部銀行からしかアクセスできない状態で、離れた場所の支店銀行のPCからでは、直接アクセスしての残高の変更ができなくなるイメージですね!

なるほど!確かに、本部とかだと、顧客の全個人情報だとか、支店からでもアクセスされたくない情報がありそうですもんね!そういう情報には、privateをつけておけばいいのか〜♪
じゃあ、もしかして、protectedは、継承した支店銀行には、アクセスを許して、情報を共有・変更してもいいよってことであってますか?

今日のOZくんは、覚醒してますね。その通りです。しっかりと理解できましたね。

継承とprotectedのポイント!

ここがポイントです:

  1. 継承の使い方
    • class 支店銀行 extends 本部銀行】 で、支店銀行は本部銀行のクラスを継承します
    • これにより、支店銀行は「入金」や「残高確認」の機能をアクセスして、直接利用できるため、重複するコードを書く必要がありません。
  2. protectedの役割
    • $残高protectedなので、継承先の支店銀行でもアクセスして使えますprivateだと本部銀行からしかアクセスできないため、支店銀行で残高を変更することができなくなります。
    つまり、protectedを使うことで、「本部と支店で残高を共有し、どちらからもアクセスできる」設計が実現できます。

今回の銀行の例で、継承protectedの便利さが少しでも伝わりましたか?
本部と支店がPCを使って同じ残高を管理するイメージで覚えておくと、応用もしやすいですよ!

まとめ:アクセス修飾子と継承を活用しよう!

今回の記事では、アクセス修飾子(publicprivateprotected)の違いや、それぞれの使いどころを自動販売機と銀行の例で学びました。

  • publicインスタンス化すればどこからでもアクセス可能
  • private:クラスの内部だけで使用し、外部や継承先からのアクセスを制限
  • protected:クラス内部と継承先のクラスからアクセス可能

また、継承の力を使えば、コードの重複を避け、保守性が高いプログラムを実現できます。「本部銀行と支店銀行」のような共有設計を作る際に、protectedが非常に便利であることが理解できたと思います。

プログラムを設計するときは、どの情報を外部に公開するか(public)、隠すべきか(private)、あるいは共有するべきか(protected)を意識することが重要です。こうした設計の工夫で、安全でメンテナンスしやすいコードが書けるようになります。

次は、さらに高度なオブジェクト指向プログラミングのテクニックに挑戦してみましょう!継承の応用や、他のデザインパターンを学ぶことで、プログラムの幅がさらに広がるはずです。