抽象化とは、細かい実装を隠し、必要な性質や操作だけを公開することです。
6.1 カプセル化
データと、それを扱う操作を一つにまとめます。
class BankAccount
{
private int $balance = 0;
public function deposit(int $amount): void
{
if ($amount <= 0) {
throw new InvalidArgumentException();
}
$this->balance += $amount;
}
}
外部から残高を自由に変更させず、正しい操作だけを許可します。
6.2 インターフェース
実装ではなく、満たすべき契約を表します。
interface UserRepository {
findById(id: number): Promise<User | null>;
save(user: User): Promise<void>;
}
実装は複数存在できます。
MySQL実装
PostgreSQL実装
メモリ上のテスト実装
外部API実装
利用側は具体的な保存方法を知らなくて済みます。
6.3 継承
既存の型を基に新しい型を作ります。
Animal
├─ Dog
└─ Cat
ただし、継承は実装と状態を強く結合させやすいため、現代的な設計ではコンポジションが好まれる場合があります。
6.4 コンポジション
必要な機能を別オブジェクトとして組み合わせます。
class UserService {
constructor(
private repository: UserRepository,
private mailer: Mailer
) {}
}
「継承して機能を得る」のではなく、「必要な機能を持つオブジェクトを受け取る」設計です。
6.5 公称型と構造的型付け
公称型
型名や明示的な宣言によって互換性が決まります。
Java、C#、Rustなどが代表的です。
class UserId {
int value;
}
構造が同じでも、別名の型とは区別されます。
構造的型付け
必要なプロパティやメソッドを持っていれば互換とみなします。
TypeScriptが代表的です。
type Named = {
name: string;
};
const user = {
name: "Taro",
age: 30
};
const named: Named = user;
userはnameを持つため、Namedとして扱えます。