Допустим, я хочу использовать 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, и все связано друг с другом (модель-презентатор-представление).
Это поток:
- Пользователь вводит хост и порт в EditText и нажимает кнопку «ОК».
- Просмотр уведомлений Presenter с
onViewChanged
- Presenter запрашивает данные из View (
view.host
и view.port
), проверяет их и обновляет модель с model.update()
.
- Модель обновляет данные и уведомляет Presenter с помощью
onModelChanged()
- Теперь здесь Presenter должен выполнять логику для конкретного приложения - создавать экземпляр Retrofit с использованием данных модели.
Шаг № 5 может быть выполнен как продолжение # 3 (а не как слушатель модели), но он ничего существенно не меняет.
Какой лучший способ сделать это?
Варианты:
- расширение Presenter Impl и переопределить
onModelChanged()
(не хочу использовать наследование только для него)
- добавить интерфейс слушателя Presenter, сохранить список слушателей и уведомить их об изменении модели в
onModelChanged()
- ваш ответ
Presenter impl находится в другом модуле Gradle, не зависящем от Android, поэтому я не могу просто добавить этот код для конкретного приложения в onModuleChanged()
в PresenterImpl
.