MVC能夠說是框架的經典了,可是在MVC框架的實踐中,咱們很難作到下降它的耦合度,咱們在使用過程當中,會有大量的接口都出如今controller中,致使controller中的代碼很是的龐大,而在view中實現的時候,咱們又習慣性的只實現頁面佈局相關的東西,而到了動畫,頁面佈局邏輯,咱們又會丟到controller中去處理。controller複雜的邏輯,與頁面極高的耦合度,會致使咱們在開發過程沒法抽離測試代碼,只能經過e2e的方式進行全量測試,增長程序員自測的工做量。 前端
MVVM架構是MVX裏面目前來講最新的一個,讓咱們但願它在出現的時候已經考慮到了MVX模式以前所遇到的問題吧。
在一個前端的角度來說,MVVM是一個再熟悉不過的框架了,畢竟react/vue都是在MVVM框架的基礎上出現的,MVVM對於MVC來講作的最大的改造就是將controller拆解,並分給view和view-model兩個部分,經過數據驅動的方式呈現頁面,更加的直觀。 vue
VIPER 框架,能夠說把層次劃分到最細,自然的解耦讓VIPER代碼的測試工做變得異常輕鬆。
view與view之間是經過router相關聯的,沒有任何頁面之間是強依賴的,這意味着你能夠單獨測試某張頁面而不須要將所有的流程都回歸一遍。
並且,viper框架生成的各個組件,均可以認爲是一個獨立的模塊,一個獨立的個體,只要你的基礎架構相同,那麼這些獨立模塊在任何系統中均可以互相嵌套使用,而不須要作重複工做單獨開發這些組件。react
VIPER框架最初起始於iOS設計中,是在MVVM框架的基礎上演變而來。git
從字面意思來理解,VIPER 即 View Interactor Presenter Entity Router(視圖 交互 協調器 實體 路由)。VIPER 在責任劃分層面進行了迭代,VIPER 分爲五個層次:程序員
VIPER的特點就是職責明確,粒度細,隔離關係明確,這樣能帶來不少優勢:github
VIPER由於需求的拆分粒度細,相應的會帶來如下問題:服務器
VIPER框架最關鍵的是如何將相關接口定義出來,爲了實現VIPER框架的目錄結構,咱們將代碼實現爲以下目錄結構: markdown
View中主要是當前頁面的初始化等操做,並將頁面事件傳遞給本身的Presenter網絡
class MainTabView extends StatefulWidget implements BaseView { const MainTabView({ Key key, this.appBar, this.views, this.presenter, }); final MainTabPresenter presenter; // mainTab中的appBar使用 final PreferredSizeWidget appBar; final List<TabModel> views; @override _MainTabViewState createState() => _MainTabViewState(); } class _MainTabViewState extends State<MainTabView> with SingleTickerProviderStateMixin { TabController tabController; @override void initState() { super.initState(); tabController = new TabController(length: widget.views.length, vsync: this); } @override void dispose() { super.dispose(); tabController.dispose(); } List<Tab> createTabs() { List<Tab> tabs = new List<Tab>(); widget.views.forEach((e) { var tab = Tab( text: e.tabName, icon: e.icon, ); tabs.add(tab); }); return tabs; } List<Widget> createBody() { List<Widget> bodies = new List<Widget>(); widget.views.forEach((e) { bodies.add(e.body); }); return bodies; } @override Widget build(BuildContext context) { print(widget.views.map((e) => e.body)); return Scaffold( backgroundColor: Colors.blue, appBar: widget.appBar, body: Material( child: TabBarView( controller: tabController, children: createBody(), ), ), bottomNavigationBar: SafeArea( child: Material( color: Colors.blue, child: SafeArea( child: TabBar( onTap: (index) { widget.presenter.tabChanged(index); }, indicator: const BoxDecoration(), controller: tabController, tabs: createTabs(), ), ), ), ), ); } } 複製代碼
Interactor中主要是實例化相關的數據,並將數據接口提供給Presenter以反饋給View使用:架構
class MainTabViewModel { List<TabModel> tabs; MainTabViewModel({ this.tabs, }); } class MainTabInteractor implements BaseInteractor { MainTabViewModel viewModel = MainTabViewModel( tabs: [ TabModel( tabName: '測試tab1', body: Container( child: Text('測試頁面1'), ), ), ... ], ); } 複製代碼
Presenter主要是將Interactor中處理的viewModel反饋給View,並接收View中的頁面事件,進行處理。
class MainTabPresenter implements BasePresenter { @override Widget create(List<TabModel> params) { return MainTabView( views: MainTabInteractor().viewModel.tabs, presenter: this, ); } void tabChanged(int index) { print('tab changed to: $index'); } } 複製代碼
Entity中主要是實現當前結構中所須要使用的各類類定義,並不須要作實體化操做
class TabModel implements BaseModel { String tabName; Icon icon; Widget body; TabModel({ this.tabName, this.icon, this.body, }); } 複製代碼
Router中主要定義push/pop操做時的一些動做,以及頁面如何初始化。頁面初始化均由Presenter觸發。
class MainTabRouter extends BaseRouter { @override void push(context, params, title) { super.push(context, params, title); Route route = MaterialPageRoute(builder: (context) { return MainTabPresenter().create(params); }); Navigator.push(context, route); } } 複製代碼
咱們在主路由中實現靜態方法Push/Pop:
// 定義Router的key值,方便後續調用 enum RouterKey { MainTab, } // 實現Router類 class Router { static Map<RouterKey, BaseRouter> routeMap = { RouterKey.MainTab: MainTabRouter(), }; static void push(RouterKey destination, context, {params, title}) { if (routeMap.containsKey(destination)) { var router = routeMap[destination]; router.push(context, params, title); } } static void pop(context) { if (Navigator.canPop(context)) { Navigator.pop(context); } } } 複製代碼
此時咱們的一套完整的VIPER流程就實現完成了
此時經過main中寫入一個Button,用來觸發Router的頁面push效果:
body: Center( child: MaterialButton( onPressed: () { Router.push("mainTab", context); }, child: Text('push頁面'), ), ), 複製代碼
以後就能夠看到完整的一套流程了:
1.增長頁面建立腳本/插件,用於快速生成框架頁面
2.抽離基類,以便於其餘項目中使用