【ITニュース解説】Why ‘IS-A’ Broke My Java Code (And Why ‘HAS-A’ Saved It)
2025年09月17日に「Medium」が公開したITニュース「Why ‘IS-A’ Broke My Java Code (And Why ‘HAS-A’ Saved It)」について初心者にもわかりやすく解説しています。
ITニュース概要
Javaコードで「IS-A(継承)」の誤用が原因で問題が発生し、「HAS-A(合成)」で解決した事例を解説。オブジェクト指向の基本的な関係性を理解し、適切な設計を行う重要性を学ぶ。
ITニュース解説
プログラミングを学ぶ上で、オブジェクト指向の考え方は非常に重要だ。その中でも特に、クラスとクラスがどのように関係し合うかを示す「IS-A(イズエー)」と「HAS-A(ハズエー)」という二つの関係性は、コードの品質やメンテナンス性に大きく影響する。これらはしばしば混同されがちだが、その適切な使い分けが、後に大きな問題を防ぎ、コードをより堅牢で柔軟なものにする鍵となる。
まず、「IS-A」関係とは、プログラミングの世界では「継承(Inheritance)」と呼ばれる概念に相当する。「XはYの一種である」という関係が成り立つ場合にこれを用いる。例えば、「犬は動物の一種である」という例を考えてみよう。この場合、DogクラスはAnimalクラスから特性や振る舞いを引き継ぐことができる。Animalクラスに「鳴く」という共通の機能があれば、Dogクラスはその機能をそのまま使えるし、必要に応じてDog独自の「ワンと鳴く」という振る舞いを加えることもできる。これはコードの重複を避け、共通の機能を一箇所で管理できるという大きなメリットがある。しかし、この「IS-A」関係を安易に適用すると、やがて開発者を悩ませる問題に直面することがある。
ニュース記事の筆者は、まさにこの「IS-A」の誤用によってコードが破綻寸前になった経験を語っている。彼が陥った問題は、一つの機能を多くのクラスで共通化しようとしすぎたために発生した。例えば、様々な種類の通知(メール、SMS、プッシュ通知など)を扱うシステムを設計する際、「通知サービス」という抽象的なクラスを作り、そこから具体的な「メール通知サービス」や「SMS通知サービス」が継承するという形をとったとしよう。最初はうまくいっているように見えても、それぞれの通知方法が持つ固有の要件や機能が追加されるにつれて問題が浮上する。例えば、メール通知には「件名」が必要だが、SMS通知には不要かもしれない。プッシュ通知には「デバイスID」が必要だが、他の通知には関係ない。共通の「通知サービス」クラスがすべてのサブクラスにとって最適なインターフェースを提供できなくなり、不必要なメソッドを実装しなければならなかったり、既存のクラスを大規模に修正しなければならなくなったりする。このような状態を「緊密な結合(Tight Coupling)」と呼ぶ。クラス同士が強く結びつきすぎているため、一つのクラスの変更が他の多くのクラスに波及し、システムの柔軟性が失われてしまうのだ。結果として、新しい通知方法の追加や既存機能の変更が非常に困難になり、コードは脆くなってしまう。さらに、親クラスの振る舞いを子クラスが完全に代替できない場合、システム全体の予測可能性が損なわれるという「リスコフの置換原則(Liskov Substitution Principle)」の違反にも繋がる可能性がある。
ここで登場するのが、「HAS-A」関係、つまり「コンポジション(Composition)」という考え方だ。「XはYを持っている」という関係を表す。先の例で言えば、「車はエンジンを持っている」という関係がこれに当たる。CarクラスがEngineクラスのオブジェクトを内部に持つことで、CarはEngineの機能を利用できるようになる。これは、CarがEngineの機能を引き継ぐのではなく、Engineを部品として利用するイメージだ。この「HAS-A」関係の最大の利点は、「疎結合(Loose Coupling)」であることだ。CarクラスとEngineクラスは独立しており、Engineクラスに変更があっても、Carクラスに与える影響は最小限に抑えられる。異なる種類のエンジンを簡単に交換したり、新しいエンジンタイプを追加したりすることも容易だ。
ニュース記事の筆者は、この「HAS-A」関係に切り替えることでコードを救った。彼が最終的に採用したのは、各通知サービスを独立したインターフェースとして定義し、それらの通知サービスを「管理する」クラスが、必要な通知サービスオブジェクトを「持つ」という設計だった。具体的には、NotificationServiceというインターフェースを定義し、EmailNotificationServiceやSMSNotificationServiceがそのインターフェースを実装するように変更した。そして、NotificationManagerのようなクラスが、これらのNotificationServiceのインスタンスをリストとして「持つ」ようにしたのだ。これにより、NotificationManagerは、どの通知サービスを使うかを柔軟に選択できるようになり、新しい通知方法を追加する際も、既存のコードに大きな変更を加えることなく、新しいNotificationServiceの実装を追加するだけで済むようになった。これは、システムが「柔軟性」を獲得したことを意味する。特定のコンポーネントを簡単に交換したり、追加したりできるため、変更に強いシステムになるのだ。また、独立した通知サービスはそれぞれが完結した機能を持つため、「コードの再利用性」も高まる。別の場所で同じ通知機能が必要になった場合でも、そのサービスをそのまま利用できる。
まとめると、「IS-A」関係である継承は、確かにコードの共通化と型の統一には非常に強力なツールだ。しかし、「本当にXはYの一種であるのか」という問いに対して厳密に答える必要があり、安易な継承はコードの「緊密な結合」を引き起こし、将来的な変更や拡張を困難にする。一方、「HAS-A」関係であるコンポジションは、クラスが他のクラスを部品として利用する関係であり、「疎結合」と「柔軟性」をシステムにもたらす。これにより、コードはよりモジュール化され、変更に強く、再利用しやすいものになる。システムエンジニアを目指す上では、この二つの関係性の違いと、それぞれがコードにもたらす影響を深く理解し、適切な場面で適切な設計を選択する能力を磨くことが、質の高いソフトウェアを開発するための第一歩となるだろう。最初は複雑に感じるかもしれないが、実際のプログラミング経験を積み重ねる中で、この設計原則の重要性を肌で感じることができるはずだ。