まとめ
- オープンクローズドの原則は「拡張に対して開いている」と「修正に対して閉じている」を同時に満たすこと
- オープンクローズドの原則に従うと既存のコードに影響を与えずに機能の拡張ができる
背景
社内の勉強会でSOLID原則を扱っていて今回はオープンクローズドの原則について予習します。
オープンクローズドの原則とは
「拡張に対して開いている」と「修正に対して閉じている」を同時に満たす設計のことです。
拡張に対して開いているとは「コードの振る舞いを拡張できる」という意味です。
修正に対して閉じているとは「コードの振る舞いを拡張しても、その他のコードはまったく影響を受けない」という意味です。
引用
https://www.shuwasystem.co.jp/book/9784798046143.html
オープンクローズドの原則に従っていない例
Userクラスを例にします。
Userクラスにはインスタンス変数にpermissionを持っていて、これで権限を設定できます。
class User {
name: string;
email: string;
permission: string;
constructor(name: string, email: string, permission: string) {
this.name = name;
this.email = email;
this.permission = permission;
}
getPermission(): void {
switch(this.permission) {
case 'admin':
console.log('管理者権限があります');
break;
case 'editor':
console.log('編集者権限があります');
break;
case 'viewer':
console.log('閲覧者権限があります');
break;
default:
console.log('不明な権限です');
}
}
}
const user1 = new User('山田太郎', 'yamada@example.com', 'admin');
user1.getPermission(); // 管理者権限があります
const user2 = new User('鈴木花子', 'suzuki@example.com', 'viewer');
user2.getPermission(); // 閲覧者権限があります
このclassを拡張する必要が出てきました。
例えば、新しい権限としてゲスト権限を追加したい場合です。
以下のようにするとコードを拡張できましたが、既存のコードを修正しています。
よってこれはオープンクローズドの原則を違反しています。
getPermission(): void {
switch(this.permission) {
case 'admin':
console.log('管理者権限があります');
break;
case 'editor':
console.log('編集者権限があります');
break;
case 'viewer':
console.log('閲覧者権限があります');
break;
case 'guest':
console.log('ゲスト権限があります');
break;
default:
console.log('不明な権限です');
}
}
オープンクローズドの原則に従っている例
Permissionをinterfaceとして定義し、管理者権限や各権限がそのinterfaceを実装するようにします。
interface Permission {
showPermission(): void;
}
class AdminPermission implements Permission {
showPermission() {
console.log('管理者権限があります');
}
}
class EditorPermission implements Permission {
showPermission() {
console.log('編集者権限があります');
}
}
class ViewerPermission implements Permission {
showPermission() {
console.log('閲覧者権限があります');
}
}
class User {
name: string;
email: string;
permission: Permission;
constructor(name: string, email: string, permission: Permission) {
this.name = name;
this.email = email;
this.permission = permission;
}
showPermission() {
this.permission.showPermission();
}
}
const user1 = new User("山田太郎", "yamada@example.com", new AdminPermission());
user1.showPermission(); // 管理者権限があります
const user2 = new User('鈴木花子', 'suzuki@example.com', new ViewerPermission());
user2.showPermission(); // 閲覧者権限があります
この設計で新しい権限としてゲスト権限を追加します。
以下を実装するだけで拡張が可能です。これは既存のコードに影響を与えていないのでオープンクローズドの原則に従っていると言えます。
class GuestPermission implements Permission {
showPermission() {
console.log('ゲスト権限があります');
}
}
Goでも書いてみました。
type Permission interface {
showPermission()
}
type AdminPermission struct{}
func (a AdminPermission) showPermission() {
println("管理者権限があります")
}
type EditorPermission struct{}
func (e EditorPermission) showPermission() {
println("編集者権限があります")
}
type ViewerPermission struct{}
func (v ViewerPermission) showPermission() {
println("閲覧者権限があります")
}
type user struct {
name string
email string
permission Permission
}
func (u user) showUserPermission() { u.permission.showPermission() }
func main() {
editor := user{name: "Alice", email: "alice@example.com", permission: EditorPermission{}}
editor.showUserPermission()
}
Goのインタフェースは他の言語とは異なり「暗黙的に」実装されます。
具象型の方ではあるインターフェイスを実装するかを宣言しません。
具象型Cのメソッド群がインターフェースIのメソッド群を完全に含めば、具象型CはインターフェイスIを実装することができます。
まとめ
- オープンクローズドの原則は拡張に対して開いている」と「修正に対して閉じている」を同時に満たすこと
- オープンクローズドの原則に従うと既存のコードに影響を与えずに機能の拡張ができる
今後勉強したいこと
- 流動的要素のカプセル化
- OCPを実現する手段としてのStrategyパターン等のデザインパターン
- バリエーション防護壁
参考
