0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftでSOLID原則(3b/3)

Last updated at Posted at 2025-01-22

はじめに

前回の記事「SwiftでSOLID原則(3a/3)」では、SOLID原則のインターフェース分離の原則(ISP)について解説しました。この記事では、SOLID原則の依存性逆転の原則(DIP)に関して、Swiftを使った例で、紹介したいと思います。

依存性逆転の原則(DIP)

依存性逆転の原則(DIP)は、「高レベルモジュールは低レベルモジュールに依存してはならず、両者は抽象化に依存するべき」という考え方です。抽象化を使用することで、モジュール間の依存を減らし、柔軟で保守性の高い設計が可能になります。特にテストの際、依存性の注入によりテストが簡単になる点も大きなメリットです。

概念的な例:

家の電気システムを例にしてみましょう。家電製品(冷蔵庫、テレビ、電子レンジなど)は直接的に電源(配線やコンセント)に依存しているように見えるかもしれませんが、実際には抽象化(コンセントやプラグの規格)を通して電力供給を受けています。

抽象化の役割:
  1. 家電製品は「電力を受け取る」ことだけを知っています
  2. 電力を供給する側(発電所や家庭内の配線)は、「どの家電製品に接続されるか」を意識していません

もし家電製品がそれぞれ異なる規格のプラグや電圧に直接依存していたらどうなるでしょうか?家の配線を新しい家電に合わせて変更しなければならず、非効率的で保守性が低くなります。

解決策: 抽象化(インターフェース)の導入

家電製品と電源の間に共通のインターフェース(例えば、コンセントとプラグ)を設けることで、どちらもお互いの詳細を知らなくても動作できるようにします。

現実世界の例:

二つのコードを見てみましょう、

例1:

以下のコードでは、NotificationManager クラスが具体的な実装である EmailService および SMSService に直接依存しています。この結果、コードは密結合となり、拡張性が損なわれます。

class EmailService {
    func sendEmail(message: String) {
        print("Email sent: \(message)")
    }
}

class SMSService {
    func sendSMS(message: String) {
        print("SMS sent: \(message)")
    }
}

class NotificationManager {
    private let emailService = EmailService()
    private let smsService = SMSService()
    
    func sendEmailNotification(message: String) {
        emailService.sendEmail(message: message)
    }
    
    func sendSMSNotification(message: String) {
        smsService.sendSMS(message: message)
    }
}

// 使用例
let manager = NotificationManager()
manager.sendEmailNotification(message: "こんにちは、メール!")
manager.sendSMSNotification(message: "こんにちは、SMS!")


この例の問題点は、NotificationManager クラスが EmailServiceSMSService に直接依存しているため、例えば PushNotificationService のような新しい通知サービスを追加する場合、NotificationManager を修正しなければなりません。コードのテストが難しくなり、わかりにくくなります。EmailServiceSMSService をモックとして置き換えることができません。
具体的な実装に直接依存することでコードが密結合となり、保守性が低下します。

例2:

protocol NotificationService {
    func sendNotification(message: String)
}

class EmailService: NotificationService {
    func sendNotification(message: String) {
        print("Email sent: \(message)")
    }
}

class SMSService: NotificationService {
    func sendNotification(message: String) {
        print("SMS sent: \(message)")
    }
}

class NotificationManager {
    private let notificationService: NotificationService
    
    init(notificationService: NotificationService) {
        self.notificationService = notificationService
    }
    
    func notify(message: String) {
        notificationService.sendNotification(message: message)
    }
}

// 使用例
let emailService = EmailService()
let smsService = SMSService()

let emailManager = NotificationManager(notificationService: emailService)
emailManager.notify(message: "こんにちは、メール!")

let smsManager = NotificationManager(notificationService: smsService)
smsManager.notify(message: "こんにちは、SMS!")

この例では、NotificationManager クラスが NotificationService プロトコルという抽象化に依存しています。この設計により、具体的な実装に対する依存が排除され、コードが柔軟かつテストしやすくなります。

この例の改善点は、新しい PushNotificationService を追加しても、NotificationManager を変更する必要がありません。NotificationService プロトコルをモックとして簡単に置き換えることができ、単体テストが容易になります。実行時に異なるサービスを簡単に切り替えることができます。依存性逆転の原則に準拠することで、抽象化に依存する設計の力を示し、コードの拡張性、テスト可能性、柔軟性を大幅に向上させています。

適用性:

この原則は、ソフトウェア設計において、結合を低くし、柔軟性を向上させるために有効です。

実装方法:

抽象化に依存させ、特定の実装には依存しないようにします。

メリット:

コードの拡張性が高まり、依存性の注入によってテストが容易になります。また、コードの再利用性も向上します。

制限:

抽象化によって複雑さが増し、特に設計が大規模になると管理が難しくなる場合があります。

結論:

これで、SOLID原則の全ての原則について掘り下げて学ぶことで、SOLID原則の理解が深まりました。これらの基本原則を実践することで、保守しやすく、柔軟性のあるSwiftコードの設計が可能になります。
次の記事まで、ハッピーコーディング!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?