MVC、MVP、MVVM

MVCMVPMVVM都是常见的软件架构设计模式(Architectural Pattern),它通过分离关注点来改进代码的组织方式。 不同于设计模式(Design Pattern),只是为了解决一类问题而总结出的抽象方法,一种架构模式往往使用了多种设计模式。

architectural [ˌɑːrkɪˈtektʃərəl] 建筑学的,建筑上的 pattern [ˈpætərn] 模式,方式

MVCMVPMVVM不同部分是C(Controller)P(Presenter)VM(View-Model),而相同的部分则是MV(Model-View)Model层用于封装和应用程序的业务逻辑相关的数据以及对数据的处理方法。View作为视图层,主要负责数据的展示。

1 MVC

  • 模型(Model)层:用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。Model层有对数据直接访问的权利,例如访问数据库、获取网络数据。Model不依赖ViewController,也就是说,Model不关心它是如何显示或者如何被操作。但是,Model中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此ModelView必须事先在此Model上注册,从而,View可以了解在数据Model上发生的改变;
  • 视图(View)层:主要负责数据显示,在View中一般没有程序上的逻辑。为了实现View上的刷新功能,View需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册;Activity(View)/Fragment(View)Android种的xml文件转换成View后加载到Activity/Fragment中);
  • 控制(Controller)器:用于控制应用程序的流程。它处理事件并作出相应,“事件”包括用户的行为和数据Model上的改变。Android控制层的重任往往落在Activity/Fragment的肩上;

选用这种架构设计的优点是:ViewModel是隔离的,View层的变动不会影响到Model。缺点是:在Android开发中不适合MVC模式,ViewController都放在了Activity/Fragment中,这要会导致Activity/Fragment十分臃肿。

mvc

通过以下的Demo来实现MVC架构:

mvc页面

以下是Controller层代码:

class LoginCActivity : AppCompatActivity(), LoginModel.OnDoLoginStateChange {

    private val loginModel by lazy {
        LoginModel()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_c_login)

        initListener()
    }

    private fun initListener() {
        loginBtn.setOnClickListener {
            toLogin()
        }
    }

    private fun toLogin() {
        val account = accountInput.text.toString()
        val password = passwordInput.text.toString()

        if (TextUtils.isEmpty(account.trim())) {
            ToastUtils.showShort("用户名不可以为空")
            return
        }
        if (TextUtils.isEmpty(password.trim())) {
            ToastUtils.showShort("密码不可以为空")
            return
        }

        loginModel.checkAccountState(account) {
            when (it) {
                0 -> {
                    ToastUtils.showShort("该用户不存在")
                }
                1 -> {
                    loginModel.doLogin(this, account, password)

                    loginBtn.isEnabled = false
                }
            }
        }

    }

    override fun onLoading() {
        tipsTv.text = "登录中..."
    }

    override fun onLoginSuccess() {
        tipsTv.text = "登录成功"
    }

    override fun onLoginFailed() {
        tipsTv.text = "登录失败"
    }
}

以下是Model层代码:

class LoginModel {

    private val random = Random()

    fun doLogin(callback: OnDoLoginStateChange, account: String, password: String) {
        callback.onLoading()
        Thread.sleep(1000)

        val randomValue = random.nextInt(2)
        if (randomValue == 1) {
            callback.onLoginSuccess()
        } else {
            callback.onLoginFailed()
        }
    }

    fun checkAccountState(account:String,block: (Int) -> Unit) {
        block.invoke(random.nextInt(2))
    }

    interface OnDoLoginStateChange {
        fun onLoading()
        fun onLoginSuccess()
        fun onLoginFailed()
    }
}

2 MVP

MVC的设计中,将ViewController的代码放到一起了,这样会导致Activity/Fragment中的代码越来越多,所以开始分离VC中的代码。

Activity/Fragment中复杂的逻辑处理移至另外的一个类(Presenter)中,此时,Activity其实就是MVP模式中的View,负责UI元素的初始化,建立UI元素与Presenter的关联(Listener之类),同时自己也会处理一些简单的逻辑,复杂的逻辑交由Presenter处理。

MVP

  • 模型(Model)层:用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。Model层有对数据直接访问的权利,例如访问数据库、获取网络数据。Model不依赖ViewPresenter,也就是说,Model不关心它是如何显示或者如何被操作;
  • 视图(View)层:负责显示数据,与用户进行交互。持有Presenter层的引用。在Android中体现为Activity/Fragment;
  • 逻辑(Presenter)层:负责逻辑处理,通过Model层获取数据,回显到UI层,响应用户的行为;

通过以下的Demo来实现MVP架构,以下是Model层代码:

class LoginModel {

    companion object {
        const val STATE_LOADING = 1
        const val STATE_SUCCESS = 2
        const val STATE_FAILED = 0
    }

    private val random = Random()

    fun doLogin(account: String, password: String, block: (Int) -> Unit) {
        block.invoke(STATE_LOADING)
        Thread.sleep(1000)

        val randomValue = random.nextInt(2)
        if (randomValue == 1) {
            block.invoke(STATE_SUCCESS)
        } else {
            block.invoke(STATE_FAILED)
        }
    }

    fun checkAccountState(account: String, block: (Int) -> Unit) {
        block.invoke(random.nextInt(2))
    }

}

以下是Presenter层代码:

class LoginPresenter {

    private val loginModel by lazy {
        LoginModel()
    }

    fun checkAccountState(account: String, callback: OnCheckAccountStateResultCallback) {
        loginModel.checkAccountState(account) {
            when (it) {
                0 -> {
                    callback.onNotAvailable()
                }
                1 -> {
                    callback.onAvailable()
                }
            }
        }
    }

    fun doLogin(account: String, password: String, callback: OnDoLoginStateChange) {
        if (TextUtils.isEmpty(account.trim())) {
            callback.onAccountFormatError()
            return
        }
        if (TextUtils.isEmpty(password.trim())) {
            callback.onPasswordEmpty()
            return
        }


        loginModel.doLogin(account, password) {
            when (it) {
                STATE_LOADING -> {
                    callback.onLoading()
                }
                STATE_SUCCESS -> {
                    callback.onLoginSuccess()
                }
                STATE_FAILED -> {
                    callback.onLoginFailed()
                }
            }
        }
    }

    interface OnCheckAccountStateResultCallback {
        fun onNotAvailable()
        fun onAvailable()
    }

    interface OnDoLoginStateChange {
        fun onAccountFormatError()
        fun onPasswordEmpty()
        fun onLoading()
        fun onLoginSuccess()
        fun onLoginFailed()
    }
}

以下是View层代码:

class LoginCActivity : AppCompatActivity(), LoginPresenter.OnDoLoginStateChange,
    LoginPresenter.OnCheckAccountStateResultCallback {

    private val loginPresenter by lazy {
        LoginPresenter()
    }

    private var isAccountAvailable = true

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_c_login)

        initListener()
    }

    private fun initListener() {
        loginBtn.setOnClickListener {
            toLogin()
        }
        accountInput.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                loginPresenter.checkAccountState(s.toString(), this@LoginCActivity)
            }

            override fun afterTextChanged(s: Editable?) {
            }

        })

    }

    private fun toLogin() {
        val account = accountInput.text.toString()
        val password = passwordInput.text.toString()

        if (!isAccountAvailable) {
            return
        }

        loginPresenter.doLogin(account, password, this)

    }

    override fun onAccountFormatError() {
        tipsTv.text = "账号格式不正确"
        this.isAccountAvailable = false
    }

    override fun onPasswordEmpty() {
        tipsTv.text = "密码不可以为空"
    }

    override fun onLoading() {
        tipsTv.text = "登录中..."
    }

    override fun onLoginSuccess() {
        tipsTv.text = "登录成功"
    }

    override fun onLoginFailed() {
        tipsTv.text = "登录失败"
    }

    override fun onNotAvailable() {
        tipsTv.text = "当前用户不可用"
        this.isAccountAvailable = false
    }

    override fun onAvailable() {
        tipsTv.text = "当前用户可用..."
        this.isAccountAvailable = true
    }
}

优点:分离视图层和逻辑层,降低了耦合,模型层与视图层完全分离。解决了业务逻辑复杂时Activity/Fragment代码过于臃肿的问题。

缺点:

  • 那就是对UI的操作必须在ActivityFragment的生命周期之内,否则容易出现各种异常,比如内存泄漏。Presenter无法直接感知View层的生命周期;
  • PresenterView之间的耦合度高。如果app中很多界面都使用了同一个Presenter ,每个View层都可能都会存在一些无关方法,一旦Presenter层有所变动,相关的个View都需要调整;

以下通过一个例子来说明第二个缺点。以下是页面展示:

优化Presenter

以下Presenter&Model层的代码:

class PlayerPresenter private constructor() {

    companion object {
        val instance by lazy {
            PlayerPresenter()
        }
    }

    enum class PlayState {
        NONE, PLAYING, PAUSE, LOADING
    }

    private val callbacksList = arrayListOf<IPlayerCallback>()

    private var currentPlayState = PlayState.NONE

    fun registerCallback(callback: IPlayerCallback) {
        if (!callbacksList.contains(callback)) {
            callbacksList.add(callback)
        }
    }

    fun unRegisterCallback(callback: IPlayerCallback) {
        callbacksList.remove(callback)
    }

    fun doPlayOrPause() {
        dispatchTitleChange("当前播放的歌曲标题")
        dispatchCoverChange("当前播放的歌曲封面")
        if (currentPlayState != PlayState.PLAYING) {
            dispatchPlayingState()
            currentPlayState = PlayState.PLAYING
        } else {
            dispatchPauseState()
            currentPlayState = PlayState.PAUSE
        }
    }

    fun playNext() {
        dispatchTitleChange("切换到下一首,标题变化了")
        dispatchCoverChange("切换到下一首,封面变化了")
        currentPlayState = PlayState.PLAYING
    }


    fun playPre() {
        dispatchTitleChange("切换到上一首,标题变化了")
        dispatchCoverChange("切换到上一首,封面变化了")
        currentPlayState = PlayState.PLAYING
    }

    private fun dispatchPauseState() {
        callbacksList.forEach { it.onPlayingPause() }
    }

    private fun dispatchPlayingState() {
        callbacksList.forEach { it.onPlaying() }
    }

    private fun dispatchCoverChange(cover: String) {
        callbacksList.forEach { it.onCoverChange(cover) }
    }

    private fun dispatchTitleChange(title: String) {
        callbacksList.forEach { it.onTitleChange(title) }
    }

}

class PlayerModel {
  // 数据处理
}

以下是View层代码:

class PlayerActivity : AppCompatActivity(), IPlayerCallback {

    private val playerPresenter by lazy {
        PlayerPresenter.instance
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_player)

        playerPresenter.registerCallback(this)

        initListener()

    }

    private fun initListener() {
        playerOrPauseBtn.setOnClickListener {
            playerPresenter.doPlayOrPause()
        }
        playNextBtn.setOnClickListener {
            playerPresenter.playNext()
        }
        playPreBtn.setOnClickListener {
            playerPresenter.playPre()
        }

    }

    override fun onDestroy() {
        super.onDestroy()
        playerPresenter.unRegisterCallback(this)
    }

    override fun onTitleChange(title: String) {
        songTitleTv?.text = title
    }

    override fun onProgressChange(current: Int) {
    }

    override fun onPlaying() {
        playerOrPauseBtn.text = "暂停"
    }

    override fun onPlayingPause() {
        playerOrPauseBtn.text = "播放"
    }

    override fun onCoverChange(cover: String) {
        println("封面更新 $cover")
    }
}

class FloatPlayerActivity : AppCompatActivity(), IPlayerCallback {

    private val playerPresenter by lazy {
        PlayerPresenter.instance
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_float_player)

        playerPresenter.registerCallback(this)

        initListener()
    }

    override fun onDestroy() {
        super.onDestroy()
        playerPresenter.unRegisterCallback(this)
    }

    private fun initListener() {
        playOrPauseBtn.setOnClickListener {
            playerPresenter.doPlayOrPause()
        }
    }

    override fun onTitleChange(title: String) {
    }

    override fun onProgressChange(current: Int) {
    }

    override fun onPlaying() {
        playOrPauseBtn.text = "暂停"
    }

    override fun onPlayingPause() {
        playOrPauseBtn.text = "播放"
    }

    override fun onCoverChange(cover: String) {
    }
}

FloatPlayerActivity中没有用到onTitleChange/onProgressChange/onCoverChange等方法,但是由于实现了IPlayerCallback接口,所以必须进行方法重写。

进行优化,添加DataListenerController类,做数据监听:

class DataListenerController<T> {

    private val blocks = arrayListOf<(T?) -> Unit>()

    var value: T? = null
        set(value: T?) {
            blocks.forEach { it.invoke(value) }
        }

    fun addListener(block: (T?) -> Unit) {
        if (!blocks.contains(block)) {
            blocks.add(block)
        }
    }
}

修改PlayerPresenter中的代码:

class PlayerPresenter private constructor() {

    private val playerModel by lazy {
        PlayerModel()
    }

    private val player by lazy {
        MusicPlayer()
    }

    var currentMusic = DataListenerController<Music>()

    var currentPlayState = DataListenerController<PlayState>()

    companion object {
        val instance by lazy {
            PlayerPresenter()
        }
    }

    enum class PlayState {
        NONE, PLAYING, PAUSE, LOADING
    }

    fun doPlayOrPause() {
        if (currentMusic.value == null) {
            currentMusic.value = playerModel.getMusicById("卡农")
        }
        player.play(currentMusic.value)

        currentPlayState.value = if (currentPlayState.value != PlayState.PLAYING) {
            PlayState.PLAYING
        } else {
            PlayState.PAUSE
        }
    }

    fun playNext() {
        currentMusic.value = playerModel.getMusicById("下一首:梦中的婚礼")
        currentPlayState.value = PlayState.PLAYING
    }


    fun playPre() {
        currentMusic.value = playerModel.getMusicById("上一首:一步之遥")
        currentPlayState.value = PlayState.PLAYING
    }

}

修改PlayerActivity中的代码:

class PlayerActivity : AppCompatActivity() {

    private val playerPresenter by lazy {
        PlayerPresenter.instance
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_player)
        initDataListener()

        initListener()
    }

    private fun initListener() {
        playerOrPauseBtn.setOnClickListener {
            playerPresenter.doPlayOrPause()
        }
        playNextBtn.setOnClickListener {
            playerPresenter.playNext()
        }
        playPreBtn.setOnClickListener {
            playerPresenter.playPre()
        }
    }

    private fun initDataListener() {
        playerPresenter.currentMusic.addListener {
            songTitleTv.text = it?.name
            println("封面改变了... ${it?.cover}")
        }
        playerPresenter.currentPlayState.addListener {
            when (it) {
                PlayerPresenter.PlayState.PAUSE -> {
                    playerOrPauseBtn.text = "播放"
                }
                PlayerPresenter.PlayState.PLAYING -> {
                    playerOrPauseBtn.text = "暂停"
                }
                else -> {}
            }
        }
    }

}

对于第一个缺点,如何让Presenter层感知View层的生命周期变化?

定义一个和生命周期有关的接口ILifecycle

interface ILifecycle {
    fun onCreate()
    fun onStart()
    fun onResume()
    fun onPause()
    fun onStop()
    fun onDestroy()
}

Presenter实现这个接口:

class PlayerPresenter private constructor() : ILifecycle {
    ...
    override fun onCreate() {
    }
    override fun onStart() {
    }
    override fun onResume() {
    }
    override fun onPause() {
    }
    override fun onStop() {
    }
    override fun onDestroy() {
    }
}

View层的生命周期方法中调用:

class PlayerActivity : AppCompatActivity() {
  
  	...
    override fun onStart() {
        super.onStart()
        playerPresenter.onStart()
    }
  
    override fun onResume() {
        super.onResume()
        playerPresenter.onResume()
    }

    override fun onPause() {
        super.onPause()
        playerPresenter.onPause()
    }

    override fun onStop() {
        super.onStop()
        playerPresenter.onStop()
    }

    override fun onDestroy() {
        super.onDestroy()
        playerPresenter.onDestroy()
    }

}

进一步优化:抽取BaseActivity让其它的Activity也可以通知实现ILifecyclePresenter生命周期状态变化:

open class BaseActivity : AppCompatActivity() {

    private val lifecycleListener = arrayListOf<ILifecycle>()

    open fun addLifeListener(listener: ILifecycle) {
        if (!lifecycleListener.contains(listener)) {
            lifecycleListener.add(listener)
        }
    }

    open fun removeLifeListener(listener: ILifecycle) {
        lifecycleListener.remove(listener)
    }

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

        lifecycleListener.forEach {
            it.onCreate()
        }
    }

    override fun onStart() {
        super.onStart()
        lifecycleListener.forEach {
            it.onStart()
        }
    }

    override fun onResume() {
        super.onResume()
        lifecycleListener.forEach {
            it.onResume()
        }
    }

    override fun onPause() {
        super.onPause()
        lifecycleListener.forEach {
            it.onPause()
        }
    }

    override fun onStop() {
        super.onStop()
        lifecycleListener.forEach {
            it.onStop()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        lifecycleListener.forEach {
            it.onDestroy()
        }
    }
}

Presenter层实现ILifecycle接口,并实现其中的方法:

class PlayerPresenter private constructor() : ILifecycle {

    private val playerModel by lazy {
        PlayerModel()
    }

    private val player by lazy {
        MusicPlayer()
    }

    var currentMusic = DataListenerController<Music>()

    var currentPlayState = DataListenerController<PlayState>()

    companion object {
        val instance by lazy {
            PlayerPresenter()
        }
    }

    enum class PlayState {
        NONE, PLAYING, PAUSE, LOADING
    }

    fun doPlayOrPause() {
        if (currentMusic.value == null) {
            currentMusic.value = playerModel.getMusicById("卡农")
        }
        player.play(currentMusic.value)

        currentPlayState.value = if (currentPlayState.value != PlayState.PLAYING) {
            PlayState.PLAYING
        } else {
            PlayState.PAUSE
        }
    }

    fun playNext() {
        currentMusic.value = playerModel.getMusicById("下一首:梦中的婚礼")
        currentPlayState.value = PlayState.PLAYING
    }


    fun playPre() {
        currentMusic.value = playerModel.getMusicById("上一首:一步之遥")
        currentPlayState.value = PlayState.PLAYING
    }

    override fun onCreate() {
        println("开始监听网络变化")
    }

    override fun onStart() {
    }

    override fun onResume() {
    }

    override fun onPause() {
    }

    override fun onStop() {
        println("结束监听网络变化")
    }

    override fun onDestroy() {
    }
}

View层添加监听:

class PlayerActivity : BaseActivity() {

    private val playerPresenter by lazy {
        PlayerPresenter.instance
    }

    init {
        addLifeListener(playerPresenter)
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_player)

        initDataListener()
        initListener()
    }

    private fun initListener() {
        playerOrPauseBtn.setOnClickListener {
            playerPresenter.doPlayOrPause()
        }
        playNextBtn.setOnClickListener {
            playerPresenter.playNext()
        }
        playPreBtn.setOnClickListener {
            playerPresenter.playPre()
        }
    }

    private fun initDataListener() {
        playerPresenter.currentMusic.addListener {
            songTitleTv.text = it?.name
            println("封面改变了... ${it?.cover}")
        }
        playerPresenter.currentPlayState.addListener {
            when (it) {
                PlayerPresenter.PlayState.PAUSE -> {
                    playerOrPauseBtn.text = "播放"
                }
                PlayerPresenter.PlayState.PLAYING -> {
                    playerOrPauseBtn.text = "暂停"
                }
                else -> {}
            }
        }
    }
}

MVP如何设计避免内存泄漏?

MVP模式在封装的时候会造成内存泄漏,因为Presenter层,需要做网络请求,所以就需要考虑到网络请求的取消操作,如果不处理,Activity销毁了,Presenter层还在请求网络,就会造成内存泄漏。

如何解决MVP模式造成的内存泄漏?只要Presenter层能感知Activity生命周期的变化,在Activity销毁的时候,取消网络请求,就能解决这个问题。

MVCMVP区别

ViewModel并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVCView可以与Model直接交互。MVP隔离了MVC中的MV的直接联系后,靠Presenter来中转,所以使用MVPP是直接调用View的接口来实现对视图的操作的。

MVC&MVP

3 MVVM

  • 模型层(Model):即数据模型,用于获取和存储数据;
  • 视图模型层(ViewModel):与Presenter大致相同,都是负责处理数据和实现业务逻辑,但ViewModel层不应该直接或者间接地持有View层的任何引用;
  • 视图层(View):包含布局,以及布局生命周期控制器(Activity/Fragment);
    MVP

MVVM使用了观察者模式,可以算是MVP的升级版,ViewModel层的功能和MVP中的Presenter层类似,都是用来进行逻辑处理的。在MVVM中,ViewModel层和View层的交互是通过DataBinding(数据双向绑定)来完成的,ViewModel层可以监听视图产生的事件,同样的,当Model层的数据发生变化时,也是通过ViewModel层对视图上的内容进行更新的。因此,Data Binding减轻了MVP中的Presenter层和View层的互动职责,降低了耦合性。

MVVM的优点:

  • 耦合度更低,复用性更强,没有内存泄漏;
  • 结合Jetpack,写出更优雅的代码;

缺点:

ViewModelView层的通信变得更加困难了,在一些比较简单的页面中要酌情使用,对于MVP这个道理也依然适用。

参考

https://blog.csdn.net/lihaoxiang123/article/details/78977181
https://blog.csdn.net/caijunfen/article/details/78478438

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MVC(Model-View-Controller)、MVP(Model-View-Presenter)和MVVM(Model-View-ViewModel)是常见的软件架构模式,用于组织和管理应用程序的代码。 1. MVC(Model-View-Controller): - Model(模型):负责存储和管理应用程序的数据和业务逻辑。 - View(视图):负责显示数据并与用户进行交互。 - Controller(控制器):处理用户输入,并根据输入更新模型和视图。 在MVC中,模型和视图是相互独立的,通过控制器来协调数据的更新和视图的更新。用户的输入首先由控制器处理,然后控制器更新模型的状态,最后模型的变化会反映在视图上。MVC模式可以有效地分离应用程序的逻辑和界面。 2. MVP(Model-View-Presenter): - Model(模型):负责存储和管理应用程序的数据和业务逻辑。 - View(视图):负责显示数据并与用户进行交互。 - Presenter(展示器):作为View和Model之间的中间人,处理用户输入并更新模型和视图。 在MVP中,Presenter负责处理用户的输入,并根据输入更新模型和视图。View只负责显示数据和将用户输入传递给Presenter,而不直接与模型交互。这种分离使得视图和模型可以独立开发和测试。 3. MVVM(Model-View-ViewModel): - Model(模型):负责存储和管理应用程序的数据和业务逻辑。 - View(视图):负责显示数据并与用户进行交互。 - ViewModel(视图模型):作为View和Model之间的中间人,处理视图的状态和行为,并将数据从模型转换为视图可用的形式。 在MVVM中,视图通过绑定(数据绑定)与视图模型关联,当模型的状态发生变化时,视图模型会自动更新视图。这种双向绑定使得视图和模型始终保持同步,减少了手动更新视图的代码量。 总结来说,MVCMVPMVVM都是用于组织和管理应用程序的代码,它们都有各自的优势和适用场景。选择哪种架构模式取决于应用程序的需求、团队的技术背景和个人偏好。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值