はじめに
前回の記事「SwiftでSOLID原則(2b/3)」では、SOLID原則のリスコフの置換原則(LSP)について解説しました。この記事では、SOLID原則のインターフェース分離の原則(ISP)に関して、Swiftを使った例で、紹介したいと思います。
インターフェース分離の原則(ISP)
インターフェース分離の原則(ISP)は、「クライアントが使用しないインターフェースに依存することを避ける」ことを提唱しています。これは、各インターフェースが特定のクライアントのニーズに適したものになり、役割が明確であるべきだという考え方です。これにより、インターフェースの密結合を防ぎ、汚染(不要な依存)が減らせます。
概念的な例:
インターフェース分離の原則を理解するために、次のようなツールボックスを想像してみてください。
ある工場で複数の職人が働いていて、それぞれが異なる作業を担当しています。例えば、大工は木材を加工し、電気工は電気配線を行い、配管工は水道の配管を担当しています。それぞれの職人が必要とする道具は異なりますが、全員が1つの大きなツールボックスを共有していると仮定しましょう。
もし、すべての職人が同じツールボックスを使っている場合、大工には必要ない電気工事の工具や配管工事の道具が含まれているため、使わない道具を無駄に抱えてしまいます。さらに、道具を探すのが大変で、効率が落ちてしまうかもしれません。
インターフェース分離の原則では、このような問題を避けるために、ツールボックスを職人ごとに分けて、それぞれの職人が必要とする道具だけを含むようにします。こうすることで、職人ごとに最適化された道具セットが作られ、効率が上がり、無駄が減ります。
この考え方をソフトウェアに適用すると、インターフェースも同様に特定のクライアントに必要な機能だけを提供するべきだ、ということになります。つまり、必要ない機能が含まれたインターフェースは避け、各クライアントに合ったインターフェースを提供することで、依存関係が簡潔かつ明確になります。
現実世界の例:
二つのコードを見てみましょう、
例1:
以下のコードでは、すべての家電製品を表現するために Appliance
インターフェースが使われています。このインターフェースには turnOn()
, turnOff()
, setTemperature()
, playMusic()
などのメソッドが含まれており、様々な機能をまとめた広範なインターフェースになっています。しかし、すべての家電がこれらの機能を必要としているわけではありません。
// 家電製品のインターフェース
protocol Appliance {
func turnOn()
func turnOff()
func setTemperature(degrees: Int) // 温度設定
func playMusic() // 音楽を再生
}
// エアコン
class AirConditioner: Appliance {
func turnOn() {
print("エアコンがオンになりました")
}
func turnOff() {
print("エアコンがオフになりました")
}
func setTemperature(degrees: Int) {
print("エアコンの温度を\(degrees)度に設定しました")
}
// エアコンには音楽再生機能がないため、何も実装しない
func playMusic() {
// 何もしない
}
}
// スピーカー
class Speaker: Appliance {
func turnOn() {
print("スピーカーがオンになりました")
}
func turnOff() {
print("スピーカーがオフになりました")
}
// スピーカーには温度設定機能がないため、何も実装しない
func setTemperature(degrees: Int) {
// 何もしない
}
func playMusic() {
print("スピーカーが音楽を再生しています")
}
}
この例の問題点は、AirConditioner
や Speaker
クラスが、それぞれ使用しないメソッドを無理に実装していることです。例えば、エアコン (AirConditioner
) は音楽を再生する機能が不要ですが、playMusic()
メソッドの実装が強制されています。同様に、スピーカー (Speaker
) は温度を設定する機能がありませんが、setTemperature()
メソッドを持たざるを得ません。このように、使わないメソッドを持つことで、コードがわかりにくくなり、保守性も低下します。
例2:
// 電源のオン・オフ機能
protocol PowerControllable {
func turnOn()
func turnOff()
}
// 温度調節機能
protocol TemperatureAdjustable {
func setTemperature(degrees: Int)
}
// 音楽再生機能
protocol MusicPlayable {
func playMusic()
}
// エアコンは、電源と温度調節の機能のみを実装
class AirConditioner: PowerControllable, TemperatureAdjustable {
func turnOn() {
print("エアコンがオンになりました")
}
func turnOff() {
print("エアコンがオフになりました")
}
func setTemperature(degrees: Int) {
print("エアコンの温度を\(degrees)度に設定しました")
}
}
// スピーカーは、電源と音楽再生の機能のみを実装
class Speaker: PowerControllable, MusicPlayable {
func turnOn() {
print("スピーカーがオンになりました")
}
func turnOff() {
print("スピーカーがオフになりました")
}
func playMusic() {
print("スピーカーが音楽を再生しています")
}
}
この例では、各家電が必要な機能のみを持つように、インターフェースを PowerControllable
, TemperatureAdjustable
, MusicPlayable
の3つに分割しました。これにより、クラスは実際に必要なインターフェースだけを実装できます。
この例の改善点は、インターフェースの分離で各クラスは必要なインターフェースだけを実装しています。エアコン (AirConditioner
) は TemperatureAdjustable
を実装して温度設定が可能ですが、MusicPlayable
を実装していないため、無関係な playMusic()
メソッドを持つ必要がありません。
適用性:
この原則は、インターフェースの設計において凝集度を高め、インターフェースの肥大化を防ぎます。
実装方法:
似た機能をもつメソッドグループを見つけ、個別のインターフェースに分けることが効果的です。
メリット:
コードが明確になり、インターフェースが単純化され、不必要な依存関係が防止されます。クラスが自身の目的に応じたインターフェースのみを実装することで、コードが簡潔で明確になり、保守性も向上します。
制限:
インターフェースが増えることでコードが複雑になり、管理が難しくなる場合もあります。
結論:
ここまでで、SOLID原則の三つの重要な原則である「単一責任の原則」、「オープン/クローズドの原則」、「リスコフの置換原則(LSP)」と「インターフェース分離の原則 」 について掘り下げて学びました。次回の記事では、SOLID原則の最後の原則 依存性逆転の原則 に注目し、Swiftでの具体的な実装例とともに、その効果とメリットを紹介します。
次の記事まで、ハッピーコーディング!