タスクばらし
- オブジェクト指向のメリット
- オブジェクト指向とは
- コンストラクタとは
- アクセス権
- 継承
- インターフェイス
- ポリモーフィズム
- 依存オブジェクトの注入
- 静的メソッド
オブジェクト指向のメリット
なぜ仕様変更に弱いのか?
- 工夫せずに書くとコードは保守しづらくなる
- メインプログラムに責務が集中し、凝集度が低く結合度が高い状態になっている
- メインプログラムに責務が集中 = 凝集度が低く結合度が高い → 変更しようとすると他の箇所も変更しないといけない
仕様変更に対応するには?
-
責務を委譲すると、メインプログラムから責務が取り去られ変更しやすくなる
-
メインプログラムはカードを引く処理の詳細を知る必要がない
require_once('Player.php');
// プレイヤー情報を登録する
$player = new Player('田中');
// プレイヤーがカードを引く
$cards = $player->drawCards();
- プレイヤーがカードを引くという責務を負っている
// Playerクラスを定義する
class Player
{
// プロパティを宣言する
private $name;
// メソッドを宣言する
function __construct($name)
{
$this->name = $name;
}
function drawCards()
{
// カードを引く処理
}
}
オブジェクト指向とは
-
オブジェクトとは「モノ」(プログラミングするのに便利な単位)のこと
-
クラスはオブジェクトの振る舞いを定義した、一種のデータ型
役割ごとに責務を委譲したくて、責務毎にひとまとまりにしたもの -
クラスはデータ(プロパティ)と振る舞い(メソッド)を責務毎にまとめたもの
プロパティは変数、メソッドは関数
class Player
{
private $name; // プロパティ
function __construct($name) // コンストラクタ(メソッド)
{
$this->name = $name;
}
function drawCards() // メソッド
{
}
}
- インスタンスとはクラスを元にして作られたデータのかたまり
クラスという雛形から生成された、具体的な魂を持ったモノがインスタンス
require_once('Player.php');
// プレイヤー情報を登録する
$player = new Player('田中'); // インスタンス
// プレイヤーがカードを引く
$cards = $player->drawCards();
オブジェクト指向って何?
- オブジェクト指向とは変更に対処しやすくするためのソフトウェアの開発手法
- 目的
- 変更に対処しやすくなる
- 手法
- オブジェクトに責務を以上する(オブジェクトを中心に考える)
- 効果
- 各クラスごとにロジックが集約される
- 仕様変更があったとしても、影響範囲を抑えられる
- インスタンスが生成されるたびに呼び出されるメソッドをコンストラクタと言う
インスタンスを初期化する時に呼び出される - 外から使われていないプロパティやメソッドを非公開にすると、非公開部分は自由に変更できるようになる
- 全て公開すると
- どのメソッドも他のクラスで使われている可能性があるため、修正しづらい
- 修正した際に他の部分に影響が出る可能性がある
- 公開・非公開部分を分けると
- 他のクラスへの影響範囲が公開部分に限定される
- 公開部分が同じであれば、非公開部分は自由に変更できるようになる
- クラスのプロパティやメソッドを公開部分と非公開部分に分ける仕組みがアクセス権
- 目的
- 影響範囲を限定する
- 非公開部分を交換可能にする
- キーワード
- public:どこからでもアクセスできる
- protected:そのクラス自身、そのクラスを継承したクラス、親クラスからのみアクセスできる
- private:そのクラスからのみアクセスできる
- 「クラスはたった1つのアクターに対して責務を負うべき」という原則
-
複雑な場合分けを条件分岐で対応することのデメリットは条件分岐が複雑になり見通しが悪くなる
-
各ルールの責務をクラスに委譲する
-
継承とはクラスが他のクラスの持つメソッドやプロパティを引き継ぐこと
-
継承先のメソッド/プロパティによって、継承元のメソッド/プロパティを上書きできる
-
抽象クラスには、関連するクラスが行えることを定義できる
-
インスタンスを生成できない
-
メソッドの実装を定義できない
- メソッドの定義をせずに、クラスが実装する必要があるメソッドを指定するための仕組みがインターフェイス
- インスタンスを生成できない
- メソッドの実装は定義しない
- メソッドの実装の定義を強制できる
- 子クラスにメソッドの実装を強制できる点はどちらも同じ
- 抽象クラス
- 継承先で使用するメソッドを実装することもできる
- クラスは単一の抽象クラスしか実装できない
- 目的:「実装の継承」。親クラスで実装できるものは実装するよ、できないものは子クラスで実装する
- インターフェイス
- メソッドの実装はできない
- クラスは複数のインターフェイスを実装できる
- 目的:「外部に対する約束」。必ずこのメソッドが実装されていることを約束する
-
ポリモーフィズムとは、抽象クラスやインターフェイス経由でオブジェクトに支持すると、そのオブジェクトのクラスの実装に応じて異なった動作が行われること
-
ポリモーフィズムは呼び出す側のロジックを一本化する仕組み
-
ポリモーフィズムを使うと、既存コードを修正することなく機能追加できる
- カプセル化とは詳細を隠すことにより、外部に公開されたインターフェイスだけを使ってプログラミングすること
- 再利用の単位という役割に特化した小さな構造がトレイト。機能をまとめたもの
- 個別インスタンスに紐付かない処理や、専用クラスを作るほどでない時に使うのが静的メソッド
- インスタンスに紐づく処理を静的メソッドで書くのはNG
- 静的メソッドはグローバル関数を定義しているに近い状態
- プログラム動詞が密結合な状態になりやすく、保守性が低下する
- 静的メソッドのデメリットとしてポリモーフィズムが使えない
- ポリモーフィズムは個別のインスタンスを切り替えることで実現
- 静的メソッドはインスタンスを静的できないためポリモーフィズムも使えない
-
名前空間とは、関連するクラスやインターフェイス、関数、定数をひとまとめにして扱うもの(イメージはディレクトリ)
-
名前空間を定義するには、namespaceを使う
- 名前空間の影響を受けるのはクラス(抽象クラスやトレイト含む)とインターフェイス、関数、定数
- 例外的な状況の時、プログラムが失敗するもの
ex) - 通信相手が応答しない
リクエストしたけど通信相手のサーバーが応答しない - ディスクが満杯
ファイルに書き込もうとしたけどディスクが満杯だった - 例外で失敗を伝えよう
例外とは、以上が発生した状態のこと。異常時にプログラムの実行を止める仕組みが発明された - try ... catch ... finally ブロックで例外を補足して処理を継続する
- 例外は握りつぶさない
- 条件を把握していることにはif文を使い、例外は例外的状態だけに使おう
- やたら広い例外を補足すると、適切な事後処理を行えない
コンストラクタとは
class Poker
{
function __construct()
{
echo 'ポーカーを開始' . PHP_EOL;
}
}
new Poker(); // ポーカーを開始
アクセス権
※基本的には非公開にし、他のクラスで使うものだけを公開するとよい
単一責任の原則
継承
抽象クラスを使おう
abstract class Rule
{
abstract public function getHand($cards);
}
※継承先にgetHandメソッドの実装を強制することができる。Ruleクラスでは実装を定義しなくて良い。
インターフェイス
interface Rule
{
public function getHand(array $cards);
}
class RuleA implements Rule
{
public function getHand(array $cards)
{
return 'pair';
}
}
抽象クラスとインターフェイスの違い
※具体的な実装を継承したい場合は抽象クラスにまとめ、メソッドの実装の強制だけを行う場合はインターフェイスを使う
ポリモーフィズム
※抽象(インターフェイス)に対してプログラムするので、具象(個別インスタンス)に依存せず、クラス間の繋がりを疎結合にでき、柔軟性が大幅に向上する
カプセル化
トレイト
trait TaxCalculator
{
private float $tax = 0.1;
private int $price;
public function taxedPrice()
{
return $this->price * (1 + $this->tax);
}
}
class Book
{
use TaxCalculator;
private int $price;
public function __construct(private string $title, int $price)
{
$this->price = $price;
}
}
$book = new Book('ハリーポッター', 1000);
echo $book->taxedPrice(); // 1100
静的メソッド
class HandEvaluator
{
public static function getWinner($hand1, $hand2)
{
// 勝者を判定する
}
}
class Game
{
public function start()
{
$hands = [];
foreach ([$this->name1, $this->name2] as $name) {
$player = new Player($name);
$cards = $player->drawCards($deck, $this->drawNum);
$hands[] = $handEvaluator->getHand($cards);
}
return HandEvaluator::getWinner($hands[0], $hands[1]);
}
}
静的メソッドは必要を感じたときだけ使う
※明確に使いたい理由があるときだけ使用し、それ以外は使用しない
名前空間
namespace 名前空間;
namespace 名前空間\サブ名前空間A\サブ名前空間B;
namespace Log\Logger1;
function info() {
print 'logger 1 info' . PHP_EOL;
}
class Logger {
static function alert() {
print 'logger 1 alert' . PHP_EOL;
}
}
namespace Log;
require_once 'Logger1.php';
use function Log\Logger1\info as info1; // asで指定した名前でアクセスできる
use Log\Logger1\Logger as Logger1; // asは省略可能
function info() {
print 'logger 2 info' . PHP_EOL;
}
class Logger {
static function alert() {
print 'logger 2 alert' . PHP_EOL;
}
}
info(); // logger 2 info
Logger::alert() // logger 2 alert
info1(); // logger 1 info
Logger1::alert() // logger 1 alert
例外処理
異常を伝えて対応する仕組みが必要
class CurrencyCal
{
const CURRENCIES = ['dollar', 'pound'];
static function calculateToYen($amount, $currency)
{
if (!in_array($currency, self::CURRENCIES)) {
throw new Exception('対応していない通貨です');
}
...
}
}
CurrencyCal::calculateToYen(100, 'won');
// PHP Fatal error: Uncaught Exception: 対応していない通貨です
try {
// 通常処理
} catch (Exception $e) {
// 例外処理
} finally {
// 必ず実行する処理
}
class CurrencyCal
{
const CURRENCIES = ['dollar', 'pound'];
static function calculateToYen($amount, $currency)
{
try {
if (!in_array($currency, self::CURRENCIES)) {
throw new Exception('対応していない通貨です');
}
} catch (Exception $e) {
echo '例外:' . $e->getMessage() . PHP_EOL;
} finally {
echo '必ず行われる処理' . PHP_EOL;
}
}
}
CurrencyCal::calculateToYen(100, 'won');
// 例外: 対応していない通貨です
// 必ず行われる処理