iT邦幫忙

2022 iThome 鐵人賽

DAY 16
0
Software Development

Kotlin on the way系列 第 16

Day 16 Solid 能吃嗎?開放封閉 OCP

  • 分享至 

  • xImage
  •  

模組應該要對擴展開放,對修改封閉,這是什麼意思?

看看上面的架構圖,在開放封閉原則之中,了解層級間的依賴非常重要,圖中的同心圓,應該由外層依賴內層,而不是內層依賴外層,此設計以箭頭標示在圖的左中,而這件事為什麼重要呢?

先來看看什麼是所謂的內外層吧,所謂外層,可以理解為最接近實作的地方,可以泛指為套件、裝置、代碼實作等等,而越往內層靠,越接近商業邏輯,實作邏輯等等

開放封閉的核心概念在於管理依賴方向,以及隱藏資訊,開放封閉除了用來描述類別耦合,更適用於專案架構,可以將專案切割成模組、元件等,並透過抽象、封裝隱藏資訊,使其在添加新功能時,以很少甚至不需要更改到原本的程式碼和現有架構,而在不同層級間的溝通,應該依賴在抽象,以此確保每層、每個模組、元件的更動不會影響到其他層級、模組

scope of module

共同覆用原則可以讓我們可以定義模組的範圍,多數的類別不會單獨存在,疼過把一同覆用、更動的類別視為同一模組,而在模組之中,我們可以期待和其相關或是依賴的其他類別

當我們設計模組時,要確保裡面的每個類別都是必要的且無法分離的,而當依賴於另一模組時,就會產生出耦合關係,a 模組如果依賴於 b模組,那當 b 模組更動時,往往可能影響到 a 模組,並需要為所有影響做設計更動、編譯、部署等任務,如果套到上面的架構圖,當商業邏輯更動時,會連帶影響到專案裡面的多個地方,而相反的,如果是畫面設計更動,往往只需修改畫面即可

don't rely on things you don't need

ocp between modules

跟業務邏輯相關的類別,內圈的

class LoginUseCase{
    override operator fun invoke(){
        //...
    }
}
class UserFacadeImpl:UserFacade {
     override fun login()
    //...
}
interface UserFacade {
    fun login()
}

跟畫面相關的類別,外圈的

class LoginViewModel(
    val repo:UserFacade
)

由 viewModel 去依賴 facade時,如果 facade 更動,往往伴隨著依賴於他的類別新增或修改,相反的即使 viewModel 更動了,卻不會對 facade 有影響

ocp between class - decorate design pattern

裝飾者模式從設計來看有著很好的開放封閉觀念,此設計模式在於彈性和覆用性,舉個例子

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))
}

可以看到裝飾者都實現了同一介面,而且可以根據需求任意的拼裝組合,用擴展的方式增加功能,而不用更改原始代碼

English

The module should be open to extend, but close to change, what does it mean?

Check this architecture graph. In the principle of open close, understanding the dependency layer is important, just like the circle, the outer layer should rely on the layer inside, not reversed, why does it matter?

the center of the circle is higher layer, and it could represent the business logic, the demand, the core of the application, and each layer outside is further and further from demand to the implementation

The core idea of open/ close is to manage the dependency direction, and hide information, ocp aim more on the project itself, not on a single class or file, to slice system into module, components, to make it capable for extend new feature without modify the current architecture, and keep the minimum to zero change in current code base, the different layer should communicate on abstract method, and shouldn't be impacted since others layer changed.

scope of module

common reuse principle allow us to define scope of module, most classes wouldn't exists independently, all the classes together reuse, modify should belong to the same module, and inside the scope there are other classes relate to same demand

When we design on module, we have to make sure all the classes is necessary and can't be divided, once a module(a) rely on the other one(b, their relational is coupled, when (b) changed, it might affect on module(a), and cause modify, compile, deploy task, now get back to the architecture graph, when business changed, it might affect multiple place in project, on the contract if ui design changed, most of time it only affect itself

don't rely on things you don't need

ocp between modules

class related to logic, inner circle

class LoginUseCase{
    override operator fun invoke(){
        //...
    }
}
class UserFacadeImpl:UserFacade {
     override fun login()
    //...
}
interface UserFacade {
    fun login()
}

ui related class, outer circle

class LoginViewModel(
    val repo:UserFacade
)

if facade changed, if might affect on all the classes rely on it, but when viewModel changed, it won't affect facade

ocp between class - decorate design pattern

decorate pattern has a great concept on ocp, it aims on flexible and reusable

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))
}

As you can see, all the decorate implement same interface, and you can put different class together depend on demand, add feature with extension not change


上一篇
Day 15 Solid 能吃嗎? 單一職責的誤區 Single Responsibility principle
下一篇
Day 17 Solid 能吃嗎? 李氏替換 Liskov and inheritage
系列文
Kotlin on the way31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言