簡體   English   中英

如何使用 Activity 或 Fragment 中的視圖重構復雜的方法?

[英]How can I refactor complicated method with views in Activity or Fragment?

我正在學習清潔代碼和重構。 而且我的項目有很多長的視圖訪問方法,像這樣。

    private fun updateStatusFragmentUI(statusData: StatusListData.StatusData) =
        with(binding.detail) {
            with(statusData) {
                tvCodeDoc.text = getString(R.string.reception_code).format(receptionKey)
                tvTime.text = createdAt.toFullTimeString()
                tvName.text = name
                tvContact.text = phoneNumber
                tvAddress.text = address
                tvAddressOld.text = addressOld
                btnSubAction.isVisible = false
                contentsRead.root.isVisible = false
                contentsWrite.root.isVisible = false
                footerAction.root.isVisible = false
                footerInformation.root.isVisible = false
                when (detailStatus) {
                    ReceptionUiType.PRICE_REQUEST -> {
                        btnSubAction.setText(R.string.reception_cancel)
                        btnSubAction.isVisible = viewModel.isCancelButtonVisible
                        btnSubAction.setOnClickListener {
                            showDialogReceptionCancel(false)
                        }
                        setWriteContents(this)
                        setActionFooter(R.string.all_send_price)
                    }
                    ReceptionUiType.PAYMENT_WAIT -> {
                        btnSubAction.setText(R.string.reception_cancel)
                        btnSubAction.isVisible = viewModel.isCancelButtonVisible
                        btnSubAction.setOnClickListener {
                            showDialogReceptionCancel(false)
                        }
                        setReadContents(this)
                        contentsRead.clDuration.isVisible = false
                        setInformationFooter(R.string.include_detail_info_payment_method_title,
                            R.string.include_detail_info_payment_method_sub_title
                        )
                    }
                    ReceptionUiType.DISPENSING_WAIT -> {
                        btnSubAction.setText(R.string.dialog_dispensing_cancel_action)
                        btnSubAction.isVisible = false //notice 조제 대기 상태에서는 취소를 표시하지않음!
                        btnSubAction.setOnClickListener {
                            showDialogReceptionCancel(true)
                        }
                        setReadContents(this)
                        contentsRead.clDuration.isVisible = false

                        setInformationFooter(
                            titleRes = R.string.include_detail_info_dispensing_wait_title,
                            isOnConfirm = false)
                        setActionFooter(
                            R.string.include_detail_action_dispensing_wait,
                            isDispensingStart = true
                        )
                    }
                    ReceptionUiType.IN_DISPENSING -> {
                        btnSubAction.setText(R.string.dialog_dispensing_cancel_action)
                        btnSubAction.setOnClickListener {
                            showDialogDispensingCancel()
                        }
                        setReadContents(this)
                        setInformationFooter(R.string.include_detail_info_in_dispensing_title, R.string.include_detail_info_in_dispensing_sub_title,
                            isOnConfirm = false)
                        setActionFooter(R.string.include_detail_action_in_dispensing)
                    }
                    ReceptionUiType.DELIVERY_WAIT -> {
                        setReadContents(this)
                        // val subTitle = if (deliveryMethod == DeliveryMethodType.PARCEL) null else R.string.include_detail_info_delivery_wait_sub_title
                        setInformationFooter(R.string.include_detail_info_delivery_wait_title,
                            isOnConfirm = false)
                        setActionFooter(
                            R.string.include_detail_action_delivery_wait,
                            isDeliveryPayment = deliveryMethod == DeliveryMethodType.PARCEL
                        )
                    }
                    ReceptionUiType.DELIVERY_PAYMENT_WAIT -> {
                        setReadContents(this)
                        setInformationFooter(R.string.include_detail_info_waiting_payment_for_delivery_title,
                            R.string.include_detail_info_waiting_payment_for_delivery_sub_title,
                            isWaiting = true
                        )
                    }
                    ReceptionUiType.DELIVERY_PAYMENT_WAIT_FAIL -> {
                        setReadContents(this)
                        contentsRead.clDuration.isVisible = false

                        setInformationFooter(title = getString(R.string.include_detail_info_delivery_payment_wait_fail_title),
                            sub = getString(R.string.include_detail_info_delivery_payment_wait_fail_sub_title),
                            additionalInfo = getString(R.string.include_detail_info_delivery_payment_wait_time_out),
                            isOnConfirm = false
                        )

                    }
                    ReceptionUiType.DELIVERY_RECEPTION_WAIT -> {
                        setReadContents(this)
                        setInformationFooter(R.string.include_detail_info_delivery_reception_wait_quick_title,
                            R.string.all_please_wait,
                            isOnConfirm = false,
                            isWaiting = true
                        )
                    }
                    ReceptionUiType.DELIVERY_RECEPTION_REQUEST_PARCEL -> {
                        setReadContents(this)
                        setActionFooter(R.string.include_detail_info_delivery_reservation_wait_parcel_reception_completed,
                            isParcelReady = true
                        )
                    }
//                    ReceptionUiType.PICKUP_WAIT -> {
//                        setReadContents()
//                        setActionFooter(R.string.include_detail_action_pickup_wait, false, R.string.include_detail_action_pickup_wait_title, R.string.include_detail_action_pickup_wait_sub_title)
//                    }
                    ReceptionUiType.DELIVERY_RECEPTION_COMPLETED -> {
                        setReadContents(this)
                        val dispensingCompleteAt =
                            this.endAt.toFullTimeString().takeIf { it.isNotEmpty() }
                        val deliveryCompletedAt =
                            this.deliveryAt.toFullTimeString().takeIf { it.isNotEmpty() }
                        val sub =
                            if (dispensingCompleteAt != null && deliveryCompletedAt != null) getString(
                                R.string.include_detail_info_delivery_completed_sub_title).format(
                                dispensingCompleteAt,
                                deliveryCompletedAt) else null
                        setInformationFooter(getString(R.string.include_detail_info_delivery_completed_title),
                            sub)
                    }
                    ReceptionUiType.DELIVERY_COMPLETED_IN_PERSON -> {
                        setReadContents(this)
                        val dispensingCompleteAt =
                            this.endAt.toFullTimeString().takeIf { it.isNotEmpty() }
                        val deliveryCompletedAt =
                            this.deliveryAt.toFullTimeString().takeIf { it.isNotEmpty() }
                        val sub =
                            if (dispensingCompleteAt != null && deliveryCompletedAt != null) getString(
                                R.string.include_detail_info_delivery_completed_in_person_sub_title).format(
                                dispensingCompleteAt,
                                deliveryCompletedAt) else null
                        setInformationFooter(getString(R.string.include_detail_info_delivery_completed_in_person_title),
                            sub)
                    }
                    ReceptionUiType.DELIVERY_RECEPTION_COMPLETED_PARCEL -> {
                        setReadContents(this)
                        val dispensingCompleteAt =
                            this.endAt.toFullTimeString().takeIf { it.isNotEmpty() }
                        val deliveryCompletedAt =
                            this.deliveryAt.toFullTimeString().takeIf { it.isNotEmpty() }
                        val sub =
                            if (dispensingCompleteAt != null && deliveryCompletedAt != null) getString(
                                R.string.include_detail_info_delivery_completed_parcel_sub_title).format(
                                dispensingCompleteAt,
                                deliveryCompletedAt) else null
                        setInformationFooter(getString(R.string.include_detail_info_delivery_completed_parcel_title),
                            sub)
                    }
//                    ReceptionUiType.PICKUP_COMPLETE -> {
//                        setReadContents()
//                        setInformationFooter(R.string.include_detail_info_pickup_complete_title)
//                    }
                    ReceptionUiType.RECEPTION_CANCEL -> {
                        setReadContents(this)
                        contentsRead.clDuration.isVisible = false

                        val canceledAt =
                            this.canceledAt.toFullTimeString().takeIf { it.isNotEmpty() }
                        setInformationFooter(title = getString(R.string.include_detail_info_purchase_cancel_title),
                            sub = String.format(getString(R.string.include_detail_info_purchase_cancel_sub_title),
                                failReason),
                            additionalInfo = String.format(getString(R.string.include_detail_info_reception_cancel_sub_title),
                                canceledAt),
                            isOnConfirm = false
                        )
                    }
                    ReceptionUiType.RECEPTION_CUSTOMER_CANCEL -> {
                        setReadContents(this)
                        contentsRead.clDuration.isVisible = false

                        val canceledAt =
                            this.canceledAt.toFullTimeString().takeIf { it.isNotEmpty() }
                        setInformationFooter(title = getString(R.string.include_detail_info_customer_cancel_title),
                            sub = "고객 취소 $canceledAt",
                            isOnConfirm = false
                        )
                    }
                    ReceptionUiType.RECEPTION_PAYMENT_TIME_OUT -> {
                        setReadContents(this)
                        contentsRead.clDuration.isVisible = false
                        val canceledAt =
                            this.canceledAt.toFullTimeString().takeIf { it.isNotEmpty() }
                        setInformationFooter(title = getString(R.string.include_detail_info_reception_payment_time_out_title),
                            sub = getString(R.string.include_detail_info_reception_payment_time_out_sub_title).format(
                                canceledAt),
                            isOnConfirm = false
                        )
                    }
                    ReceptionUiType.DELIVERY_CANCEL -> {
                        setReadContents(this)
                        val canceledAt =
                            this.canceledAt.toFullTimeString().takeIf { it.isNotEmpty() }
                        setInformationFooter(getString(R.string.include_detail_info_delivery_cancel_title),
                            sub = "배송 취소  $canceledAt",
                            isOnConfirm = false
                        )
                    }
                    ReceptionUiType.DISPENSING_CANCEL -> {
                        setReadContents(this)
                        val canceledAt =
                            this.canceledAt.toFullTimeString().takeIf { it.isNotEmpty() }
                        setInformationFooter(title = getString(R.string.include_detail_info_dispensing_cancel_title),
                            sub = "조제 취소  $canceledAt",
                            isOnConfirm = false
                        )
                    }
                    ReceptionUiType.CANCEL -> {
                        setReadContents(this)
                        val sub = failReason.takeIf { it.isNotEmpty() }?.let { reason ->
                            this.canceledAt.toFullTimeString().takeIf { it.isNotEmpty() }?.let {
                                "$reason\n$it"
                            } ?: reason
                        }
                        setInformationFooter(getString(R.string.include_detail_info_cancel_title),
                            sub,
                            isOnConfirm = false
                        )
                    }
                    ReceptionUiType.PAYMENT_REFUND -> {
                        setReadContents(this)
                        setInformationFooter(
                            title = getString(R.string.include_detail_info_refunding_title),
                            sub = getString(R.string.include_detail_info_refunding_sub_title),
                            additionalInfo = getString(R.string.include_detail_info_delivery_payment_wait_time_out),
                            isOnConfirm = false
                        )
                    }
                    else -> {
                        // no-op
                    }
                }
            }

        }

我學會了 用多態性替換條件,但我不知道如何應用這種技術。 我該如何解決這個問題,如果可以創建類,我該如何命名它們? 我在想 DetailViewManager、StatusUiManager 等。但這似乎不對。

(代碼示例很長,但我希望它能給你一些想法!)

您的鏈接中的多態方法基本上就是您現在所擁有的,只需將每個案例的代碼放入 class 中的 function 中。 所以代替這個:

when (detailStatus) {
    ReceptionUiType.PRICE_REQUEST -> {
        btnSubAction.setText(R.string.reception_cancel)
        btnSubAction.isVisible = viewModel.isCancelButtonVisible
        btnSubAction.setOnClickListener {
            showDialogReceptionCancel(false)
        }
        setWriteContents(this)
        setActionFooter(R.string.all_send_price)
    }
    // etc

你會有這樣的事情:

sealed class ReceptionUiType {

    // we need to pass something in here - I'll talk about it below!
    abstract fun doStuff(activity: MyActivity)

    class PriceRequest : ReceptionUiType() {
        override fun doStuff(activity: MyActivity) {
            with(activity) {
                btnSubAction.setText(R.string.reception_cancel)
                btnSubAction.isVisible = viewModel.isCancelButtonVisible
                btnSubAction.setOnClickListener {
                    showDialogReceptionCancel(false)
                }
                setWriteContents(this)
                setActionFooter(R.string.all_send_price)
            }
        }
    }

    // add the classes for the other types
}

然后你可以這樣做:

detailStatus.doThing(activity)

無論ReceptionUiType detailStatus是哪個子類,它都有一個doThing方法來執行特定於該類型的操作。 因此,不是檢查detailStatus是哪種類型,並根據結果采取不同的行動,而是將代碼移動到類型本身,並說“不管你是什么,做你的事”。


但考慮到代碼與您的ActivityFragment的緊密聯系,我認為這在這里不太合適。 我在示例中傳遞了Activity ,因為代碼需要調用它的方法並戳它的 UI(我不知道它實際在哪里運行,這只是一個示例。)。 因此,該代碼不是一個簡潔的關注點分離 - 所有這些 UI 實現細節都溢出到您的ReceptionUiType類中!

相反,您可能想嘗試將數據保存在您的ReceptionUiType類中,並讓您的when語句處理對該數據執行的操作,即所有方法調用、UI 設置等。這不是您的UiType類應該知道的任何事情,正確的? 他們只需要表示一些數據,而其他組件使用它 - 理想情況下沒有太多重復!


所以我認為一個好的第一種方法是解決每種情況下代碼塊中的重復問題。 他們都有什么共同點? 他們中的一些人有什么共同點? 如果你可以開始分解事情,你可以開始將事情分組,然后將它們作為一個組來處理

一種方法(在這里不起作用,但作為一般示例)是創建一個 class (或data class ),它具有您可能需要處理的每一位數據的屬性:

data class ReceptionUiType(
    // these are nullable, so you can have "no data" for a particular property
    val buttonTextResId: Int?,
    val buttonVisible: Boolean?,
    // etc
)

然后,您可以根據包含的數據位來設置內容,而不是處理單個類型:

// or ?.let { btnSubAction.setText(it) } - I think this is neater and clearer though!
detailStatus.buttonTextResId?.run(btnSubAction::setText)
detailStatus.buttonVisible?.run(btnSubAction::isVisible)

這樣,您不必為每種類型定義一個案例並明確列出每次發生的事情(這有時很好而且很有幫助),您只需處理每條數據 - 如果您有的話。 用它做相關的事情,如果你不這樣做,跳到下一件事!


但是您的代碼比這更復雜,可能會發生很多不同的事情,以及每個事情的數據。 所以你最終可能會得到一個單一的數據 class ,它涵蓋了太多太多不同的不相關的東西,然后將它們分成更具體的類以用於幾個不同的用例是有意義的。

查看您的代碼,感覺每個案例都可以分為幾組:

  • 影響按鈕的東西/顯示取消對話框
  • 與交貨完成有關的東西
  • 與取消有關的東西

並且這些影響諸如是否處理取消時間,是否處理交貨時間等。並非每個組都存在的行為,但該組中的情況很常見。

還有一些適用於大多數情況(但不是全部)的東西,比如信息頁腳


因此,您可以嘗試兩種方法 - 構建一個class 層次結構,該層次結構創建具有共享屬性/行為的組,或者使用組合來定義每一位行為,並根據需要將它們添加到每種類型。

這是層次結構的外觀:

sealed class ReceptionUiType {

    // common to (mostly) everything
    abstract val informationFooterTitleId: Int?

    sealed class ButtonToucher: ReceptionUiType() {

        // common to the ButtonToucher group
        abstract val buttonTextId: Int        

        class PriceRequest : ButtonToucher() {
            override val buttonTextId: Int = R.string.reception_cancel
            override val informationFooterTitleId: Int? = null
        }

    }
}

然后您的處理程序代碼可以執行以下操作:

// general stuff
detailsStatus.informationFooterTitleId?.let {
    setInformationFooter(titleRes = it)
}

// group-specific stuff
if (detailStatus is ReceptionUiType.ButtonToucher) {
    // set up the button using the specific properties on this subtype
    btnSubAction.setText(detailStatus.buttonTextId)
    // etc
}

當您擁有整潔的組時,這種事情可以起作用,其中有些事情肯定是另一種事情的更具體類型,例如inheritance 一個 class 只能有一個父超類,有時這足以描述共享行為和屬性的方式。

但有時它更混亂,一個類型可以共享另一個 class 的一些行為和屬性,也可以共享另一個class 的一些行為和屬性,並且那里沒有明顯的層次結構。 因此,使用接口定義事物並將它們組合起來構成類的行為和屬性更有意義。

我認為這是您應該采用 go 的方式,因為其中一些案例在某些方面是相互關聯的,但在其他方面則不然。 所以是這樣的:

interface ButtonToucher {
    val buttonTextId: Int
    // this could be a nullable Boolean? but I wanted to show that you can
    // define your data like this, with things like enums to represent states
    val buttonVisibility: ButtonVisibility
    val showCancelDialog: Boolean
    
    enum class ButtonVisibility { VISIBLE, INVISIBLE, FOLLOW_VIEWMODEL }
}

interface SetsAction {
    val actionTextId: Int
}

interface SetsInformation {
    val informationTextId: Int
    // other properties - you have a lot! make them nullable and null here by default
    val isOnConfirm: Boolean? = null
}

// once you have this setup, you can define your types by combining the interfaces they use
sealed class ReceptionUiType {
    PriceRequest : ReceptionUiType(), ButtonToucher, SetsAction {
        override val buttonTextId = R.string.reception_cancel
    }
}

// and interact with them as those types, checking for each type and doing the necessary stuff:
if (detailsStatus is ButtonToucher) {
    // set up the button
}

一種類似的方法是將其與上面的數據 class 想法相結合 - 但不是讓一個數據 class 具有許多屬性,其中一些不會與其他屬性結合使用,您可以創建一個包含其他數據結構的結構(我更喜歡這個而不是我認為的接口):

data class ButtonConfig(
    val buttonTextId: Int,
    val visibility: ButtonVisibility?,
    val showCancelDialog
)

data class InformationFooterConfig(
    // all the properties you might need
)

// then define a class that may or may not contain these objects
sealed class ReceptionUiType(
    val buttonConfig: ButtonConfig? = null,
    val informationFooterConfig: InformationFooterConfig? = null,
    // etc
) {
    class PriceRequest : ReceptionUiType(
        buttonConfig = ButtonConfig(
            R.string.reception_cancel,
            ButtonConfig.ButtonVisibility.FOLLOW_VIEWMODEL,
            showCancelDialog = false
        )
        informationFooterConfig = InformationFooterConfig(
            // define these details
        )
    )
}

然后您可以檢查這些配置對象:

detailsStatus.buttonConfig?.let { config ->
    // set up the button since we have a config for it
}

// You can still check types if it makes sense for certain things
// For instance, setReadContents seems to happen every time EXCEPT in one case
if (detailsStatus is ReceptionUiType.PriceRequest) setWriteContents(this)
else setReadContents(this)

這種方法的好處是您可以創建一個ButtonConfig實例並重用它,就像PRICE_REQUESTPAYMENT_WAIT似乎以相同的方式處理按鈕,因此您可以執行val paymentButtonConfig = // that button config up there並將其傳遞給您的構造函數,因此您只需要定義一次(並且可以輕松地進行更改,因為您不會重復自己)


我希望這是有道理的,你的例子似乎相當復雜,需要分解和組織,看看你可以組合什么東西,什么東西需要應用到所有東西上。 等等所以我只是想給你一個關於如何處理它的概述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM