【SOLID】インターフェース分離の原則を完全に理解したい


SOLIDの原則とは?

SOLIDは

  • 変更に強い
  • 理解しやすい
    などのソフトウェアを作ることを目的とした原則です。

次の5つの原則があります。

  • Single Responsibility Principle (単一責任の原則)
  • Open-Closed Principle (オープン・クローズドの原則)
  • Liskov Substitution Principle (リスコフの置換原則)
  • Interface Segregation Principle (インターフェース分離の原則)
  • Dependency Inversion Principle (依存関係逆転の原則)

上記5つの原則の頭文字をとってSOLIDの原則と言います。
今回の記事では Interface Segregation Principle (インターフェース分離の原則) について解説します。
その他の原則に関しては下記参照。

簡単に言うと...

インターフェース継承先で使わないメソッドがないようにインターフェースをわけよう」ということです。
尚、ここでいうインターフェスは、抽象クラスや基底クラスなども含んでいます。
具体例を用いながら詳しく見ていきましょう。

今回使用する例

ポケベル、ガラケー、スマートフォンを例に考えてみましょう。

上記例をコードに落とし込みながら考えていきましょう。
※ポケベルやガラケーの細かい定義の話をし始めると、上記例と微妙にぶれたりしますので、今回は上記の仮定で話を進めます。

インターフェース分離の原則に違反した例

ポケベル、ガラケー、スマートフォンの3つは共に電話の仲間です。
電話インターフェースを作ってコードを書いてみます。

IPhone.cs
interface IPhone
{
    public void SendMail();
    public void Call();
    public void UseApp();
}
Pager.cs
// ポケベル
public sealed class Pager : IPhone
{
    public void SendMail()
    {
        // メールを送る
    }

    public void Call()
    {
        // 電話をする
        // 使えない
    }

    public void UseApp()
    {
        // アプリを使う
        // 使えない
    }
}
FeaturePhone.cs
// ガラケー
public sealed class FeaturePhone : IPhone
{
    public void SendMail()
    {
        // メールを送る
    }

    public void Call()
    {
        // 電話をする
    }

    public void UseApp()
    {
        // アプリを使う
        // 使えない
    }
}
Smartphone.cs
// スマホ
public sealed class Smartphone : IPhone
{
    public void SendMail()
    {
        // メールを送る
    }

    public void Call()
    {
        // 電話をする
    }

    public void UseApp()
    {
        // アプリを使う
    }
}

IPhoneというインターフェースにすべての振る舞いが記述されています。
すべてのクラスがこれを継承することにより、使用することのできないふるまいが定義されてしまいます。
例えば、ポケベルクラスは使用できないはずの「電話する」「アプリを使う」というふるまいが定義されています。
使用してはいけないというふるまいが定義されることで、
この仕様を知らない人が実装する際に、「ポケベルでアプリを使う」というエキセントリックなことができてしまいます。

インターフェース分離の原則の適用例

解決方法は、文字通りインターフェースを分離していくことです。
今回は、

  • メールする
  • 電話する
  • アプリを使う

という3つの機能があるため、それぞれを下記のように分離してみましょう。

コードでの実装例は下記のようになります。

IMail.cs
interface IMail
{
    public void SendMail();
}
ICall.cs
interface ICall
{
    public void Call();
}
IApp.cs
interface IApp
{
    public void UseApp();
}
Pager.cs
// ポケベル
public sealed class Pager : IMail
{
    public void SendMail()
    {
        // メールを送る
    }
}
FeaturePhone.cs
// ガラケー
public sealed class FeaturePhone : IMail, ICall
{
    public void SendMail()
    {
        // メールを送る
    }

    public void Call()
    {
        // 電話をする
    }

}
Smartphone.cs
// スマートフォン
public sealed class Smartphone : IMail, ICall, IApp
{
    public void SendMail()
    {
        // メールを送る
    }

    public void Call()
    {
        // 電話をする
    }

    public void UseApp()
    {
        // アプリを使う
    }
}

上記の例のように

  • 必要となる最小限の機能ごとにインターフェースをわける
  • 必要なインターフェースのみをすべて継承する

としていくことで、インターフェース分離の原則を達成することができます。

まとめ

インターフェース分離の原則とは「インターフェース継承先で使わないメソッドがないようにインターフェースをわけよう」ということでした。
そうすることで下記のようなメリットが得られます。

  • 変更に強い
  • 理解しやすい

尚、今回使用したソースはこちらに上がっています。

補足

本記事の中でのインターフェース分離の原則はざっくりとした理解を補助するもので正確性は欠いています。
正しい説明では

汎用なインターフェースが一つあるよりも、各クライアントに特化したインターフェースがたくさんあった方がよい

などと表現されています。
もう少し詳しいことが知りたい場合は、参考文献にあげているような本や記事を読んでみてください。

参考文献