使用介面來隔離依賴是非常有用的,一種常見的設計模式 proxy pattern
就是以此概念設計的,那使用介面來隔離到底帶給我們什麼好處呢?
在物件導向的設計中,請記住一件事,依賴於抽象,不要依賴於實體,在之前的可讀性文章中,我示範了常見的使用介面的模式
interface ShapeFactory {
}
class ShapeFactoryImpl: ShapeFactory {
}
這種定義法,會被如此使用
fun makeShape(shapeFactory:ShapeFactory){
}
class ColorfulSquareMaker constructor(shapeFactory:ShapeFactory) {
}
...
makeShape(ShapeFactoryImpl())
當類別都依賴於抽象時,就可以輕鬆地替換掉實作那些抽象的實體,所有我們可以將 ShapeFactoryImpl()
替換成其他實體,這項技巧也被廣泛的應用在測試的 Mock 物件上
而當一個類別依賴於介面時,他只能互呼叫介面定義的方法,而對實作不在意,透過既有的 input/ output ,即使有再多的不同實例,也能夠呼叫到方法
在實際的應用中,不同的模組也會利用介面隔離,來確保每個模組只知道最低限度的資訊,同時降低耦合使其可以輕鬆的替換,在每個模組內,也會利用抽象來設計程式,而模組內部的抽象,要更考量功能性和可讀性,不要過度設計了
另一個使用介面隔離優點的設計模式是 decorate pattern
在Solid 能吃嗎?開放封閉 OCP 的文章中,我們看了一段範例
interface Processor {
fun process(res: Request): Response
}
class RequestProcessor : Processor {
override fun process(res: Request): Response {
return Response("result from ${res.endPoint}")
}
}
class LoggerProcessor(
val logger: Logger,
val processor: Processor
) : Processor {
override fun process(res: Request): Response {
logger.printLog(res.endPoint)
return processor.process(res)
}
}
fun main(){
val processor: Processor =
LoggerProcessor(
Logger(),
RequestProcessor()
)
println(processor.process(request))
}
所有的實例都實作了相同的介面,並且依賴於相同的介面,所以我們可以輕鬆的調換順序、組合、拆解,甚至將 proxy
和 decorate
一同使用
Using interfaces to separate dependency is very useful, a common design pattern called proxy is built with this idea, but what does this benefit us?
Remember one things, relay on abstract, not the implementation
, in the article Readability, I demonstrate the common pattern of using interface
interface ShapeFactory {
}
class ShapeFactoryImpl: ShapeFactory {
}
its usage will be like
fun makeShape(shapeFactory:ShapeFactory){
}
class ColorfulSquareMaker constructor(shapeFactory:ShapeFactory) {
}
...
makeShape(ShapeFactoryImpl())
When all the classes rely on abstract, it will be easy to change the implementation, we can replace the ShapeFactoryImpl() to another implementation, and the caller wouldn't notice, this technique is often used for mock classes in tests.
a class relies on one interface, it can only call those methods defined in the interface, if you have a class implementing three different interfaces, it wouldn't cause unnecessary problems.
In the real application, different modules should be segregated by interface, to make sure each module could be easily replaced, inside those modules, the usage of abstract is dependent on its functionality and reliability, don't over design your program.
The other example using the advantage of interface is decorate pattern, in the OCP article, there is a code base look like this
interface Processor {
fun process(res: Request): Response
}
class RequestProcessor : Processor {
override fun process(res: Request): Response {
return Response("result from ${res.endPoint}")
}
}
class LoggerProcessor(
val logger: Logger,
val processor: Processor
) : Processor {
override fun process(res: Request): Response {
logger.printLog(res.endPoint)
return processor.process(res)
}
}
fun main(){
val processor: Processor =
LoggerProcessor(
Logger(),
RequestProcessor()
)
println(processor.process(request))
}
All the instances implement the same interface, and also rely on the same interface, therefore we can switch any order, and easy to compose, tear down.