ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子1=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
| この記事の要点 |
|
基本
abstract class Shape
{
public function __construct(protected string $label) {}
abstract public function area(): float; // 中身は書かない(; で終わる)
public function describe(): string // 通常のメソッドも持てる
{
return sprintf('%s の面積は %.2f', $this->label, $this->area());
}
}
class Circle extends Shape
{
public function __construct(private float $r)
{
parent::__construct('円');
}
public function area(): float // 実装が必須
{
return M_PI * $this->r ** 2;
}
}
echo (new Circle(2))->describe(); // 円 の面積は 12.57
// new Shape('図形');
// Fatal error: Cannot instantiate abstract class Shape
実装しないとエラーになる
class Broken extends Shape
{
// area() を実装していない
}
// Fatal error: Class Broken contains 1 abstract method and must therefore be
// declared abstract or implement the remaining methods (Shape::area)
このエラーはクラスを読み込んだ時点で出ます。実行してそのメソッドに到達するまで気づけない、ということがありません。これが抽象メソッドの最大の利点です。
// 中間クラスとして「まだ実装しない」ことは宣言できる
abstract class Polygon extends Shape
{
abstract public function sides(): int; // 抽象メソッドを追加してもよい
}
class Square extends Polygon
{
public function __construct(private float $side)
{
parent::__construct('正方形');
}
public function area(): float { return $this->side ** 2; }
public function sides(): int { return 4; }
}
書き方のルール
| できること | できないこと |
|---|---|
| 抽象メソッドと通常メソッドを混ぜる | 抽象メソッドに中身(波かっこ)を書く |
| プロパティ・定数を持つ | private abstract にする(子から実装できない) |
| コンストラクタを持つ | final abstract にする(矛盾している) |
| 抽象クラスを継承した抽象クラスを作る | 抽象メソッドが 1 つでもあれば new する |
| static な抽象メソッドを宣言する | — |
abstract class Sample
{
abstract public function a(): void; // OK
abstract protected function b(): void; // OK
// abstract private function c(): void; // Fatal error
// abstract public function d(): void {} // Fatal error: 中身を書けない
}
テンプレートメソッドパターン
抽象クラスの代表的な使い方です。処理の流れは親が決め、変わる部分だけ子に書かせます。
abstract class Importer
{
final public function run(string $path): int // 流れは固定(final)
{
$rows = $this->read($path);
$valid = array_filter($rows, fn($r) => $this->isValid($r));
foreach ($valid as $row) {
$this->save($row);
}
return count($valid);
}
abstract protected function read(string $path): array; // 形式ごとに違う
abstract protected function save(array $row): void; // 保存先ごとに違う
protected function isValid(array $row): bool // 既定の実装あり
{
return $row !== [];
}
}
class CsvImporter extends Importer
{
protected function read(string $path): array
{
$rows = [];
$fp = fopen($path, 'r');
while (($cols = fgetcsv($fp)) !== false) { $rows[] = $cols; }
fclose($fp);
return $rows;
}
protected function save(array $row): void
{
// DB に入れる
}
}
echo (new CsvImporter())->run('data.csv');
run() に final を付けると、順番を勝手に変えられません。共通の手順を守らせたいときに有効です。
インターフェースとの違い
| 抽象クラス | インターフェース | |
|---|---|---|
| 実装を持てる | 持てる | 持てない |
| プロパティを持てる | 持てる | 持てない(定数のみ) |
| コンストラクタ | 持てる | 宣言だけ可能 |
| 1 クラスがいくつ使えるか | 1 つだけ | いくつでも |
| メソッドの可視性 | public / protected | public のみ |
| 表すもの | 「〜の一種である」 | 「〜ができる」 |
interface Exportable
{
public function export(): string;
}
abstract class Report implements Exportable // 併用できる
{
public function __construct(protected array $rows) {}
protected function header(): string // 共通の実装は抽象クラスに
{
return '=== ' . static::class . ' ===';
}
}
class CsvReport extends Report
{
public function export(): string
{
return $this->header() . "\n"
. implode("\n", array_map(fn($r) => implode(',', $r), $this->rows));
}
}
function save(Exportable $e, string $path): void // 型はインターフェースで受ける
{
file_put_contents($path, $e->export());
}
受け取る側の型はインターフェースにしておくと、抽象クラスを継承していない実装も渡せるようになります。
トレイトとの使い分け
trait Loggable
{
protected function log(string $m): void { error_log($m); }
}
abstract class Job
{
use Loggable; // 実装を取り込む
abstract public function handle(): void;
}
| やりたいこと | 選ぶもの |
|---|---|
| 共通の手順を強制し、一部だけ差し替えさせたい | 抽象クラス |
| 「この操作ができる」ことを型で保証したい | インターフェース |
| 継承関係に関係なく同じ実装を配りたい | トレイト |
| 実装が 1 種類しかない | 普通のクラス(抽象化しない) |
実装が 1 つしかないうちは抽象クラスにしないでください。2 つ目が出てきて共通部分が見えた時点で切り出すほうが、正しい抽象になります。
実例: 支払い方法を差し替える
abstract class PaymentMethod
{
abstract public function name(): string;
abstract protected function charge(int $amount): string; // 決済 ID を返す
public function pay(int $amount): string
{
if ($amount <= 0) {
throw new InvalidArgumentException('金額が不正です');
}
$id = $this->charge($amount);
error_log("{$this->name()} で {$amount} 円を決済: {$id}");
return $id;
}
}
class CreditCard extends PaymentMethod
{
public function name(): string { return 'クレジットカード'; }
protected function charge(int $amount): string
{
return 'cc_' . bin2hex(random_bytes(4));
}
}
class BankTransfer extends PaymentMethod
{
public function name(): string { return '銀行振込'; }
protected function charge(int $amount): string
{
return 'bt_' . bin2hex(random_bytes(4));
}
}
function checkout(PaymentMethod $m, int $amount): void
{
echo $m->pay($amount);
}
checkout(new CreditCard(), 1000);
checkout(new BankTransfer(), 1000);
金額の検証とログ出力はどの支払い方法でも同じなので親に置き、実際の課金処理だけを子に書かせています。新しい支払い方法を足すときは PaymentMethod を継承するだけで、checkout() は変更不要です。
定数とインターフェース定数
abstract class Job
{
public const MAX_RETRY = 3; // 子からも self::MAX_RETRY で使える
abstract public function handle(): void;
}
interface HasStatus
{
public const OPEN = 'open'; // インターフェースにも定数を置ける
public const DONE = 'done';
}
class Task extends Job implements HasStatus
{
public function handle(): void
{
echo self::MAX_RETRY; // 3
echo self::OPEN; // open
}
}
状態を表す文字列の集合は、PHP 8.1 以降なら enum のほうが型安全です。
enum Status: string
{
case Open = 'open';
case Done = 'done';
public function label(): string
{
return match ($this) {
Status::Open => '未着手',
Status::Done => '完了',
};
}
}
echo Status::from('open')->label(); // 未着手
var_dump(Status::tryFrom('x')); // null(不正値でも例外にしない)
抽象クラスにしないほうがよい場合
- 実装が 1 つしかない — 2 つ目が出るまで普通のクラスでよい
- 共通部分が「たまたま同じ」だけ — 別々の理由で変わるものを 1 つにまとめると、片方の変更が他方を壊す
- 継承の階層が 3 段以上になる — どこに何が書かれているか追えなくなる。委譲かトレイトを検討する
- 子ごとに親のメソッドを空実装している — インターフェースを分けるべき合図
関連
- 文法 — 親カテゴリ
- 継承の構文
- オーバーライド
- ポリモーフィズム(多様性)の具体例
- カプセル化の具体例
- クラスとインスタンス
ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子1=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
子ページ
子ページはありません
同階層のページ
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- 【django】ログイン 認証機能 2026-09-08 03:22:09
- MySQL ERROR 1063 Incorrect column specifier for column|原因と直し方 2026-09-08 03:11:09
- 【PHPエラー】Object of class stdClass could not be converted to string 2026-09-07 07:46:04
- Julia Genie ローカル開発サーバ起動完全ガイド 2026-09-07 07:46:04
- Wi-Fi とは|規格と世代(Wi-Fi 4〜7)・周波数帯・CSMA/CA・WPA3 NEW 2026-09-07 07:45:13
- set コマンドでシェルオプションと位置パラメータを操作 | bash 2026-09-07 07:45:13
- JPEG(.jpg/.jpeg)画像形式の完全ガイド — 仕様・マジックナンバー・他形式との比較・EXIF 2026-09-07 07:45:13
- UE5 Get Overlapping Actorsで特定クラスだけ処理|Class Filter・Cast To 2026-09-07 07:45:13
- Linuxで特定拡張子のファイルを再帰削除|find -deleteの安全手順 2026-09-07 07:45:13
- Django CBVでテンプレートに値を渡す|get_context_data・extra_context 2026-09-07 07:45:13
- Linuxで複数ファイルの文字列を一括置換|find・sed・xargsの安全手順 2026-09-07 07:45:13
- Laravelルートグループ|prefix・middleware・nameで一括管理 2026-09-07 07:45:13
- Linux whichコマンドの使い方|絶対パス取得とtype・command -vの違い 2026-09-07 07:45:13
- Linux catコマンドの使い方|行番号表示・複数ファイル連結 2026-09-07 07:45:13
- unable to execute 'gcc': No such file or directory 2026-09-07 07:45:13
コメントを削除してもよろしいでしょうか?