SOLID原則とは?
プログラムを綺麗に保つための5つのお約束のこと。
これを守ることで、プログラムがごちゃごちゃにならずに済み、後で読んだり変更したりする時に楽になる。
以下の5つの原則の頭文字をとったもの
- S: Single Responsibility Principle(単一責任の原則)
- O: Open/Closed Principle(オープン/クローズドの原則)
- L: Liskov Substitution Principle(リスコフの置換原則)
- I: Interface Segregation Principle(インターフェース分離の原則)
- D: Dependency Inversion Principle(依存性逆転の原則)
これらの原則を理解し適用することで、コードはより柔軟で、読みやすく、そしてメンテナンスしやすいものになる。
(これから紹介するサンプルコードはswift6)
SwiftにおけるSOLID原則
S: 単一責任の原則 (Single Responsibility Principle, SRP)
「1つのクラスに色々な役割を詰め込みすぎないようにしよう」 ということ。
例えば、「レポートを作成するクラス」が「印刷」や「保存」までやるのはNG。
それぞれの機能を別のクラスに分けることで、修正や拡張がしやすくなる。
下記はswiftを用いた良い例です。
class Report {
func create() -> String { "This is a report" }
}
class ReportSaver {
func save(_ report: String) { print("Saving: \(report)") }
}
class ReportPrinter {
func print(_ report: String) { print("Printing: \(report)") }
}
let report = Report().create()
ReportSaver().save(report)
ReportPrinter().print(report)
O: 開放閉鎖の原則 (Open/Closed Principle, OCP)
コードを修正せずに新しい機能を追加したり、拡張できるようにしよう という考え方。
悪い例
class Report {
func create() -> String { "This is a report" }
func save(type: String) {
let report = create()
if type == "local" {
print("Saving locally: \(report)")
} else if type == "cloud" {
print("Saving to cloud: \(report)")
} else {
print("Unknown type")
}
}
}
良い例
class Report {
func create() -> String { "This is a report" }
}
extension Report {
func save() { print("Saving: \(create())") }
func print() { print("Printing: \(create())") }
}
L: リスコフの置換原則 (Liskov Substitution Principle, LSP)
子クラスは親クラスと同じように動くべきというルール
親クラスを使う場面で子クラスを入れ替えても、プログラムがちゃんと動くようにしようという考え方
良い例
親クラスであるShape()には、area() メソッド(面積を計算するメソッド)だけが定義されている。
子クラスであるRectangle(長方形)と Square(正方形)はShape()に従って、それぞれの面積を計算する area() メソッド(面積を計算するメソッド)を実装している。
Shape()とRectangle()又はSquare()を入れ替えても、プログラムが正常に動作しようとする考え方。
I: インターフェース分離の原則 (Interface Segregation Principle, ISP)
1つの大きなプロトコル、インターフェースではなく、役割ごとに小さく分けたほうがいいよね。という考え方
D: 依存関係逆転の原則 (Dependency Inversion Principle, DIP)
具体的な実装 (class) ではなく、抽象 (protocol) に依存
「上のもの(重要な部分)は下のもの(細かい部分)に直接依存しないようにしよう」 という考え方です。
悪い例
class Phone {
func call() {
print("電話をかける")
}
}
class PhoneApp {
var phone: Phone
init(phone: Phone) {
self.phone = phone
}
func makeCall() {
phone.call() // Phoneに依存している
}
}
良い例
// 抽象クラス(プロトコル)
protocol Caller {
func call()
}
// 具体的な実装(電話をかける方法)
class Phone: Caller {
func call() {
print("電話をかける")
}
}
// 具体的な実装(Skypeで電話をかける方法)
class Skype: Caller {
func call() {
print("Skypeで電話をかける")
}
}
// アプリは抽象(Caller)に依存
class PhoneApp {
var caller: any Caller
init(caller: any Caller) {
self.caller = caller
}
func makeCall() {
caller.call() // 抽象(Caller)に依存している
}
}
SOLID原則 まとめ表
原則 | 概要 | 例 |
---|---|---|
単一責任の原則 (SRP) | 1つのクラスには1つの責務だけを持たせる | レポート作成、保存、印刷をそれぞれ別のクラスに分ける |
開放閉鎖の原則 (OCP) | 既存のコードを変更せずに、新しい機能を追加できるようにする |
if-else を使わず、拡張 (extension ) で機能を追加 |
リスコフの置換原則 (LSP) | 子クラスは親クラスと置き換え可能であるべき |
Shape クラスを継承する Rectangle や Square が、適切に area() を実装 |
インターフェース分離の原則 (ISP) | 大きなインターフェースを小さく分割し、不要な依存を避ける | 1つの巨大なプロトコルではなく、必要な機能ごとに分割 |
依存関係逆転の原則 (DIP) | 具体的なクラスではなく、抽象 (protocol ) に依存する |
Caller プロトコルを定義し、Phone や Skype のような具体的な実装と分離 |
これらを守ることで、コードの可読性・再利用性が向上し、変更にも強い設計が可能になる。