WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

オブジェクト指向プログラミングの五つ原則 - S.O.L.I.D (S編)

良いソフトウェアとは何か。

顧客満足を実現するアプリケーション?不具合がないソフトウェア?

難しい質問だと思っておりますが、概して機能の拡張や変更が容易でバグが発生しにくいことに纏められると思います。 かつて、手続き型プログラミングからクラス、メソッド、抽象化などの概念のオブジェクト指向プログラミングを経て関数型プログラミングに至るまで ソフトウェア開発方法論は開発者がより短い時間で様々な機能を少ないミスで開発できるように発展してきました。

今回はオブジェクト指向の五大原則と言われているSOLIDについて話してみましょう。

プログラミングのデザインパターンといえば有名な「GOFパターン」がありますが、SOLID原則はそれよりもさらに基本原則で良いデザインパターンを作成するために 必要なコンセプトだと行っても無理ではないと思います。

S.O.L.I.Dとは?

S - 単一責任の原則 (Single Responsibility Principle)

O - 開放・閉鎖原則 (Open/closed principle)

L - リスコフ置換原則 (Liskov substitution principle)

I - インタフェース分離の原則 (Interface segregation principle)

D - 依存性逆転の原則 (Dependency inversion principle)

1. SRP (単一責任の原則:Single Responsibility Principle)

A class should have one and only one reason to change, meaning that a class should have only one job.

上記の言葉を直訳すると「クラスはただ一つの理由で変更すべきだ。つまり、クラスは一つの機能だけを持っているようにすること。」になります。

SRP原則を適用すれば責任領域が明確になりますので、一つの責任を変更することが次々と他の責任の変更になり続ける連鎖作用を防止できます。

更に、責任を適切に分配することにより、コードの可読性の向上,メインテナンスに容易という利点があります。

それに加えSRP原則は次回紹介するOCP原則を適用する基盤となっています。

活用の例

リファクタリング(Refactoring)を要求する問題はこのSRP原則を考えていないことで発生する場合が多いです。

  • 散弾銃手術(Shotgun surgery) : ショットガンサージャリーはアンチパターン(antipattern)の一つで,新しいサービスや責任をコードベースに追加して 一つの変更に複数な変更が行ってしまうことをいいます。

ショットガンサージャリーコードの例)

void LogFunc()
{
    printf("Entering LogFunc()\n");
    ...
}

void LogFunc2()
{
    printf("Entering LogFunc2()\n");
    ...
}

...

void LogFuncN()
{
    printf("Entering LogFuncN()\n");
    ...
}

SRP BEFORE & AFTER

  • SRP原則適用前
class Guitar()
{
    public Guitar(String serialNumber, double price, Maker maker, Type type,
                  String model, Wood backWood, Wood topWood, int stringNum)
    {
        this.serialNumber = serialNumber;
        this.price = price;
        this.maker = maker;
        this.type = type;
        this.model = model;
        this.backWood = backWood;
        this.topWood = topWood;
        this.stringNum = stringNum;
    }

    private String serialNumber;
    private double price;
    private Maker maker;
    private Type type;
    private String model;
    private Wood topWood;
    private Wood backWood;
    private int stringNum;

    // ...
}

上記の要所の中で「price, maker, type, model, backWood, topWood, stringNum」はギターのタイプによって変更が多い部分だと思います。

この場合,ギターの種類によって変更する必要がない要所(例. serialNumber)あるいはクラス全体を修正しなければならないようになってしまいます。

では、SRP原則を考えてリファクタリングしてみましょう。

  • SRP原則適用後
class Guitar()
{
    public Guitar(String serialNumber, GuitarSpec spec)
    {
        this.serialNumber = serialNumber;
        this.spec = spec;
    }

    private String serialNumber;
    private GuitarSpec spec;

    // ...
}

class GuitarSpec()
{
    double price;
    Maker maker;
    Type type;
    String model;

    // ...
}

上記のように変更が発生した部分を新しいクラスに分けて処理したことが確認できます。

まとめ

クラスは一つのサービスに一つの責任を持たせて、名前はその内容を明確に表すように作成した方は良いと思っております。

ただ、単純に責任で区別することではなくて各機能の凝集力(cohesion)を考慮し、最初からオブジェクトの間にデカップリング(decoupling)する手間が掛かないようにした方が良いですね。

参考資料)Head First Object-Oriented Analysis and Design (O'Reilly 2006.11) - Brett D. McLaughlin, Gary Pollice, David West