iT邦幫忙

2022 iThome 鐵人賽

DAY 20
0
Software Development

Kotlin on the way系列 第 20

Day 20 多用組合 組個提拉米蘇 composition over inheritance

  • 分享至 

  • xImage
  •  

大家都說,對複合的類別要多用組合少用繼承,要組合具體來說是要做什麼呢?
就是把每個類別各自實現了,再把他們放在一起XD

那組合主要有兩種方式,包含以及委託,今天講包含,明天講委託

建構子

組合類別的一種方式就是利用類別的建構子,在 Kotlin,裡面大概會長這樣

class Tiramisu(
    val cocoaPowder: CocoaPowder,
    val filling:Mascarpone,
    val fingerCookie:FingerCookie
) {

}

interface CocoaPowder {
    
}

abstract class Mascarpone {
    
}

abstract class FingerCookie {
    
}

當我們要建立提拉米蘇的物件時,需要可可粉、馬斯卡碰內餡和手指餅乾,如果建構子需要預設的話也能這樣寫

class Tiramisu(
    val cocoaPowder: CocoaPowder = ValrhonaCocoaPowder()
    ...

那當我們要實際使用時,就會長這樣,利用建構子把物件組合起來

Tiramisu(
    filling = LowSugarFilling(),
    fingerCookie = classicFingerCookie()
)

參數

那另一種參數是什麼意思呢?

class Tiramisu{
    private val cocoaPowder: CocoaPowder = NormalCocoaPowder()
    private val filling:Mascarpone = NormalFlling()
    private val fingerCookie:FingerCookie = NormalFingerCookie()
}

以基本語法來說,會長這樣,可以直接看出,這個類別寫死了裡面的實例,也就導致類別沒有彈性,你需要為每個版本的提拉米蘇創建一個專屬類別

改個 di

那當然,我們不滿足於僅僅使用建構子,我們還想要更懶惰做更少事情

這時依賴注入就出來惹,首先將物件建構改寫為這樣
以 Android hilt 示範,

class Tiramisu @Inject constructor(
    val cocoaPowder: CocoaPowder,
    val filling:Mascarpone,
    val fingerCookie:FingerCookie
):Dessert {

}

class LuxuryDessert @Inject constructor(
    val moreFilling:List<Fillable>,
    val dessert: Dessert
):Dessert {
    
}

接著編寫我們的 di 設置,

@Module
@InstallIn(SingletonComponent::class.java)
object DessertModule {
    @Binds
    fun bindCocoaPowder():CocoaPowder {
        return NormalCocoaPowder()
    }
    
    @Binds
    fun bindFingerCookie():FingerCookie {
        return NormalFingerCookie()
    }
    
    @Binds
    fun bindTiramisuFilling:Mascapon {
        return TiramisuFilling(Yolk(), Sugar())
    }

    @Binds
    fun bindTiramisu(
        cocoaPowder: CocoaPowder,
        filling:Mascarpone,
        fingerCookie:FingerCookie
    ):Tiramisu {
        return Tiramisu(
            cocoaPowder, filling, fingerCookie 
        )
    }
    
    @Binds
    fun bindLuxuryTiramisu(tiramisu:Tiramisu):Tiramisu{
        return Luxury(
            listOf(Brandy(), Maple()),
            tiramisu
        )
    }
}

這樣當我們需要建立 Tiramisu 類別時,就不再需要自己建立手指餅乾類別,內餡類別等等

阿不是,這樣怎麼知道拿到哪個版本的提拉米蘇呢?
有這想法的肯定還沒讀過文件,還可以這樣定義呢,先定義 annotatoin

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class LuxuryTiramisu

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class NormalTiramisu

並在提供的方法做上標注

@NormalTiramisu
@Binds
fun bindTiramisu(
    
@LuxuryTiramisu
@Binds
fun bindLuxuryTiramisu(tiramisu:Tiramisu):Tiramisu{
   

並在使用的地方也加上標注

class DessertMenu @Inject constructor (
    @NormalTiramisu tiramisu:Tiramisu
)

對的最基本的依賴注入大概醬,最後附上我最喜歡的提拉米蘇食譜
更動:
咖啡酒 -> 聖塔茵莊園黃波旁,中烘焙,養豆四天,摩卡壺沖煮,和 ciroc 用 2:1 比例混合
內餡 -> 砂糖部分替換成 golden A 等級,另外再加入 30ml 的 1738

其他就一樣惹,要小心加入液體,內餡也會比較濕

English

We all talk about, using composition over inheritance, but what should we do for real?

Basicly, implement each class indenpendly, then put it together

and there are two way to implement composition, contains and delegate, we will discuss contain today, and delegate tomorrow

constructor

one way to use composition is using constructor, in Kotlin it will look like this

class Tiramisu(
    val cocoaPowder: CocoaPowder,
    val filling:Mascarpone,
    val fingerCookie:FingerCookie
) {

}

interface CocoaPowder {
    
}

abstract class Mascarpone {
    
}

abstract class FingerCookie {
    
}

when we want ot create tiramisu object, it needed cocoa powder, mascarpone filling, finger cookie, it you need default value you can do this

class Tiramisu(
    val cocoaPowder: CocoaPowder = ValrhonaCocoaPowder()
    ...

When we need it in pratice, it will look like this, using constructor combine them

Tiramisu(
    filling = LowSugarFilling(),
    fingerCookie = classicFingerCookie()
)

Argument

what is the other argument means?

class Tiramisu{
    private val cocoaPowder: CocoaPowder = NormalCocoaPowder()
    private val filling:Mascarpone = NormalFlling()
    private val fingerCookie:FingerCookie = NormalFingerCookie()
}

you can tell form this code base, it hardcode the instance inside the class, but the class doesn't flexable, you have to create each class for tiramisu

check out di

well, we will not satify with using constructor, we want to be lazy easier

here is where the di show it ability, first we do this, and using Android hilt for demostrate

class Tiramisu @Inject constructor(
    val cocoaPowder: CocoaPowder,
    val filling:Mascarpone,
    val fingerCookie:FingerCookie
):Dessert {

}

class LuxuryDessert @Inject constructor(
    val moreFilling:List<Fillable>,
    val dessert: Dessert
):Dessert {
    
}

and start our di setup

@Module
@InstallIn(SingletonComponent::class.java)
object DessertModule {
    @Binds
    fun bindCocoaPowder():CocoaPowder {
        return NormalCocoaPowder()
    }
    
    @Binds
    fun bindFingerCookie():FingerCookie {
        return NormalFingerCookie()
    }
    
    @Binds
    fun bindTiramisuFilling:Mascapon {
        return TiramisuFilling(Yolk(), Sugar())
    }

    @Binds
    fun bindTiramisu(
        cocoaPowder: CocoaPowder,
        filling:Mascarpone,
        fingerCookie:FingerCookie
    ):Tiramisu {
        return Tiramisu(
            cocoaPowder, filling, fingerCookie 
        )
    }
    
    @Binds
    fun bindLuxuryTiramisu(tiramisu:Tiramisu):Tiramisu{
        return Luxury(
            listOf(Brandy(), Maple()),
            tiramisu
        )
    }
}

When we need to build Tiramisu class, and we don't need to re-declare fingercookie, and filling class ...etc

Then how do we know which version will we get?
If you have this concept, it can define it like this, define annotatoin first

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class LuxuryTiramisu

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class NormalTiramisu

and marked it when we delcare it

@NormalTiramisu
@Binds
fun bindTiramisu(
    
@LuxuryTiramisu
@Binds
fun bindLuxuryTiramisu(tiramisu:Tiramisu):Tiramisu{
   

and marked it when we need it

class DessertMenu @Inject constructor (
    @NormalTiramisu tiramisu:Tiramisu
)

that is the basic of di, in the end of this, attached my favorate tiramisu receipt
change two part :
Coffee liquid -> Brazil Carmo de Minas Fazenda Santa Ines Yellow Bourbon Pulp Natural, medium roast, aged four days, brew in moka pot, and mixup with ciroc vodka in 2:1 percentage

Filling -> replace part of sugar with golden A level maple, and add extra 30 ml of 1738 brandy

Other step are same, just be aware when we add liquid inside, the filling will be flowable


上一篇
Day 19 Solid 能吃嗎 ? 依賴反轉 dependency inversion
下一篇
Day 21 多用組合 委託 Delegate
系列文
Kotlin on the way31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言