7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

先日、GitHubが2022年に利用された言語ランキングを発表した。このランキングの上位を見てみると、JavaやC#、C++などオブジェクト指向言語はいまだ多くのソフトウェアの開発に利用されていることが分かる。
今回のアドベントカレンダーでは、そんなオブジェクト指向言語のリファクタリング手法に関する話をしようと思う。
※本記事はオブジェクト指向言語を利用したことがある人向けの話となってしまうため、あらかじめご了承願いたい。
※本記事は部活のアドベントカレンダーも兼ねているため、ご了承願いたい。

この記事の結論

この記事の結論を先に述べると
is-a関係でないクラスに継承が利用されているなら委譲に書き換えてみてはどうだろうか?
という提案になる。
ちなみに、委譲を用いたリファクタリングはM.Fowler氏のリファクタリングカタログ[1]でも紹介されているので、気になる方はそちらも参照してほしい。
継承と言う単語は聞くけど委譲は聞き慣れないという方も多いと思うのでそこら辺の解説もしていきたいと思う。

リファクタリング

この記事を見ている方ならご存知の方も多いだろうが、リファクタリングとは「外部から見た時の振る舞いを保ちつつ、理解や修正が簡単になるように、ソフトウェアの内部構造を変化させること[1]」と一般的に言われている。
つまり、メソッドやクラスを利用する立場の人に対して機能等価性を担保しつつ、コード内部を書き換えてより保守性を向上させるような変更を行うことである。

is-a関係

オブジェクト指向言語が持つ特徴の一つに継承がある(継承について知りたい方はこちらの記事とか見てほしい)。
継承を用いるかどうかの判断基準として、よく「is-a関係であるかどうか」が用いられる。
継承におけるis-a関係とは、あるインスタンスがとあるインスタンスとしても見做せると断言できないならば継承してはならない[2]というものである。
たとえば、以下の事例はis-a関係に合致しそうだ。

class Person {
    public int age;
}
class Engineer extends Person {
    // 一般的にエンジニアは人間だよね
}

しかし、集団で開発していると特定の機能を使いたいがためにis-a関係をガン無視して継承を使ってしまう人が出てきてしまう。これをされると、原則を無視したコーディングになっているから可読性が低いコードになってしまいがちになるし、オブジェクト指向の基礎概念であるカプセル化の観点からも推奨されない。
そのため、あるクラスの一部の機能だけ使うhas-a関係が成り立つなら、後述の委譲で書き換えてしまったほうがいい。

委譲

委譲とは、とあるクラスの一部の機能だけを別のクラスで利用する実装方法である。
例えば、元のクラスを包含するよう実装するラッパークラスには委譲の概念が入り込んでいるかと思われる。

今回は、継承を用いている実装を委譲で置き換える方法を紹介していきたい。

以下はis-a関係が成立しないのに継承を用いてしまっている例である。

/**
 * 車のクラス
 */
class Car {            
    public double amountOfGasoline;              // ガソリンの残量
    protected TorqueConverter torqueConverter;   // トルクコンバーター(車の部品)
    ...
}
/**
 * 車の所有者
 * CarOwner is not a Car.
 */
class CarOwner extends Car {       
    void drive() {
        if (super.amountOfGasoline > 0) {
            // 運転
        }
    }
}

車の所有者にとって、車は期待通りに動作するかが重要であり、中身の部品の情報を知らなくてもいい。しかし、継承を用いてしまうと継承先のクラスが必要としていない情報まで扱えてしまうため、保守の面でもよろしくない。

そこで、上記のCarOwnerクラスを委譲を用いて手直しすると以下のようになる。

// 車の所有者
class CarOwner {
    Car car;    
    void drive() {
        if (this.car.amountOfGasoline > 0) {
            // 運転
        }
    }
}

上記のようにオブジェクトを部品として所持する形であれば、車の情報が上手く隠蔽されているため保守しやすい。
委譲を用いた書き方のコードの方が、直感的に理解しやすいコードになるだろうし、オブジェクト指向の原則である単一責任の原則(1つのオブジェクトには複数の役割を与えてはいけない)にも合致する。

最後に

継承はオブジェクト指向言語が持つ最大の武器の一つであり、適切な場面で活用すれば保守性も大きく上がる便利な代物だ。しかし、安易に使ってしまうとオブジェクト指向の強みを潰してしまいかねないため、委譲も駆使したソフトウェア開発を推奨していきたい。

余談

せっかくなので、以下の著書を紹介しておきたい。
Refactoring: Improving the Design of Existing Code(著者:M.Fowler)

この本では様々なリファクタリングの手法が具体例とともに紹介されており、どんな時にどのリファクタリングを適用した方が良いかを詳細に解説している。
この本の初版は1999年と一昔前であるが、長きにわたり多くの方に支持され続けてきたことから、2018年には第二版が公開された。

プログラマーA「リファクタリングとは言っても具体的にはどんなことをすればいいのだろう...」
プログラマーB「要求通りに動作しているんだから別にリファクタリングとかしなくてもよくね?」

上記のようなモヤモヤを抱えている人には特にお勧めしたい一冊である。

参考文献

  • [1] Fowler, M. (1999). Refactoring: Improving the Design of Existing Code. Boston, MA, USA: Addison-Wesley. ISBN: 0-201-48567-2
  • [2] Bertrand Meyer. 1997. Object-oriented software construction (2nd ed.). Prentice-Hall, Inc., USA.
7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?