Какой предпочтительный способ добавить логику приложения в Presenter? (MVP) - PullRequest
0 голосов
/ 17 июня 2019

Допустим, я хочу использовать MVP (на Android, чтобы быть более детальным). Есть много статей о том, как это сделать. Однако неясно, как добавить в него логику для конкретного приложения?

Пример: я хочу, чтобы пользователь ввел имя хоста и порт и создал api impl глобального сервера приложений, используя эти данные (скажем, экземпляр Retrofit).

Так что у меня есть контракт и база .. интерфейсы:

/**
 * MVP : Presenter (Passive View flavor)
 */
interface Presenter : BasePresenter<View>

/**
 * MVP : View (Passive View flavor)
 */
interface View : BaseView<Presenter> {
    var host: String
    var port: String

    fun showValidationError(error: Exception)
}

interface BasePresenter<ConcreteView> {
    fun onModelChanged()
    fun onViewChanged()

    fun attachView(view: ConcreteView)
    fun detachView()
}

interface BaseView<ConcretePresenter> {
    var presenter: ConcretePresenter?
}

Модель:

/**
 * MVP : Model
 */
class Model(
    _host: String = "",
    _port: UInt = 0U) {

    // we could add presenter notification on both `host` and `port` changed,
    // but we need it to be notified once only when both are set

    var host = _host
        private set

    var port: UInt = _port
        private set

    fun update(host: String, port: UInt) {
        this.host = host
        this.port = port

        presenter?.onModelChanged()
    }

    var presenter: Presenter? = null
}

Presenter:

class PresenterImpl(val model: Model) : Presenter {

    private var view: View? = null

    override fun attachView(view: View) {
        if (this.view != null) {
            detachView()
        }

        this.view = view
        this.view?.presenter = this

        updateView()
    }

    private fun updateView() {
        view?.let {
            it.host = model.host
            it.port = model.port.toString()
        }
    }

    // as Model observer
    override fun onModelChanged() {
        view?.let { updateView() }
    }

    // as View observer

    override fun onViewChanged() {
        view?.let {
            if (!isValidView(it)) {
                return
            }
            model.update(it.host, it.port.toUInt())
        }
    }

    private fun isValidView(view: View): Boolean {
        try {
            if (view.host.isEmpty())
                throw Exception("Host can not be empty")

            if (view.port.isEmpty())
                throw Exception("Port can not be empty")

            if (view.port.toUIntOrNull() == null)
                throw Exception("Port is expected to be positive Number")
        } catch (e : Exception) {
            view.showValidationError(e)
            return false
        }
        return true
    }

    override fun detachView() {
        view?.presenter = null
        view = null
    }
}

Для простоты мы используем Activity as View impl, и все связано друг с другом (модель-презентатор-представление).

Это поток:

  1. Пользователь вводит хост и порт в EditText и нажимает кнопку «ОК».
  2. Просмотр уведомлений Presenter с onViewChanged
  3. Presenter запрашивает данные из View (view.host и view.port), проверяет их и обновляет модель с model.update().
  4. Модель обновляет данные и уведомляет Presenter с помощью onModelChanged()
  5. Теперь здесь Presenter должен выполнять логику для конкретного приложения - создавать экземпляр Retrofit с использованием данных модели.

Шаг № 5 может быть выполнен как продолжение # 3 (а не как слушатель модели), но он ничего существенно не меняет.

Какой лучший способ сделать это?

Варианты:

  1. расширение Presenter Impl и переопределить onModelChanged() (не хочу использовать наследование только для него)
  2. добавить интерфейс слушателя Presenter, сохранить список слушателей и уведомить их об изменении модели в onModelChanged()
  3. ваш ответ

Presenter impl находится в другом модуле Gradle, не зависящем от Android, поэтому я не могу просто добавить этот код для конкретного приложения в onModuleChanged() в PresenterImpl.

...