iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 29
0
Software Development

Functional Programming in Kotlin系列 第 29

Functional Programming in Practice - Part 3

接下來處理另一個狀態, LoginFormState ,他也是一個擁有眾多不可能狀態的 Product Type :

data class LoginFormState(val usernameError: Int? = null,
                          val passwordError: Int? = null,
                          val isDataValid: Boolean = false)

仔細分析後發現,如果 usernameError 或是 passwordError 不是 null 的話,isDataValid 就會是 false ,usernameError 跟 passwordError 都是 null 的話,isDataValid 就會是 true。其他任何組合都會是奇怪、不應該存在的狀態,這樣實在是太多雜訊了,在閱讀、理解上會浪費很多時間,犯錯的可能性也很高,但其實,他也可以改成簡單的 Sum Type:

enum class LoginUiState {
     Valid,
     UserNameError,
     PasswordError 
}

改過之後,上面的 enum 就非常容易理解,這三個狀態都是互斥的,不會有其他不可能的狀態發生,接下來的修改就只剩 LoginViewModelLoginActivity 了:

class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() {

    val loginState: MutableLiveData<LoginUiState> = MutableLiveData<LoginUiState>()

    fun loginDataChanged(username: String, password: String) {
        if (!isUserNameValid(username)) {
            loginState.postValue(LoginUiState.UserNameError)
        } else if (!isPasswordValid(password)) {
            loginState.postValue(LoginUiState.PasswordError)
        } else {
            loginState.postValue(LoginUiState.Valid)
        }
    }
}

class LoginActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...

        loginViewModel.loginState.observe(this@LoginActivity, Observer {
            val loginState = it ?: return@Observer

            when(loginState) {
                LoginUiState.Valid -> login.isEnabled = true
                LoginUiState.UserNameError -> {
                    login.isEnabled = false
                    username.error = getString(R.string.invalid_username)
                }
                LoginUiState.PasswordError -> {
                    login.isEnabled = false
                    password.error = getString(R.string.invalid_password)
                }
            }
        })
   }
}

Domain

剛剛已經看過 Repository 跟 ViewModel 了,但是其實嚴格來說,這些都不是非常“純”,還是有 side effect ,Repository 有登入狀態,ViewModel 有 LiveData 的狀態。那我們的 pure functional 程式在哪裡呢?其實以目前的需求來說,只要 Domain 是純 functional 就足夠了,而這個 Domain 呢,在這個專案中其實只有兩個 function ,就是 isUserNameValidisPasswordValid

// A placeholder username validation check
val isUserNameValid = { username: String ->
    if (username.contains('@')) {
        Patterns.EMAIL_ADDRESS.matcher(username).matches()
    } else {
        username.isNotBlank() && !username.contains(" ")
    }
}

// A placeholder password validation check
val isPasswordValid = { password: String ->
    password.length > 5
}

小結

這幾篇稍微修改了官方所提供的範例程式碼,運用了之前所說的概念,將一些 error prone 、相對難閱讀的程式碼改成表達能力比較好的程式碼了,還有,functional programming 是可以跟 object oriented programming 是可以並存的,不需要全部的專案程式碼都不能有 side effect 。

最後附上今天的 Repo:


上一篇
Functional Programming in Practice - Part 2
下一篇
完賽心得
系列文
Functional Programming in Kotlin30

尚未有邦友留言

立即登入留言