MVC、MVP、MVVM、最高のアーキテクチャパターンは?
ギャングオブフォーは、特定のシナリオのために抽象化された23の古典的なデザインパターンがあり、MVC、MVPとMVVMは、デザインパターンと呼ぶべきではない、あなたはそれがデザインパターンであることを言いたいだけでなく、デザインパターンのより高いレベルである場合は、ビュー、制御、およびロジックの3つのレベルの設計のプロジェクト全体であるカットと相互通信。千人は千人のハムレットを持って、MVC、MVPとMVVPも同じで、千人のプログラマも千種類の理解が、共通の理解やいくつかのコンセンサスを持っています。
- MVCはプロジェクト全体の構造をモデル(Model)、ビュー(View)、コントローラー(Controller)の3つの部分に分け、各機能モジュールはこのように分けることができます。ビューは主にUI関連を管理し、モデルはビジネスロジックの処理とデータの要求と保存、コントローラーはビューとモデルの連結、です。MVCは、機能によってコードを異なるモジュールに分割するため、各モジュールはインターフェイスの定義を持つことができ、モジュール間の直接的な結合がないため、交換可能な効果を達成することができ、したがって、再利用性も向上します。しかし、MVCモデルは定義があまり明確ではなく、実際の開発ではViewとControllerの担当が混同されることが多く、Activity/FragmentはViewに属するかControllerに属するか、Activity/FragmentはViewと言われていますが、データ処理やユーザーイベント処理のロジックが多く含まれており、Controllerと言えます。Activity/FragmentはViewだと言われていますが、データ処理やユーザーイベント処理のロジックが多く含まれており、Controllerだと言いたいのであれば、Activity/FragmentにはViewの初期化やUIのセットアップ関数が多く含まれています。Activity/FragmentがViewとControllerを混同しているのではなく、実際の開発ではControllerの定義が非常に曖昧なのです。MVCパターンに基づいてコードを書いて完璧だと思わせることができるのは、よほど古風なアーキテクトだけで、古風なアーキテクトはもうどのアーキテクチャパターンにも縛られないのかもしれません。
- MVPは、MVC ControllerをPresenterに置き換えたもので、実際には、名前は重要ではありませんが、重要なことは、Pの概念は、人々をより明確にするために、アーキテクチャレベルでCよりも表現することです。 Presenterは、表現を意味し、ViewとModelを分離することです。 Presenterは、Viewへのインターフェイスを介して呼び出すために、MVCのMVPは、進化します!MVPは、Pを通してMVP MとVに間接的に依存するだけで、UIとロジックを、より明確にすることができます。
- MVVMはModel + View + ViewModel、つまりMVCとMVPのCとPをViewModelに置き換えたものと理解してください。 ViewModelはUI関連のステートフィールドを格納し、Modelのビジネスロジックの結果をコールバックする必要がある場合、ステートフィールドが更新され、ステートフィールドが更新される限り、これらのステートフィールドとUIは互いにバインドされます。ステートフィールドが更新される限り、UIもそれに応じて更新されるので、ControllerやPresenterにデータバインディングロジックを書いたり、オブジェクト同士を保持したりする必要はありません。さらに、ViewModelはライフサイクルの特別な処理を持っており、非Active状態では、UIを更新することができず、状態がActiveに変更されると、再び更新コマンドが実行されます。データが変更されると、ビューの状態フィールドが更新され、状態はUI更新にバインドされます。データの更新がUI更新につながり、UIイベントがビジネス操作をトリガーします。
ViewModel簡単なデモ
class ViewModelDemoActivity: AppCompatActivity() {
companion object {
const val TAG = "ViewModelDemoActivity"
class DemoViewModel: ViewModel() {
val demo = MutableLiveData<Int>().also { it.postValue(0) }
}
class GetDemoFragment(): Fragment() {
lateinit var demoViewModel: DemoViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
demoViewModel = ViewModelProvider(activity!!, object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return DemoViewModel() as T
}
}).get(DemoViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val btn = Button(context)
btn.text = "get"
btn.setOnClickListener {
Toast.makeText(context, "get demo = ${demoViewModel.demo.value}", Toast.LENGTH_SHORT).show()
}
return btn
}
}
class SetDemoFragment(): Fragment() {
lateinit var demoViewModel: DemoViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
demoViewModel = ViewModelProvider(activity!!, object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return DemoViewModel() as T
}
}).get(DemoViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val btn = Button(context)
btn.text = "set"
btn.setOnClickListener {
demoViewModel.demo.postValue(demoViewModel.demo.value!! + 10)
}
return btn
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "activity onCreate")
val content = LinearLayout(this)
content.gravity = Gravity.CENTER
content.orientation = LinearLayout.VERTICAL
val btn = Button(this)
btn.text = "change orientation"
btn.setOnClickListener {
requestedOrientation = if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
} else {
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
}
content.addView(btn)
val contentView = LinearLayout(this)
contentView.orientation = LinearLayout.HORIZONTAL
val fragment1 = FrameLayout(this)
fragment1.id = R.id.fragment1
val fragment1LayoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1.0F)
contentView.addView(fragment1, fragment1LayoutParams)
val fragment2 = FrameLayout(this)
fragment2.id = R.id.fragment2
val fragment2LayoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1.0F)
contentView.addView(fragment2, fragment2LayoutParams)
val demoViewModel = ViewModelProvider(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return DemoViewModel() as T
}
}).get(DemoViewModel::class.java)
demoViewModel.demo.observe(this, Observer {
Toast.makeText(this, "demo changed to $it", Toast.LENGTH_SHORT).show()
})
content.addView(contentView)
setContentView(content)
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment1, GetDemoFragment())
transaction.replace(R.id.fragment2, SetDemoFragment())
transaction.commitAllowingStateLoss()
}
}
DemoViewModelはこのデモで使用されており、LiveDataの1つのプロパティのデモしかありません。Activityには2つのFragmentがあり、それぞれにボタンがあり、クリックするとこのDemoViewModelのデモでそれぞれgetとsetが行われます。これは、Activity/Framgentの再構築プロセスにおけるViewModelの役割のデモンストレーションとして、画面の回転をさせるために、もう一度ActivityのonCreateを強制的に再実行し、2つのFragmentを再作成します。Fragment は、Activity/Framgent の再構築プロセスにおける ViewModel の役割を実証するためであり、2 つの Fragment は、2 つの Fragment 間でデータを転送するための ViewModel の役割を実証するためでもあります。
ViewModelソースコード分析
まとめ
ソースコードを分析すると、ViewModel は実際には Activity#onRetainNonConfigurationInstance メソッドと Activity#getLastNonConfigurationInstance メソッドの組み合わせに依存して、設定変更によってアクティビティが破棄されたり再構築されたりしたときにオブジェクトを保存したり復元したりしていることがわかります。Activity#onConfigurationInstanceメソッドとActivity#getLastNonConfigurationInstanceメソッドを組み合わせることで、設定変更によりアクティビティが破棄・再構築された際にオブジェクトを保存・復元することができます。ViewModel の作成プロセスは Factory によって決定されるため、Activity/Fragment のようなコンテキストの重いオブジェクトを保持しない ViewModel を持つことも可能です。ViewModelとLiveDataを組み合わせることで、ライフサイクルとUI操作の関係をメモリリークすることなく友好的に扱うことができ、データの保存と復元、UIの復元を安全に行うことができます。LiveDataは主にライフサイクルとデータバインドされたUIの関係を処理し、ViewModelはActivityの破棄を処理します。ViewModel+LiveDataはMVVMアーキテクチャの実装にもよく使用され、LiveDataがデータバインディングの問題を処理し、ViewModelがデータとUIライフサイクルの関係を処理します。