目次:
インターフェース分離の原則(ISP) ってドユコト?
説明しよう。ISP(Interface segregation principle)とは。
インターフェース分離原則(ISP)とは、ソフトウェア設計におけるSOLIDの原則で、クライアントが使用しないインターフェースに依存することを強いられるべきでないとするものである。
つまり、あるクラスがそのインターフェースのメソッドのサブセットを使う必要があるだけなら、インターフェースは複数の小さな、より特化したインターフェースに分割されるべきである。
ソフトウェアの複雑性によく効くの?
YES。
- ISPの効果でクライアントクラスが実装しなければならない不必要なメソッドの量を減らすことができるよ。
- システムの複雑さを軽減することができるよ
- クラスがインターフェイスのごく一部だけを実装すればよい場合、そのクラスは理解しやすく、保守しやすくなるよ。
テストのし易さによく効くの?
YES。
- ISPはクラスのテストを書きやすくすることができるよ。
- 大きなインターフェースをより小さく、より専門的なインターフェースに分解することで、クラスが実装する特定の機能に焦点を当てたテストを行うことができるよ。
- 結果的にクラスのテストを書くことが容易になるよ。
ソフトウェアを変更しやすくなるの?
YES。
- ISPはシステムの改変を容易にすることができるよ。
- 大きなインターフェースを小さなインターフェースに分解することで、システムに対する変更をより漸進的に、より少ないリスクで行うことができるよ。
- なぜなら、インターフェースへの変更は、システム全体ではなく、そのインターフェースに依存するクラスのみに影響するからだよ。
- その結果、システムの改変が容易になり、意図しない結果を引き起こすことなく変更を加えることができるようになるよ。
コードはドウダ?
それぞれの言語の「やり方」と「コードの説明」を見ると面白いことがわかる。
つまり各言語の抽象クラスのサポート状況は違うが同じことができるということだ。
インターフェース分離の原則(ISP)をJavaで書くと?
Javaのやり方
- Javaではインターフェースがあり、クラスは1つ以上のインターフェイスを実装することができる。
- 大きなインターフェースが定義されている場合、ISPに準拠するために、より小さな、より特化した複数のインターフェースに分割することができます。
- 例えば、あるクラスがインターフェースで定義されたメソッドのサブセットだけを実装する必要がある場合、必要なメソッドだけを含むより小さなインターフェースを実装することができます。
コードの説明
この例では、Document インターフェースは文書(TextDocument)が持つべき全てのメソッドを定義していますが、クラスがその全てを実装することは難しく、特にメソッドのサブセットを実装するだけで良い場合は、そのようなメソッドを実装することは困難です。
ISPに準拠するために、Documentインターフェースは2つの小さな、より特化したインターフェースに分割されています。DocumentReadOnlyとDocumentWriteOnlyです。
TextDocument クラスは、必要な機能をすべて提供するために、 Document インターフェースだけでなく、この両方のインターフェースを実装しています。
コード: Java
// 大きなインターフェース interface Document { void open(); void close(); void save(); } // 小さなインターフェースに分割 interface DocumentReadOnly { void open(); void close(); } // 小さなインターフェースに分割 interface DocumentWriteOnly { void save(); } class TextDocument implements Document, DocumentReadOnly, DocumentWriteOnly { public void open() { // Implementation } public void close() { // Implementation } public void save() { // Implementation } }
インターフェース分離の原則(ISP)をGoで書くと?
Goのやり方
- Javaのような明示的なインターフェースのサポートはないが、Goの型システムを使ってISPを適用することが可能である。
- 例えば、特定のユースケースに必要なメソッドだけを含む型を定義し、この型を大きなインターフェースの代わりに使用する方法である。
コードの説明
この例では、Document インターフェースは文書が持つべき全てのメソッドを定義するが、その全てを実装することは難しく、特にメソッドのサブセットを実装する必要がある場合、そのような型はない。
ISPに準拠するためには、Documentインターフェイスは2つの小さな、より特化したインターフェイスに分割される。DocumentReadOnlyとDocumentWriteOnly。
TextDocument 型は、そのすべてのメソッドを実装することで、Document インターフェースを実装する。
コード: Go
// 大きなインターフェース type Document interface { Open() Close() Save() } // 小さなインターフェースに分割 type DocumentReadOnly interface { Open() Close() } // 小さなインターフェースに分割 type DocumentWriteOnly interface { Save() } type TextDocument struct{} func (d TextDocument) Open() { // Implementation } func (d TextDocument) Close() { // Implementation } func (d TextDocument) Save() { // Implementation }
インターフェース分離の原則(ISP)をClojureで書くと?
Clojureのやり方
- プロトコルを用いてISPを適用することができる。Clojureにおけるプロトコルは、インターフェースを定義するための方法である。
- JavaやGoと同様に、プロトコルが大きすぎる場合、複数の小さなプロトコルに分割してISPを遵守することができる。
コードの説明
この例では、Documentプロトコルは文書が持つべきすべてのメソッドを定義していますが、レコードがそのすべてを実装することは難しく、特にメソッドのサブセットだけを実装する必要がある場合は、困難な場合がある。
ISPに準拠するために、Documentプロトコルは2つの小さな、より特化したプロトコルに分割される。DocumentReadOnlyとDocumentWriteOnlyである。
TextDocumentレコードは、extend-protocolを使用してDocumentプロトコルを実装している。
コード: Clojure
(defprotocol Document (open [this]) (close [this]) (save [this])) (defprotocol DocumentReadOnly (open [this]) (close [this])) (defprotocol DocumentWriteOnly (save [this])) (defrecord TextDocument []) (extend-protocol Document TextDocument (open [this] (println "TextDocument opened.")) (close [this] (println "TextDocument closed.")) (save [this] (println "TextDocument saved.")))
インターフェース分離の原則(ISP)をPythonで書くと?
Pythonのやり方
- 抽象基底クラス(abstractmethodの使用)を定義することでISPを適用することができる。
- 抽象基底クラスとは、関連するクラス群のインターフェースを定義する基底クラスであるが、そのメソッドの実装は提供しない。
- 基底クラスが大きすぎる場合は、複数の小さな基底クラスに分割してISPを遵守させることができる。
コードの説明
この例では、Document抽象基底クラスは、文書が持つべきすべてのメソッドを定義していますが、クラスがそのすべてを実装することは困難であり、特にメソッドのサブセットだけを実装する必要がある場合は、困難である可能性がある。
ISPに準拠するために、Document抽象基底クラスは、より小さく、より特化した2つの抽象基底クラスに分割される。DocumentReadOnly と DocumentWriteOnly である。
TextDocument クラスは、Document 抽象基底クラスと同様に、これらの抽象基底クラスの両方を実装し、必要な機能をすべて提供する。
コード: Python
from abc import ABC, abstractmethod class Document(ABC): @abstractmethod def open(self): pass @abstractmethod def close(self): pass @abstractmethod def save(self): pass class DocumentReadOnly(ABC): @abstractmethod def open(self): pass @abstractmethod def close(self): pass class DocumentWriteOnly(ABC): @abstractmethod def save(self): pass class TextDocument(Document, DocumentReadOnly, DocumentWriteOnly): def open(self): print("TextDocument opened.") def close(self): print("TextDocument closed.") def save(self): print("TextDocument saved.")
むすび
いずれの言語でも、ISPを遵守することで、システムの複雑さを軽減し、修正や保守を容易にすることができます。
大きなインターフェースをより小さく専門的なものに分解することで、クラスの理解やテストが容易になり、システムの変更もより漸進的にリスクを抑えて行うことができるようになります。
コメント