単一責任の原則(SRP)

SRP

単一責任の原則(SRP)は1つのクラスに1つの役割を持たせるというものです。オブジェクト指向三大要素の中で言うとカプセル化に当たります。

この記事では、SwiftでのSRP違反に違反しているコードをもとにSRPとは何なのかを解説していきます。

SRP違反コード

SRPに違反したコードを用意しました。

Languageクラスでは、情報の表示ローカルデータベースへの保存という2つの役割を保持しています。クラスの役割が1つではなくなってしまっているので、洗練されたカプセル化からかけ離れてしまっています。

class Language{
    let name: String
    let description: String
    init(name: String, description: String) {
        self.name = name
        self.description = description
    }
    
    func informationDisplay() {
        print("\(name): \(description)")
    }
    
    func saveLanguage() {
        let userDefaults = UserDefaults.standard
        userDefaults.set("\(name): \(description)", forKey: name)
    }
}


let language = Language(name: "Swift", description: "由来となったのアマツバメは燕の巣を作るらしい、Swiftを食べよう!")
language.saveLanguage()

let userDefaults = UserDefaults.standard
print(userDefaults.string(forKey: "Swift")!)

Qiitaの記事にカプセル化に置けるわかりやすい説明があったので引用させていただきました。SRP違反のサンプルコードは十得ナイフの状態です

十得ナイフは便利ですが、コンピュータの世界において十得ナイフは必要ありません。もし現実世界においても四次元ポケットが存在したら十得ナイフはいらなくなるでしょう。栓抜きを必要とした時、四次元ポケットから何を取り出すでしょうか?わざわざ十得ナイフを取り出して十得ナイフの栓抜きを使うようなことをするでしょうか?答えはNOです。四次元ポケットからは栓抜きを取り出して使います。

https://qiita.com/tutinoco/items/6952b01e5fc38914ec4e

現状は小さなサンプルで2つしか責任を保持していないので問題がわかりにくいかもしれませんが、2つの責務が3つ4つ…と増えていった時に既存の機能に修正を加える必要が出てきたとします。

そんな時、十得ナイフ(神クラス)状態になってしまっていると修正・変更を行うことがとても困難になります。

SRPに一致させたコード

違反コードを修正しSRPに一致させたものにしました。

新規にLanguageDBというクラスを作成し、ローカルデータベースへの保存を行うという責任を分離しました。

class Language{
    let name: String
    let description: String
    init(name: String, description: String) {
        self.name = name
        self.description = description
    }
    
    func informationDisplay() {
        print("\(name): \(description)")
    }
}
class LanguageDB {
    func saveLanguage(language: Language) {
        let userDefaults = UserDefaults.standard
        userDefaults.set("\(language.name): \(language.description)", forKey: language.name)
    }
}


let language = Language(name: "Swift", description: "由来となったのアマツバメは燕の巣を作るらしい、Swiftを食べよう!")
let db = LanguageDB()
db.saveLanguage(language: language)

let userDefaults = UserDefaults.standard
print(userDefaults.string(forKey: "Swift")!)

責務の分離は行えましたが、上記のコードでも完全に依存関係は取り除けてはいません。

LanguageDBクラスがLanguageクラスに依存しています。Languageクラスが変更された場合LanguageDBクラスにまで変更が及んでしまう可能性があります。Languageクラスだけを独立して修正することはSRPだけでは少々役不足です。依存関係逆転の原則(DIP)を守ることで独立した修正を行うことができます。

DIPに関してはまた別の記事で紹介しようと思います。

参考文献

におうコードの問題集 〜ソフトウェア設計に立ち向かう編〜

開発者が知っておくべきSOLIDの原則

単一責任原則

オブジェクト指向と10年戦ってわかったこと