自作サービスをもとにSOLID原則について学んでみた
1. はじめに
この記事では、私が開発した映画レコメンドアプリ「MoviReco」を題材に、オブジェクト指向設計の原則「SOLID」について振り返ってみたいと思います。
MoviReco
SOLID原則は理解するのが難しいと言われがちですが、自分のコードに当てはめてみることで、一気に実感が湧きました。この記事では、各原則をMoviRecoの設計に照らし合わせながら、実際にどのように意識・適用したかをご紹介します。
2. SOLID原則とは?
SOLIDとは、以下の5つの設計原則の頭文字を取ったものです。
- S: 単一責任の原則(Single Responsibility Principle)
- O: 開放/閉鎖の原則(Open/Closed Principle)
- L: リスコフの置換原則(Liskov Substitution Principle)
- I: インターフェース分離の原則(Interface Segregation Principle)
- D: 依存関係逆転の原則(Dependency Inversion Principle)
それぞれの定義を軽く紹介しつつ、以降でMoviRecoの構成と紐付けていきます。
3. SRP:単一責任の原則
1つのクラスは1つの責任だけを持つべき
MoviRecoでは、Controller / Service / Mapperを明確に分けています。
たとえば:
MovieRecommendationController:HTTPリクエストの受け口
MovieRecommendationService:映画推薦のビジネスロジック
FavoriteMapper:DB操作
これにより、「この処理どこにある?」が迷子になりにくくなっています。
4. OCP:オープン/クローズドの原則
「拡張に開かれ、修正に閉じている」構造を意識する
MoviRecoでは現在、サービスクラスが9つありますが、将来的な機能追加や実装差し替えを見据えて、一部のサービスにはインターフェースを導入しています。
特に MovieRecommendationService
は、ユーザーの回答に応じた映画推薦の中核を担うため、今後の拡張に対応できるよう、以下のような構成としています:
クラス名 | 役割 |
---|---|
MovieRecommendationService |
推薦機能のインターフェース |
MovieRecommendationServiceImpl |
現在の推薦ロジックの実装クラス |
このようにインターフェースと実装を分離することで、新しいロジックを追加する際も既存コードを修正する必要がなくなり、変更に強い拡張性の高い設計が実現できます。
今後も変更や追加の可能性が高いサービスに対しては、同様にインターフェース化を進めていく予定です。
5. LSP:リスコフの置換原則
親クラスの代わりに子クラスを使っても、システムの動作が壊れてはいけない
今のところLSPを明確に意識した作りにはしていませんが、いずれ「人気順」や「閲覧履歴ベース」など、いろんなレコメンドの仕組みを切り替えられるようにしてみたいなと思っています。そういった構成にするには、どのクラスを使っても同じように動くような作りにしておく必要があるので、LSPの考え方が大切になりそうです。
6. ISP:インターフェース分離の原則
使わないメソッドを強制されるような巨大なインターフェースはNG
現時点でMoviRecoにおいてインターフェースとして定義されているのは MovieRecommendationService
のみですが、これは映画推薦機能の中核であり、将来的な実装の切り替えを見据えて導入しています。
そのほかのサービスクラスでは、機能ごとに責務を分けることで、そもそも「巨大なクラス/巨大なインターフェース」になりにくい設計となっています。以下は現在の主要なサービスクラスとその責務です:
サービスクラス名 | 主な責務 |
---|---|
MovieSaveService |
映画の保存(お気に入り登録) |
MovieFetchService |
映画の取得 |
MovieDeleteService |
保存済み映画の削除 |
UserService |
ユーザー登録・取得 |
ContactService |
お問い合わせの登録・取得 |
MovieSearchService |
タイトル検索 |
MovieDetailService |
映画詳細・配信情報の取得 |
ProviderService |
配信サービスの取得 |
これらのサービスはインターフェースとして分離されてはいないものの、クラスごとに明確な責務を持つ構造となっており、結果としてインターフェース分離の原則に沿った設計になっています。
今後、各サービスに対しても依存の明示やテスト容易性を高める必要が生じた場合には、それぞれの責務に応じたインターフェースを導入していくことで、より柔軟で拡張性のある設計が実現できると考えています。
7. DIP:依存関係逆転の原則
上位のモジュール(ビジネスロジック)が、下位の実装(具象クラス)に依存してはいけない
MoviRecoでは、サービス層の多くが具象クラスとして実装されており、現時点でインターフェースを導入しているのは MovieRecommendationService
のみです。
層 | クラス名 | 備考 |
---|---|---|
Controller | MovieRecommendationController |
インターフェースに依存(DIにより注入) |
Service | MovieRecommendationService |
推薦処理のインターフェース |
Impl | MovieRecommendationServiceImpl |
実際の推薦ロジックの実装クラス |
このように、Controller
がインターフェース経由でサービスを扱う構成とすることで、実装の詳細に依存せず、柔軟でテストしやすい設計が実現できます。
今後は他のサービスクラスについても、拡張やテストのしやすさを考慮して、インターフェース経由で注入する形にリファクタリングしていく予定です。
8. 実際に意識してよかったこと
- 責務ごとにファイルが分かれて、構造が分かりやすくなった
- 処理を追加・変更するときに迷わなくなった
- 将来的に機能を追加するときの見通しが良くなった
9. おわりに
MoviRecoの開発を通して、SOLID原則の大切さを肌で感じました。最初から完璧を目指すのは難しいですが、「これは単一責任か?」「依存しすぎてないか?」と問い続けるだけでも、設計は確実に良くなります。
今後も継続的に改善しながら、設計力を高めていきたいと思います。
読んでいただきありがとうございました!
Discussion