上篇文章講的是狀態(tài)管理,提到了 Flutter BLoC
敌买,相比與原生的 setState()
及Provider
等有哪些優(yōu)缺點(diǎn),并結(jié)合實(shí)際項(xiàng)目寫了一個(gè)簡(jiǎn)單的使用,接下來本篇文章來講 Flutter
大型項(xiàng)目是如何進(jìn)行分層設(shè)計(jì)的潜叛,費(fèi)話不多說,直接進(jìn)入正題哈壶硅。
為啥需要分層設(shè)計(jì)
其實(shí)這個(gè)沒有啥固定答案威兜,也許只是因?yàn)槟骋惶炜吹绞掷锏拇a如同屎山一樣,如下圖庐椒,而隨著業(yè)務(wù)功能的增加椒舵,不停的往這上面堆,這個(gè)屎山也會(huì)愈發(fā)龐大和混亂约谈,如果這樣繼續(xù)下去笔宿,直到某一天因?yàn)橐粋€(gè)小小的Bug犁钟,你需要花半天的時(shí)間來排查問題出在哪里,最后當(dāng)你覺得問題終于改好了的時(shí)候泼橘,卻不料碰了不該碰的地方涝动,結(jié)果就是 fixing 1 bug will create 10 new bugs
,甚至程序的崩潰炬灭。
隨著這種問題的凸顯醋粟,于是團(tuán)隊(duì)里的顯眼包A提出了要求團(tuán)隊(duì)里的每個(gè)人都必須負(fù)責(zé)完成給自己寫的代碼添加注釋和文檔,規(guī)范命名等措施重归,一段時(shí)間后米愿,發(fā)現(xiàn)代碼是規(guī)范了,但問題依然存在提前,這時(shí)候才發(fā)現(xiàn)如果工程的架構(gòu)分層沒有做好吗货,再規(guī)范的代碼和注釋也只是在屎山上雕花,治標(biāo)不治本而已狈网。
請(qǐng)?jiān)徫掖蛄艘粋€(gè)這么俗的比方宙搬,但話糙理不糙,那么啥是應(yīng)用的分層設(shè)計(jì)呢拓哺?
簡(jiǎn)單的來說勇垛,應(yīng)用的分層設(shè)計(jì)是一種將應(yīng)用程序劃分為不同層級(jí)的方法,每個(gè)層級(jí)負(fù)責(zé)特定的功能或責(zé)任士鸥。其中表示層(Presentation Layer
)負(fù)責(zé)用戶界面和用戶交互闲孤,將數(shù)據(jù)呈現(xiàn)給用戶并接收用戶輸入;業(yè)務(wù)邏輯層(Business Logic Layer
)處理應(yīng)用程序的業(yè)務(wù)邏輯烤礁,包括數(shù)據(jù)驗(yàn)證讼积、處理和轉(zhuǎn)換;數(shù)據(jù)訪問層(Data Access Layer
)負(fù)責(zé)與數(shù)據(jù)存儲(chǔ)交互脚仔,包括數(shù)據(jù)庫或文件系統(tǒng)的讀取和寫入操作勤众。
這樣做有什么好處呢?一句話總結(jié)就是為了讓代碼層級(jí)責(zé)任清晰鲤脏,維護(hù)们颜、擴(kuò)展和重用方便,每個(gè)模塊能獨(dú)立開發(fā)猎醇、測(cè)試和修改窥突。
原生 App
開發(fā)的分層設(shè)計(jì)
說到 iOS
、Android
的分層設(shè)計(jì)硫嘶,就會(huì)想到如 MVC
阻问、MVVM
等,它們主要是圍繞著控制器層(Controller
)沦疾、視圖層(View
)称近、和數(shù)據(jù)層(Model
)贡蓖,還有連接 View
和 Model
之間的模型視圖層(ViewModel
)這些來講的。
然而煌茬,MVC
、MVVM
概念還不算完整的分層架構(gòu)彻桃,它們只是關(guān)注的 App
分層設(shè)計(jì)當(dāng)中的應(yīng)用層(Applicaiton Layer
)組織方式坛善,對(duì)于一個(gè)簡(jiǎn)單規(guī)模較小的App來說,可能單單一個(gè)應(yīng)用層就能搞定邻眷,不用擔(dān)心業(yè)務(wù)增量和復(fù)雜度上升對(duì)后期開發(fā)的壓力眠屎,而一旦 App
上了規(guī)模之后就有點(diǎn)應(yīng)付不過來了。
當(dāng) App
有了一定規(guī)模之后肆饶,必然會(huì)涉及到分層的設(shè)計(jì)改衩,還有模塊化、Hybrid
機(jī)制驯镊、數(shù)據(jù)庫葫督、跨項(xiàng)目開發(fā)等等,拿 iOS
的原生分層設(shè)計(jì)落地實(shí)踐來說板惑,通常會(huì)將工程拆分成多個(gè)Pod
私有庫組件橄镜,拆分的標(biāo)準(zhǔn)視情況而定,每一個(gè)分層組件是獨(dú)立的開發(fā)和測(cè)試冯乘,再在主工程添加Pod
私有庫依賴來做分層設(shè)計(jì)開發(fā)洽胶。
此處應(yīng)該有 Pod
分層組件化設(shè)計(jì)的配圖,但是太懶了裆馒,就沒有一個(gè)個(gè)的去搭建新項(xiàng)目和 Pod
私有庫姊氓,不過 iOS
原生分層設(shè)計(jì)不是本篇文章的重點(diǎn),本篇主要談?wù)摰氖?Flutter App
的分層設(shè)計(jì)喷好。
Flutter
的分層設(shè)計(jì)
分層架構(gòu)設(shè)計(jì)的理念其實(shí)是相通的翔横,差別在于語言的特性和具體項(xiàng)目實(shí)施上,Flutter
項(xiàng)目也是如此绒窑。試想一下棕孙,當(dāng)各種邏輯混合在一次的時(shí)候,即便是選擇了像 Bloc
這樣的狀態(tài)管理框架來隔離視圖層和邏輯實(shí)現(xiàn)層些膨,也很難輕松的增強(qiáng)代碼的拓展性蟀俊,這時(shí)候選擇采用一個(gè)干凈的分層架構(gòu)就顯得尤為重要,怎樣做到這一點(diǎn)呢订雾,就需要將代碼分成獨(dú)立的層肢预,并依賴于抽象而不是具體的實(shí)現(xiàn)。
Flutter App
想要實(shí)現(xiàn)分層設(shè)計(jì)洼哎,就不得不提到包管理工具烫映,如果在將所有分層組件代碼放在主工程里面沼本,那樣并不能達(dá)到每個(gè)組件單獨(dú)開發(fā)、維護(hù)和測(cè)試的目的锭沟,而如果放在新建的 Dart Package
中抽兆,沒發(fā)跨多個(gè)組件改代碼和測(cè)試,無法實(shí)現(xiàn)本地包鏈接和安裝族淮。使用 melos 就能解決這個(gè)問題辫红,類似于 iOS
包管理工具 Pod
, 而 melos
是 Flutter
項(xiàng)目的包管理工具。
組件包管理工具
-
安裝
Melos
祝辣,將Melos
安裝為全局包贴妻,這樣整個(gè)系統(tǒng)環(huán)境都可以使用:dart pub global activate melos
-
創(chuàng)建
workspace
文件夾,我這里命名為flutter_architecture_design
蝙斜,添加melos
的配置文件melos.yaml
和pubspec.yaml
名惩,其目錄結(jié)構(gòu)大概是這樣的:flutter_architecture_design ├── melos.yaml ├── pubspec.yaml └── README.md
-
新建組件,以開發(fā)工具
Android Studio
為例孕荠,選擇File
->New
->New Flutter Project
娩鹉,根據(jù)需要?jiǎng)?chuàng)建組件包,需要注意的是組件包存放的位置要放在workspace
目錄中稚伍。
新建組件 -
編輯
melos.yaml
配置文件底循,將上一步新建的組件包名放在packages
之下,添加scripts
相關(guān)命令槐瑞,其目的請(qǐng)看下一步:name: flutter_architecture_design packages: - widgets/** - shared/** - data/** - initializer/** - domain/** - resources/** - app/** command: bootstrap: usePubspecOverrides: true scripts: analyze: run: dart pub global run melos exec --flutter "flutter analyze --no-pub --suppress-analytics" description: Run analyze. pub_get: run: dart pub global run melos exec --flutter "flutter pub get" description: pub get build_all: run: dart pub global run melos exec --depends-on="build_runner" "flutter packages pub run build_runner build --delete-conflicting-outputs" description: build_runner build all modules. build_data: run: dart pub global run melos exec --fail-fast --scope="*data*" --depends-on="build_runner" "flutter packages pub run build_runner build --delete-conflicting-outputs" description: build_runner build data module. build_domain: run: dart pub global run melos exec --fail-fast --scope="*domain*" --depends-on="build_runner" "flutter packages pub run build_runner build --delete-conflicting-outputs" description: build_runner build domain module. build_app: run: dart pub global run melos exec --fail-fast --scope="*app*" --depends-on="build_runner" "flutter packages pub run build_runner build --delete-conflicting-outputs" description: build_runner build app module. build_shared: run: dart pub global run melos exec --fail-fast --scope="*shared*" --depends-on="build_runner" "flutter packages pub run build_runner build --delete-conflicting-outputs" description: build_runner build shared module. build_widgets: run: dart pub global run melos exec --fail-fast --scope="*widgets*" --depends-on="build_runner" "flutter packages pub run build_runner build --delete-conflicting-outputs" description: build_runner build shared module.
-
打開命令行熙涤,切換到
workspace
目錄,也就是flutter_architecture_design
目錄困檩,執(zhí)行命令祠挫。melos bootstrap
出現(xiàn)
SUCCESS
之后,現(xiàn)在的目錄結(jié)構(gòu)是這樣的:
目錄結(jié)構(gòu)
- 點(diǎn)擊
Android Studio
的add configuration
悼沿,將下圖中的Shell Scripts
選中后點(diǎn)擊OK
等舔。
以上的 Scripts
添加完后就可以在這里看到了,操作起來也很方便糟趾,不需要去命令行那里執(zhí)行命令慌植。
Flutter
分層設(shè)計(jì)實(shí)踐
接下來介紹一下上面創(chuàng)建的幾個(gè)組件庫。
-
app
:項(xiàng)目的主工程义郑,存放業(yè)務(wù)邏輯代碼蝶柿、UI
頁面和Bloc
,還有styles
非驮、colors
等等交汤。 -
domain
:實(shí)體類(entity
)組件包,還有一些接口類劫笙,如repository
芙扎、usercase
等星岗。 -
data
:數(shù)據(jù)提供組件包,主要有:api_request
戒洼,database
俏橘、shared_preference
等,該組件包所有的調(diào)用實(shí)現(xiàn)都在domain
中接口repository
的實(shí)現(xiàn)類repository_impl
中圈浇。 -
shared
:工具類組件包敷矫,包括:util
、helper
汉额、enum
、constants
榨汤、exception
蠕搜、mixins
等等。 -
resources
:資源類組件包收壕,有intl
妓灌、公共的images
等 -
initializer
:模塊初始化組件包。 -
widgets
:公共的UI
組件包蜜宪,如常用的:alert
虫埂、button
、toast
圃验、slider
等等掉伏。
它們之間的調(diào)用關(guān)系如下圖:
其中 shared
和 resources
作為基礎(chǔ)組件包,本身不依賴任何組件澳窑,而是給其它組件包提供支持斧散。
作為主工程 App
也不會(huì)直接依賴 data
組件包,其調(diào)用是通過 domain
組件包中 UseCase
來實(shí)現(xiàn)摊聋,在 UseCase
會(huì)獲取數(shù)據(jù)鸡捐、處理列表數(shù)據(jù)的分頁、參數(shù)校驗(yàn)麻裁、異常處理等等箍镜,獲取數(shù)據(jù)是通過調(diào)用抽象類 repository
中相關(guān)函數(shù),而不是直接調(diào)用具體實(shí)現(xiàn)類煎源,此時(shí)App
的 pubspec.yaml
中配置是這樣的:
name: app
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ">=2.17.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
widgets:
path: ../widgets
shared:
path: ../shared
domain:
path: ../domain
resources:
path: ../resources
initializer:
path: ../initializer
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
generate: false
assets:
- assets/images/
提供的數(shù)據(jù)組件包 data
實(shí)現(xiàn)了抽象類 repository
中相關(guān)函數(shù)色迂,只負(fù)責(zé)調(diào)用 Api 接口獲取數(shù)據(jù),或者從數(shù)據(jù)庫獲取數(shù)據(jù)手销。當(dāng)上層調(diào)用的時(shí)候不需要關(guān)心數(shù)據(jù)是從哪里來的脚草,全部交給 data
組件包負(fù)責(zé)。
initializer
作為模塊初始化組件包原献,僅有一個(gè) AppInitializer
類馏慨,其主要目的是將其它的模塊的初始化收集起來放在 AppInitializer
類中 init()
函數(shù)中埂淮,然后在主工程入口函數(shù):main()
調(diào)用這個(gè) init()
函數(shù),常見的初始化如:GetIt
初始化写隶、數(shù)據(jù)庫 objectbox
初始化倔撞、SharedPreferences
初始化,這些相關(guān)的初始會(huì)分布在各自的組件包中慕趴。
class AppInitializer {
AppInitializer();
Future<void> init() async {
await SharedConfig.getInstance().init();
await DataConfig.getInstance().init();
await DomainConfig.getInstance().init();
}
}
widgets
作為公共的 UI 組件庫痪蝇,不處理業(yè)務(wù)邏輯,在多項(xiàng)目開發(fā)時(shí)經(jīng)常會(huì)使用到冕房。上圖中的 Other Plugin Module
指的的是其它組件包躏啰,特別是需要單獨(dú)開發(fā)與原生交互的插件時(shí)會(huì)用到,
這種分層設(shè)計(jì)出來的架構(gòu)或許在開發(fā)過程中帶來一下不便耙册,如調(diào)用一個(gè)接口给僵,第一步:需要先在抽象類 repository
寫好函數(shù)聲明;第二步:然后再去Api Service
寫具體請(qǐng)求代碼详拙,并在repository_impl
實(shí)現(xiàn)類中調(diào)用帝际;第三步:還需要在 UserCase
去做業(yè)務(wù)調(diào)用,錯(cuò)誤處理等饶辙;最后一步:在bloc
的event
中調(diào)用蹲诀。這么一趟下來,確實(shí)有些繁瑣或者說是過度設(shè)計(jì)弃揽。但是如果維度設(shè)定在大的項(xiàng)目中多人合作開發(fā)的時(shí)候脯爪,卻能規(guī)避很多問題,每個(gè)分層組件都有自己的職責(zé)互不干擾矿微,都支持單獨(dú)的開發(fā)測(cè)試披粟,盡可能的做到依賴于抽象而不是具體的實(shí)現(xiàn)。
本篇文章就到這里冷冗,源碼會(huì)在后面這個(gè)系列的文章里放出來守屉,感謝您的閱讀!