代數(shù)數(shù)據(jù)類型(Algebraic Data Type,ADT)夭苗。用密封類和數(shù)據(jù)類構(gòu)建代數(shù)數(shù)據(jù)類型
3.1 代數(shù)數(shù)據(jù)類型
ADT 其實(shí)是一個(gè)組合類型菩彬。
高中課本我們接觸過代數(shù)和幾何學(xué)色迂。一個(gè)公式a + 2 = c 把這類公式轉(zhuǎn)換成為編程語言中的類型或者值,他們中的某種操作鹉动,會(huì)得到某種新的類型摹闽。
簡單說:代數(shù)或者數(shù)字轉(zhuǎn)換成類型,這種被我們代數(shù)或者數(shù)字轉(zhuǎn)換成的類型
以及通這些類型產(chǎn)生的新的類型根盒,就叫做代數(shù)數(shù)據(jù)類型
(ADT)
每種類型在實(shí)例化的時(shí)候钳幅,都有對應(yīng)的值。Boolean可能有true和false兩種類型的取值炎滞。如果我們將數(shù)字2與取值種類關(guān)聯(lián)就叫做計(jì)數(shù)敢艰,同理Unit 表示只有一個(gè)實(shí)例,那么他的計(jì)數(shù)是1.
ADT 常見的兩種類型: 積類型 和 和類型
積類型
我們可以理解和乘法相似册赛,是一種組合類型
Boolean 和 Unity 組合會(huì)產(chǎn)生兩種類型钠导。表示如下:
class BooleanProductUnit(a:Boolean,b:Unit){}
val b0 = BooleanProductUnit(true,Unit)
val b1 = BooleanProductUnit(false,Unit)
實(shí)際上這是一種product操作震嫉,積類型就是同時(shí)持有某些類型的類型
和類型
相當(dāng)于加法
比如我們 定義enum 類型表示周
enum class Day{SUN,MON,TUE,WEN,THU,FRI,SAT}
每一天只能取一個(gè)值,合起來是7種取值
- 和類型 是類型安全的牡属。 他是一個(gè)閉環(huán)票堵,事先知道可能取值,在使用時(shí)候不用擔(dān)心出現(xiàn)非法情況
- 和類型 是一種OR的關(guān)系逮栅,對比 積類型 是一種 AND 關(guān)系
雖然枚舉類是和類型悴势,但是功能單一,擴(kuò)展性不強(qiáng)措伐。但是密封類
表達(dá)上有更好的表現(xiàn)
使用密封類或者說和表達(dá)式特纤,在使用when表達(dá)式的時(shí)候不用考慮非法的情況,可以省略else侥加。如果遺漏或者多添加了額外情況捧存,編譯器檢測會(huì)幫助報(bào)錯(cuò)
fun schedule(day:Day):String = when(day){
Day.SUN -> "fishing"
Day.MON -> "work"
Day.TUE -> "study"
Day.WEN -> "library"
Day.THU -> "writing"
Day.FRI -> "appointment"
Day.SAT -> "basketball"
}
另一個(gè)小例子,計(jì)算面積:圓形担败,三角形和長方形
sealed class Shape{
class Circle(val radius:Double):Shape()
class Rectangle(val width:Double,val height:Double):Shape()
class Triangle(val base:Double,val height:Double):Shape()
}
fun getArea(shape:Shape):Double = when(shape){
is Shape.Circle -> Math.PI * shape.radius
is Shape.Rectangle -> shape.width * shape.height
is Shape.Triangle -> shape.base * shape.height
}
3.2 模式匹配
我們應(yīng)該很熟悉Java或者JS等都有正則表達(dá)式昔穴。模式匹配和他很相似,而且不只有匹配正則表達(dá)式氢架,還可以匹配其他表達(dá)式傻咖。這里面的表達(dá)式就是模式
一個(gè)數(shù)字,一個(gè)對象實(shí)例岖研,凡是能求出特定值的組合,我們叫做表達(dá)式
class Pattern(val text:String)
val a= 1
val b =2
表達(dá)式:5,a+b,Pattern("hello"),a>b
- 常量模式:之前的Day和when組合的例子
- 類型模式:之前的shape圖像面積計(jì)算Sealed和when結(jié)合
- 邏輯表達(dá)式模式:
fun logicPattern(a:Int)= when (a) {
in 2..11 -> "a in 2 .. 11"
else -> "not in pattern"
}
- 嵌套表達(dá)式模式
定義一個(gè)簡單的整數(shù)表達(dá)式
sealed class Expr {
data class Num(val alue: Int) : Expr()
data class Operate(val optName: String, val left: Expr, val right: Expr) : Expr()
}
Num 表示某個(gè)整數(shù)警检,Operate 是一個(gè)樹形結(jié)構(gòu)孙援,optName操作符+ ,- ,* ,/ 等。用來表示一些復(fù)雜表達(dá)式扇雕。
定義非常簡單拓售,但是具體邏輯實(shí)現(xiàn)起來很繁瑣,無論是java還是镶奉,kotlin
------》 轉(zhuǎn)移一下視線 對比下Scala 因?yàn)樗鼘τ谀J狡ヅ渲С值母么∮伲覀兛聪滤悸贰?br> Scala 語法 模式匹配
// test
sealed trait Expr
case class Num(value:Int) extends Expr
case class Operate(optName:String,left:Expr,right:Expr) extends Expr
def simplifyExpr(expr:Expr) = expr match{
// 0 + x
case Operate ("+",Num(0),x) => x
// x + 0
case Operate ("+",x,Num(0)) => x
case _ => expr
}
正常實(shí)例化一個(gè)對象
val expr = Expr.Operate("+", Expr.Num(0), x)
反向看一下
val ("+", Expr.Num(0), x) = expr
會(huì)想到之前元祖的解構(gòu),kotlin里前兩個(gè)文章中提到的解構(gòu)
所以哨苛,推導(dǎo)下來的結(jié)論就是:
模式匹配中的模式
就是表達(dá)式
鸽凶,模式匹配要匹配的就是表達(dá)式。模式匹配的核心就是解構(gòu)
,就是反向構(gòu)造表達(dá)式建峭。
我們需要用when去寫模式匹配玻侥,還有其他的我們之后在研究。
fun simplefyExpr(expr: Expr):Expr = when (expr) {
is Expr.Num -> expr
is Expr.Operate -> when(expr){
Expr.Operate("+",Expr.Num(0),expr.right) -> expr.right
Expr.Operate("+",expr.left,Expr.Num(0)) -> expr.left
else -> expr
}
}
如果我們要完成這個(gè)表達(dá)式
val express = Expr.Operate("+",Expr.Num(0),Expr.Operate("+",Expr.Num(0),Expr.Num(0)))
先看when用遞歸和非遞歸實(shí)現(xiàn)
fun simplefyExpr2(expr: Expr):Expr = when (expr) {
is Expr.Num -> expr
is Expr.Operate -> when(expr){
Expr.Operate("+",Expr.Num(0),expr.right) -> simplefyExpr2(expr.right)
Expr.Operate("+",expr.left,Expr.Num(0)) -> expr.left
else -> expr
}
}
實(shí)際業(yè)務(wù)沒有這么對稱亿蒸,而且我們指討論“+” 還是有些復(fù)雜的
非遞歸方式
fun simplefyExpr3(expr: Expr): Expr = when (expr) {
is Expr.Num -> expr
is Expr.Operate -> when (expr) {
(expr.left is Expr.Num && expr.left == 0) && (expr.right is Expr.Operate)
-> when (expr.right) {
Expr.Operate("+", expr.right.left, Expr.Num(0))
-> expr.right.left
else -> expr.right
}
else -> expr
}
}
增強(qiáng)模式匹配
實(shí)現(xiàn)模式匹配的技術(shù)
類型測試或類型轉(zhuǎn)換凑兰,面向?qū)ο蟮姆纸庹谱L問者設(shè)計(jì)模式,TypeCase姑食,樣本類波岛,抽取器。
前三種目前還有點(diǎn)搞頭音半,后面三個(gè)不是考慮的范疇了
類型測試或類型轉(zhuǎn)換---> 前面的Expr 的例子
面向?qū)ο蟮姆纸?/p>
sealed class Expr2 {
abstract fun isZero(): Boolean
abstract fun isAddZero(): Boolean
abstract fun left(): Expr2
abstract fun right(): Expr2
data class Num(val value: Int) : Expr2() {
override fun isZero(): kotlin.Boolean = this.value == 0
override fun isAddZero(): Boolean = false
override fun left(): Expr2 = throw Throwable("no element")
override fun right(): Expr2 = throw Throwable("no element")
}
data class Operate(val optName: String, val left: Expr2, val right: Expr2) : Expr2() {
override fun isZero(): kotlin.Boolean = false
override fun isAddZero(): Boolean = this.optName ==
"+" && (this.left.isZero() || this.right.isZero())
override fun left(): Expr2 = this.left
override fun right(): Expr2 = this.right
}
}
fun simplefyExpr3(expr: Expr2): Expr2 = when {
expr.isAddZero() && expr.right().isAddZero() && expr.right().left().isZero()
-> expr.right().right()
else -> expr
}
實(shí)際業(yè)務(wù)很復(fù)雜则拷,如果我們需要將類的方法,在每個(gè)子類再重新實(shí)現(xiàn)一遍祟剔。代價(jià)很高隔躲。比較簡單的 業(yè)務(wù)還湊合
訪問者模式
改造上面的實(shí)現(xiàn),增加一個(gè)Visitor類物延,實(shí)現(xiàn)訪問者模式
sealed class Expr3 {
abstract fun isZero(v: Visitor): Boolean
abstract fun isAddZero(v: Visitor): Boolean
abstract fun simplefyExpr(v: Visitor): Expr3
data class Num(val value: Int) : Expr3() {
override fun isZero(v: Visitor): kotlin.Boolean = v.matchZero(this)
override fun isAddZero(v: Visitor): Boolean = v.matchAddZero(this)
override fun simplefyExpr(v: Visitor): Expr3 =v.doSimplefyExpr(this)
}
data class Operate(val optName: String, val left: Expr3, val right: Expr3) : Expr3() {
override fun isZero(v:Visitor): kotlin.Boolean = v.matchZero(this)
override fun isAddZero(v:Visitor): Boolean = v.matchAddZero(this)
override fun simplefyExpr(v: Visitor): Expr3 = v.doSimplefyExpr(this,v)
}
}
class Visitor {
fun matchAddZero(expr: Expr3.Num): Boolean = false
fun matchAddZero(expr: Expr3.Operate): Boolean = when (expr) {
Expr3.Operate("+", Expr3.Num(0), expr.right)
-> true
Expr3.Operate("+", expr.left, Expr3.Num(0))
-> false
else -> false
}
fun matchZero(expr:Expr3.Num):Boolean = expr.value == 0
fun matchZero(expr:Expr3.Operate):Boolean = false
fun doSimplefyExpr(expr:Expr3.Num):Expr3= expr
fun doSimplefyExpr(expr:Expr3.Operate,v:Visitor):Expr3= when{
(expr.right is Expr3.Num && v.matchZero(expr) && v.matchAddZero(expr.right))
&& (expr.right is Expr3.Operate && expr.right.left is Expr3.Num)
&& v.matchAddZero(expr.right.left)
-> expr.right.left
else -> expr
}
}
采用訪問者設(shè)計(jì)模式
1.我們類中的設(shè)計(jì)方法宣旱,方法放到了類外實(shí)現(xiàn),讓類機(jī)構(gòu)更清晰
2.子類特別多并且結(jié)構(gòu)復(fù)雜 時(shí)候叛薯,訪問者可以減少我們寫很多判斷邏輯浑吟,特定子類進(jìn)行相關(guān)操作,會(huì)方便一些
3.缺點(diǎn)耗溜,新增加子類组力,在訪問類中增加這個(gè)子類的操作,而且一些測試方法可能要重寫抖拴,所以Visitor模式也要慎用
訪問者設(shè)計(jì)及模式+when結(jié)合----》{數(shù)據(jù)結(jié)構(gòu)在后期不會(huì)變動(dòng)太大燎字,業(yè)務(wù)邏輯相對復(fù)雜的情況}