嗨嗨大家!👋 讓我們今天一起再跳入 Flutter 的魔法世界,不過這次我們的主題會稍微進階一些——「Clean Architecture」。記得昨天我們聊到了 SOLID 原則嗎?那只是個開始。如果你正在想怎麼讓你的 Flutter 項目更加組織有序、更加容易維護,這篇文章是給你的。
在昨天的介紹裡,我們深入探討了 SOLID 原則,了解了它如何成為軟體設計的核心基石。今天,我們要進一步看看如何在 Flutter 中實踐 Clean Architecture。首先,我們要明確 Clean Architecture 的目的:它希望能建立一個獨立於框架、可測試、獨立於 UI 的架構。這樣一來,應用程式的業務邏輯和外部界面就能夠獨立發展、維護。
一樣先開始介紹先擺上最常見的這張圈圈圖,理解 Flutter 的功能與 Clean Architecture 之間的對應關係。
下面我們拿購物車來做一個完整舉例:
💡 完成購物車必須具備以下的任務,選擇商品與數量 → 點擊確認 → 完成更新購物車
首先最重要的實體就是 購物車,因此我們會定義一個 model cart.dart
,代表最核心的實體。
class Cart {
Map<Item,int> cartStorage;
}
接下來考慮 use case,也就是我們的購物車會有哪些要完成的功能,寫成 cart_service_interface.dart
,裡面包含 CartService 需要的功能,這裡的例子就是更新購物車。
寫成 abstract 的好處也有助於我們在開始完善程式碼前,先想清楚這個 Service 會有哪些功能需要被完善。甚至你在完善 Repository 之前,也可以先寫上然後後續再補上 Repostory 。
abstract class CartService {
void updateCart(Item, amount);
List<Item> getItems();
}
class CartService {
CartService(cartRepository);
List<Item> getItems(){
return cartRepository.getItems();
}
void updateCart(item, amount) {
cartRepository.updateCart(item, amount);
}
}
在實作中加入 repository 操作 db(local) 或 api(remote)
abstract class CartRepository {
void saveChart(List<Item> items);
List<Item> getItems();
}
class LocalCartRepository implements CartRepository {
@override
void saveCart(List<Item> items) {
// 在這裡實現 DB 存儲的邏輯
}
@override
void getItems(List<Item> items) {
// 在這裡實現從 DB 取得購物車資料
}
}
最後在我們來完善 Controller,想像一下用戶實際操作 UI 會是什麼樣的場景,可能他會有一個加入購物車的按鈕,所以我們加上 addCart 的方法,檢查一下操作是否正常,最後更新資料庫。這裡假設如果他要加入購物車的數量,已經大於商品本身的剩餘數量,會拋出錯誤。
class CartController {
Future addCart(Item item,int amount) async {
if(amount > item.avalible){
throw ItemNotEnoughException();
}
await cartService.updateCart(item, amount);
}
}
UI 會根據 Controller 收到的訊息來顯示
TextButton(
onPressed: () async {
try {
await cartcontroller.addCart(item, amount);
showToast("商品已加入");
} on ItemNotEnoughException {
showToast("選擇數量大於剩餘數量");
}catch (e) {
showToast("加入購物車失敗");
}
},
child: const Text(
"加入購物車",
),
);
至此我們完成了一個完整的功能模組,當然在每個專案不同搭配上不同的管理工具,在使用上可能會有些許變化,但都可以遵循上面的原則去完成。
上面實踐的方法,有借鑑和參考這篇文章,強烈建議大家有空也可以去拜讀,會收穫滿滿。
https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/
講完程式碼,比程式碼本身更抽象的就是資料夾結構,這裡當作額外 Bonus 介紹幾種資料夾結構跟大家分享。
優點:
缺點:
lib/
│
├── ui/
│ ├── pages/
│ │ ├── home_page.dart
│ │ └── ...
│ └── widgets/
│
├── application/
│ ├── service/
│ └── repository_interfaces/
│
├── data/
│ ├── models/
│ ├── datasources/
│ └── repositories/
│
├── infrastructure/
│ ├── network/
│ ├── local_db/
│ └── ...
│
└── main.dart
優點:
缺點:
lib/
│
├── features/
│ ├── feature1/
│ │ ├── data/
│ │ ├── application/
│ │ │ ├── service/
│ │ ├── domain/
│ │ └── presentation/
│ ├── feature2/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ └── ...
│
├── core/
│ ├── errors/
│ ├── service/
│ ├── entities/
│ └── ...
│
└── main.dart
目前我們的團隊是採用第二種 Feature-Based 結構為基礎,確實一定程度上可以讓分工更容易,但這完全取決於你們不同的工作模式,歡迎大家參考分享。
寫到這裡,希望透過這篇文章,你能夠有一個更深入的認識和了解 Clean Architecture 在 Flutter 的應用。正如我們所見,將清晰的架構帶入到 Flutter 中,不僅使得代碼更加模組化、可維護,還能夠確保業務邏輯和 UI 之間的獨立性。但這只是冰山一角,實際在項目中可能會遇到更多複雜的場景和挑戰。無論如何,只要堅持學習和實踐,我們總會找到最適合的方案!期待下次再和大家分享更多的知識和經驗!🚀👩💻👨💻🎉