見出し画像

よくわかるSOLID原則4: I(インターフェース分離の原則)

ソフトウェアエンジニアが知っているべきSOLID原則についての記事です。SOLID原則は、5つの原則の頭文字を並べた言葉で、S・O・L・I・Dそれぞれの原則について、5回に分けて説明します。

1) Single Responsibility Principle:単一責任の原則
2) Open/closed principle:オープン/クロースドの原則
3) Liskov substitution principle:リスコフの置換原則
4) Interface segregation principle:インターフェース分離の原則
5) Dependency inversion principle:依存性逆転の原則

今回は4番目のインターフェース分離の原則です。

なぜソフトウェアエンジニアがSOLID原則について知っていなければいけないかは最初の記事をご覧ください。

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

ここでいうインターフェースとは、抽象クラス、基底クラス、ダックタイピング的なものを全て含めたインターフェースとしての働きを持つものです。

この原則は、インターフェースの利用者にとって不要なメソッドに依存させてはいけないというものです。

こういうとややこしく聞こえますが、簡単にいうとインターフェースの利用者の立場に立って、インターフェース自体、利用用途に応じた最小限の規則だけを決めておくか、それぞれのメソッドが独立して使えるようにすべきということです。

あと、逆の視点では、インターフェースを、実装する側の都合も考えてましょう。不要なメソッドがあると、それは大体技術的負債になります。

簡単にいうと、インターフェースを複雑にしてはいけないので、分離できるものは分離しましょうという原則です。

単一責任の原則(SRP)やオープン・クローズドの原則(OCP)、リスコフの置換原則(LSP)と大体関連が強い原則です。

SRPに抵触しがちなインターフェース

SRPに抵触するようなインターフェースはインターフェース分離の原則にも抵触しているでしょう。

複数の異なるアクター、たとえばECサイトの利用者と出展者と、システム管理者がいるとします。

ECサイトに出展される商品についてのインターフェースを作るとします。そのインターフェースは、商品情報を取得するメソッドがあるとします。ところが、ECサイトの利用者と出展者と、システム管理者では、取得したい情報に違いが生じます。

※もちろんこれはそもそもSRP違反です

・ createUserContext
・ createOwnerContext
・ createAdminContext
・ getItem

という4つのメソッドがあり、商品情報を取得するためには、最初の3つのメソッドを呼び出し、それぞれのアクターに応じたコンテキスト(という名のオブジェクト)を作り、getItemの引数にコンテキストを渡す必要があるとします。

これがインターフェース分離の原則に反する設計です。getItemにアクセスするために、create*Context を呼び出さなければいけない。というのは複雑さを増やしています。

アクターとしての利用者の為のコードを書くときに、createUserContextとgetItemの2つのメソッドを知っておき、getItem より以前に createUserContext を呼び出しておくという知識が必要になりますし、たとえば、ユーザー情報が何かしらの状況によって更新されてしまった場合には createUserContext でコンテキストを作り直す必要があるのでしょうか?ないのでしょうか?

エラー処理なども含めるとこういった制約は、複雑さをもたらすものです。

また、このインターフェース自体に、createOwnerContextcreateAdminContext という別のアクターが存在するという知識が含まれています。それはもちろんそれぞれのメソッドの引数に何が指定されるか?という知識も知ることになります。

そういった知識を知らないものとしてスルーするのが大人の態度かもしれませんが、その知識はそもそも不要です。

どうするべきか?

その getItem があるインターフェースがどうあるべきか次第ですが、

・ コンテキストの作成を分離する
・ アクターごとにインターフェースを分離する

という二種類の分離方法があるでしょう。

場合によってはアクターごとのインターフェースは、実はさらに別の、コンテキスト作成インターフェースと、商品情報にアクセスするためのインターフェースをアクセスするための、アダプタかもしれません。

同一のアクターでも分離したいインターフェース

わざわざインターフェース分離の原則という、SRPとは異なる原則がある以上当然ですが、同一のアクターだとしても分離したいケースもあります。

ECサイトの利用者という単一のアクターのみが利用したい場合もあるでしょう。その場合でも何かしら事前準備が必要になるケースもあります。たとえば、通販においては届け先の住所によって送料が異なります。

届け先に応じて、アクターを別途用意するのはさすがにやりすぎでしょう。そんなコードをメンテナンスしたい人はそうそういるはずがありません。

・ 届け先など商品情報に影響のある情報をコンテキストとして作成するメソッド
・ 実際に商品を取得するメソッド

他にも、複雑なインターフェースにしようと思えばいくらでも複雑にできるはずです。カートに追加されている商品や、持っているクーポン、あるいは他社と連携しているサービスによる何か、そういった商品情報に影響を与えるものは何かしらあるはずです。

これらを統合したインターフェースは複雑なインターフェースです。

実装者の都合

インターフェースは、必ずしも1つの実装と対応しているわけではありません。インターフェースを、実際に実装するケースもあるでしょう。たとえば、データのリポジトリのインターフェースを設計するとします。

このときインターフェースにあまりも多くのメソッドを用意してしまうととても面倒なことになります。

リポジトリのインターフェースというと一般的には

・ find
・ findById
・ findByName
・ save (あるいは store)
・ delete

などがあるでしょう。ここに rollback というメソッドがあったら、結構めんどくさいなーという気持ちになりませんか?

リポジトリパターンでは、実際のデータ保存先や保存方法については一切情報を出しません。リスコフの置換原則を思い出しましょう。リポジトリというインターフェースの先にある実装の知識を知っていてはいけないのです。

rollbackができることを前提として設計されたインターフェースならば、rollback機能を実装しないわけにはいきません。システムの動作そのものに多大な影響を与えてしまうからです。

逆に、rollbackが失敗してもいいとうインターフェース設計だった場合には、利用者の方が、rollbackが使えるか使えないか調べた上でrollback機能を使うことになります。

リスコフの置換原則に反せずにこのインターフェースを設計するなら、おそらくは、canRollback のような rollback が可能か問い合わせるメソッドが必要になるでしょう。

利用者は、rollbackが利用可能か問い合わせて、それに併せてロジックを分岐させる必要がでてくるでしょう。

ここまでして rollback 機能を使いたいと思いますか?

repository には rollback のようなメソッドは存在すべきではありません。

やるとすれば RollbacklableRepository とでも名付けましょうか。rollup が出来るリポジトリのみを用意するか、通常のリポジトリと分離するかということになるでしょう。

まとめ

身も蓋もないこというと、ある1つのインターフェースに何もかもぶち込むのはアンチパターンなので、1つのインターフェースには最小限のものだけを定義しておき、別の役割をもったインターフェースを作りましょう。

次回は、SOLID原則の中で最後となる Dependency inversion principle:依存性逆転の原則です。

この記事が気に入ったらサポートをしてみませんか?