オープンクローズドの原則(OCP)

OCP

閉鎖解放の原則(OCP)は一言で表すと「拡張しやすく修正はしなくて済むような設計をしよう」」という原則です。

ソフトウェアの構成要素は拡張のために開いていて、修正のために閉じていなければならない。
これは、新たに変更が発生した場合既存のコードには修正を加えずに新しくコードを追加するだけで対応できるようにしようということです。

OCP違反コード

SRPの時に使用したLanguageクラスを少し修正しOCPに違反しているコードを用意しました。

Playgroundで実際に実行することが可能です。どこが違反しているでしょうか?

class Language{
    let name: String
    init(name: String) {
        self.name = name
    }
}

let languages:[Language] = [Language(name: "Swift"), Language(name: "Kotlin")]

func printLanguageDescription(data: [Language]) {
    data.forEach({
        if $0.name == "Swift" {
            print("Swiftの由来はアマツバメだよ!")
        }
        if $0.name == "Kotlin" {
            print("Kotlinの由来は開発された場所の近くにあったコトリン島だよ!")
        }
        
    })
}

printLanguageDescription(data: languages)

この実装の場合、新しい配列にLanguageが追加されるとprintLanguageDescriptionメソッドにまで修正が伝播してしまいます。

新たな変更

試しにGo言語を追加してみると以下のようになってしまいます。

let languages:[Language] = [Language(name: "Swift"),
                            Language(name: "Kotlin"),
                            Language(name: "Go")]

func printLanguageDescription(data: [Language]) {
    data.forEach({
        if $0.name == "Swift" {
            print("Swiftの由来はアマツバメだよ!")
        }
        if $0.name == "Kotlin" {
            print("Kotlinの由来は開発された場所の近くにあったコトリン島だよ!")
        }
        if $0.name == "Go" {
            print("Goの由来は開発したのがGoogleだからだよ!")
        }
        
    })
}

printLanguageDescription(data: languages)

Goを追加したことでprintLanguageDescriptionメソッドのロジックが変わってしまいました。変更に硬い設計になってしまっています。

OCPに準拠したコード

OCPを守った形にリファクタする方法はいくつか存在すると思いますが、今回はprotocolに準拠される形でOCP原則に一致させました。

protocol Language {
    var name: String {get set}
    func printLanguageDescription()
}

class Swift: Language {
    var name: String
    init(name: String) {
        self.name = name
    }
    func printLanguageDescription() {
        print("Swiftの由来はアマツバメだよ!")
    }
}
class Kotlin: Language {
    var name: String
    init(name: String) {
        self.name = name
    }
    
    func printLanguageDescription() {
        print("Kotlinの由来は開発された場所の近くにあったコトリン島だよ!")
    }
}

class Go: Language {
    var name: String
    init(name: String) {
        self.name = name
    }
    
    func printLanguageDescription() {
        print("Goの由来は開発したのがGoogleだからだよ!")
    }
}

let languages:[Language] = [Swift(name: "Swift"),
                            Kotlin(name: "Kotlin"),
                            Go(name: "Go")]

languages.forEach({
    $0.printLanguageDescription()
})

新たに別の言語を追加したい際もLanguageに準拠させるだけで、簡単に追加でき別の部分を修正する必要もなくなりました。

参考文献

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

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

オープン・クローズドの原則の重要性について

オープン・クローズドの原則(OCP: The Open-Closed Principle)