H5,iOS,Android;页面对象模型,也称为POM的MVM
#javascript #android #ios #designpatterns

mvmdemo

H5,iOS,Android,软件体系结构,MVM(中介,查看,ViewModel)

该设计最初是按照MVM的原理(中介,查看,ViewModel)进行概念化的。完成后,我进行了在线搜索以识别类似的方法。最终,Page Object Model, also known as POM, is a design pattern in Selenium that creates an object repository for storing all web elements.但是,在iOS,Android和H5中,我尚未找到与此方面有关的设计模式。

I. MVM简介

在其核心上,它充当后端和UI之间的中介。它将请求发送到接口数据的后端,解析数据,并生成适合UI要求的数据模型,包括datafunction组件。然后,前端直接呈现此模型,使UI开发人员摆脱了解决基础逻辑的需求。同时,逻辑开发人员摆脱了关注UI关注的关注。

startdemo中,简单代码,初学者可以轻松理解
实施:https://github.com/AblerSong/MVMDemo

ii。设计方法

  • 创建一个页面(iOS: ViewController; Android: Activity or fragment; Vue:.vue)
  • 将页面UI分为不同的组件,每个组件对应于ViewModel。 UI文本定义为ViewModel中的变量,而UI点击事件定义为关闭。
  • 建立一个Mediator,该Mediator在完成API请求后,根据从后端返回的数据来初始化所有ViewModel变量和封闭。有关特定详细信息,请参阅IV. Code Implementation
  • Mediator提供给UI开发人员,他们可以根据Mediator中包含的数据直接绑定和渲染UI。

iii。特定的实施和详细要求(参考解决方案)

  • #####遵循页面的部门。
  • #####将页面分为基于“行”的不同组件,每个组件都对应于特定的ViewModel,所有组件都通过PageMediator进行管理。
  • #####使用每个PageMediatorMediatorManager Singleton(在H5演示中,可以使用Vuex)。这种方法可确保数据保持活力,而UI元素不能保持活力。

MediatorManager Singleton管理PageMediators,每个PageMediator管理ViewModels ,如下所示:

MediatorManager.getSingleton().mediator = new Mediator
MediatorManager.getSingleton().mediator = null
  • 如果页面包含许多组件,则PageMediator可能会变得复杂。在这种情况下,您可以使用适当的design patterns来重构PageMediator。分裂的特定方法取决于个人偏好和建筑能力。
  • 至关重要的是要仔细考虑public variablesmethodsPageMediator中。,只要UI的暴露API是合理的,随后的逻辑重构就不会影响UI,并且更改UI对UI的影响最少,对逻辑。
  • 在实践发展中,还应封装其他模块,以促进将来使用Unit Test作为UI Test的替代。例如,路由器,吐司和网络等模块。例如,对于Toast
class ToastViewModel {
    // use ReactiveX replace setter
    set toast(value) {
        Toast(value)
    }
}

iv。代码实现

Image description

以下代码表明VUE使用bind,iOS使用tableView,而Android使用适配器进行渲染。数据绑定在每个页面的组件中执行,而所有逻辑都集中在中介器中。

查看

Mediator {
  username_text = "username"
  password_text = "password"
  _username_str = ""
  _password_str = ""
  login_btn_disabled = true

  constructor() {
    this.init()
  }
  init() {}

  set username_str(value) {
    this._username_str = value
    this.update_login_btn_disabled()
  }
  get username_str() {
    return this._username_str
  }

  set password_str(value) {
    this._password_str = value
    this.update_login_btn_disabled()
  }
  get password_str() {
    return this._password_str
  }

  update_login_btn_disabled() {
    this.login_btn_disabled = !(this.username_str?.length && this.password_str?.length)
  }

  onSubmit() {
    if (this.username_str == "admin" && this.password_str == "123456") {
      router.back()
    } else {
    }
  }
}

Android

class ButtonViewModel (
    val buttonState: BehaviorSubject<Boolean> = BehaviorSubject.createDefault(false),
    var buttonText: BehaviorSubject<String> = BehaviorSubject.createDefault(""),
) {
    var clickItem = {}
}

class InputViewModel (
    val text: BehaviorSubject<String> = BehaviorSubject.createDefault(""),
    val value: BehaviorSubject<String> = BehaviorSubject.createDefault("")
) {}

class Mediator : BaseMediator () {
    val usernameViewModel: InputViewModel = InputViewModel()
    val passwordViewModel: InputViewModel = InputViewModel()
    val buttonViewModel: ButtonViewModel = ButtonViewModel()

    init {
        usernameViewModel.text.onNext("username")
        passwordViewModel.text.onNext("password")

        val isNotEmpty: (String, String) -> Boolean = { name: String, age: String ->
            name.isNotEmpty() && age.isNotEmpty()
        }
        val d1 = Observable.combineLatest(usernameViewModel.value, passwordViewModel.value, isNotEmpty).subscribe {
            buttonViewModel.buttonState.onNext(it)
        }

        buttonViewModel.clickItem = {
            val username = usernameViewModel.value.value
            val password = passwordViewModel.value.value
            if (username == "admin" && password == "123456") {
                routerSubject.onNext(R.layout.activity_main)
            } else {
                ToastManager.toastSubject.onNext("input error")
            }
        }

        compositeDisposable.add(d1)
    }

    val dataList by lazy { initList() }

    private fun initList(): List<Map<String, Any>> {
        val m1 = mapOf(Pair("viewType", R.layout.input_item), Pair("viewHolder", InputViewHolder::class.java), Pair("viewModel", usernameViewModel))
        val m2 = mapOf(Pair("viewType", R.layout.input_item), Pair("viewHolder", InputViewHolder::class.java), Pair("viewModel", passwordViewModel))
        val m3 = mapOf(Pair("viewType", R.layout.button_item), Pair("viewHolder", ButtonViewHolder::class.java), Pair("viewModel", buttonViewModel))

        return listOf<Map<String, Any>>(m1, m2, m3)
    }
}

ios

class ButtonCellViewModel: BaseViewModel {
    let login_btn_disabled = BehaviorRelay(value: false)
    var onSubmit = {}
}

class TextFieldCellViewModel: BaseViewModel {
    let text = BehaviorRelay(value: "")
    let value = BehaviorRelay(value: "")
}

class Mediator: BaseMediator {
    let usernameViewModel = TextFieldCellViewModel()
    let passwordViewModel = TextFieldCellViewModel()
    let buttonCellViewModel = ButtonCellViewModel()

    lazy var list: [[[String : Any]]] = {
        let arr: [[[String : Any]]] = [
            [
                ["model":usernameViewModel,"reuseIdentifier":textFieldCellReuseIdentifier],
                ["model":passwordViewModel,"reuseIdentifier":textFieldCellReuseIdentifier],
            ],
            [
                ["model":buttonCellViewModel,"reuseIdentifier":buttonCellReuseIdentifier]
            ]
        ]
        return arr
    }()

    override init() {
        super.init()

        initPasswordViewModel()
        initUsernameViewModel()
        initButtonCellViewModel()
    }

    func initUsernameViewModel() {
        usernameViewModel.text.accept("username")
    }
    func initPasswordViewModel() {
        passwordViewModel.text.accept("password")
    }
    func initButtonCellViewModel() {
        let combineLatest = Observable.combineLatest(usernameViewModel.value, passwordViewModel.value)

        combineLatest.map { (username: String, password: String) -> Bool in
            return username.count > 0 && password.count > 0
        }.bind(to: buttonCellViewModel.login_btn_disabled).disposed(by: disposeBag)


        buttonCellViewModel.onSubmit = {
            combineLatest.subscribe( onNext: { (username: String, password: String) in
                if username == "Admin", password == "123456" {
                    RouterBehaviorSubject.onNext(RouterModel(type: .pop))
                } else {
                    ToastBehaviorSubject.onNext("input error")
                }
            }).dispose()
        }
    }
}

V.优势和缺点

优点:

  • 与Viper,MVI等这样的框架相比,核心想法更简单,更容易掌握。
  • UI和逻辑的分离有助于任务分解和组合,增强代码可重复使用性。
  • 正确分解了,Mediator可以代替UI test这有助于直接白盒自动测试
  • 多亏了MediatorManager Singleton的存在,数据仍然存在,而UI则没有。数据是集中式的,可以轻松管理。
  • 统一的业务代码结构使开发人员可以快速接管他人的代码。

缺点:

  • 开发人员不注意注意力很容易导致内存泄漏,这可能是具有挑战性的。

vi。结论

从实际的发展角度来看,该框架非常适合H5应用。例如,在演示(VUE)中,将组件分解为.vue.scss.js文件大大增强了代码可重复使用性,尤其是由于CSS文件的独立性。

对于H5,iOS和Android,使用Mediator代替Unit test代替UI test可以显着降低错误并提高测试效率。

就个人而言,这种方法给我留下了深刻的印象。与单位测试的便利性相比