GetX第三篇-依賴注入
爲什麼要使用依賴注入
依賴注入是什麼
本來接受各種參數來構造一個對象,現在只接受一個參數——已經實例化的對象。
依賴注入的目的
依賴注入是爲了將依賴組件的配置和使用分離開,以降低使用者與依賴之間的耦合度。
依賴注入的好處
實現依賴項注入可爲您帶來以下優勢:
- 重用代碼
更容易換掉依賴項的實現。由於控制反轉,代碼重用得以改進,並且類不再控制其依賴項的創建方式,而是支持任何配置。 - 易於重構
依賴項的創建分離,可以在創建對象時或編譯時進行檢查、修改,一處修改,使用處不需修改。 - 易於測試
類不管理其依賴項,因此在測試時,您可以傳入不同的實現以測試所有不同用例。
舉個例子
老王的瑪莎拉蒂需要換個v8引擎,他是自己拼裝個引擎呢還是去改裝店買一個呢?
如果自己拼裝個,引擎的構造更新了,他需要學習改進自己的技術,買新零件,而直接買一個成品,就是依賴注入。
class Car(private val engineParts: String,val enginePiston: String) {
fun start() {
val engine= Engine(engineParts,enginePiston)
engine.start()
}
}
class Engine(private val engineParts: String,val enginePiston: String){
}
上面代碼中的 Engine 類如果構造方法變動了,也需要去 Car 類裏更改。而使用依賴注入就不需要改動 Car 類。
手動實現依賴注入通常有兩種,構造函數傳入和字段傳入。
構造方法:
class Car(private val engine: Engine) {
fun start() {
engine.start()
}
}
fun main(args: Array) {
val engine = Engine()
val car = Car(engine)
car.start()
}
字段傳入:
class Car {
lateinit var engine: Engine
fun start() {
engine.start()
}
}
fun main(args: Array) {
val car = Car()
car.engine = Engine()
car.start()
}
上面雖然實現了依賴注入,但是增加了樣板代碼,如果注入實例多,也很麻煩。Android 上有 Dagger 和Hilt 實現自動注入, GetX 也給我們提供了 Binding 類實現。
使用依賴注入
Get有一個簡單而強大的依賴管理器,它允許你只用1行代碼就能檢索到 Controller 或者需要依賴的類,不需要提供上下文,不需要在 inheritedWidget 的子節點。
注入依賴:
Get.put<PutController>(PutController());
獲取依賴:
Get.find<PutController>();
就是這麼簡單。
Get.put()
這是個立即注入內存的注入方法。調用後已經注入到內存中。
Get.put<S>(
// 必備:要注入的類。
// 注:" S "意味着它可以是任何類型的類。
S dependency
// 可選:想要注入多個相同類型的類時,可以用這個方法。
// 比如有兩個購物車實例,就需要使用標籤區分不同的實例。
// 必須是唯一的字符串。
String tag,
// 可選:默認情況下,get會在實例不再使用後進行銷燬
// (例如:一個已經銷燬的視圖的Controller)
// 如果需要這個實例在整個應用生命週期中都存在,就像一個sharedPreferences的實例。
// 默認值爲false
bool permanent = false,
// 可選:允許你在測試中使用一個抽象類後,用另一個抽象類代替它,然後再進行測試。
// 默認爲false
bool overrideAbstract = false,
// 可選:允許你使用函數而不是依賴(dependency)本身來創建依賴。
// 這個不常用
InstanceBuilderCallback<S> builder,
)
permanent
是代表是否不銷燬。通常Get.put()
的實例的生命週期和 put 所在的 Widget 生命週期綁定,如果在全局 (main 方法裏)put,那麼這個實例就一直存在。如果在一個 Widget 裏 put ,那麼這個那麼這個 Widget 從內存中刪除,這個實例也會被銷燬。注意,這裏是刪除,並不是dispose
,具體看上一篇最後的部分。
Get.lazyPut
懶加載一個依賴,只有在使用時纔會被實例化。適用於不確定是否會被使用的依賴或者計算高昂的依賴。類似 Kotlin 的 Lazy 代理。
Get.lazyPut<LazyController>(() => LazyController());
LazyController 在這時候並不會被創建,而是等到你使用的時候纔會被 initialized
,也就是執行下面這句話的時候才 initialized
:
Get.find<LazyController>();
在使用後,使用時的 Wdiget 的生命週期結束,也就是這個 Widgetdispose
,這個實例就會被銷燬。
如果在一個 Widget 裏 find,然後退出這個 widget,此時這個實例也被銷燬,再進入另一個路由的 Widget,再次 find,GetX會打印錯誤信息,提醒沒有 put 。及時全局注入,也一樣。可以理解爲,Get.lazyPut
注入的實例的生命週期是和在Get.find
時的上下文所綁定。
如果想每次 find 獲取到不同的實例,可以藉助fenix
參數。
Get.lazyPut<S>(
// 必須:當你的類第一次被調用時,將被執行的方法。
InstanceBuilderCallback builder,
// 可選:和Get.put()一樣,當你想讓同一個類有多個不同的實例時,就會用到它。
// 必須是唯一的
String tag,
// 可選:下次使用時是否重建,
// 當不使用時,實例會被丟棄,但當再次需要使用時,Get會重新創建實例,
// 就像 bindings api 中的 "SmartManagement.keepFactory "一樣。
// 默認值爲false
bool fenix = false
)
Get.putAsync
注入一個異步創建的實例。比如SharedPreferences
。
Get.putAsync<SharedPreferences>(() async {
final sp = await SharedPreferences.getInstance();
return sp;
});
作用域參考Get.put
。
Get.create
這個方法可以創建很多實例。很少用到。可以當做Get.put
。
Bindings類
上面實現了依賴注入和使用,但是和前面講的手動注入一樣,爲了生命週期和使用的 Widget 綁定,需要在 Widget 裏注入和使用,並沒有完全解耦。要實現自動注入,我們就需要這個類。
這個包最大的區別之一,也許就是可以將路由、狀態管理器和依賴管理器完全集成。 當一個路由從Stack中移除時,所有與它相關的控制器、變量和對象的實例都會從內存中移除。如果你使用的是流或定時器,它們會自動關閉,我們不必擔心這些。Bindings 類是一個解耦依賴注入的類,同時 "Binding " 路由到狀態管理器和依賴管理器。 這使得 GetX 可以知道當使用某個控制器時,哪個頁面正在顯示,並知道在哪裏以及如何銷燬它。 此外,Bindings 類將允許我們利用 SmartManager 配置控制。
- 創建一個類並實現Binding
class InjectSimpleBinding implements Bindings {}
因爲Bindings
是抽象方法,所以要ide會提示要實現dependencies
。在裏面注入我們需要的實例:
class InjectSimpleBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<Api>(() => Api());
Get.lazyPut<InjectSimpleController>(() => InjectSimpleController());
}
}
- 通知路由,我們要使用該 Binding 來建立路由管理器、依賴關係和狀態之間的連接。
這裏有兩種方式,如果使用的是命名路由表:
GetPage(
name: Routes.INJECT,
page: () => InjectSimplePage(),
binding:InjectSimpleBinding(),
),
如果是直接跳轉:
Get.to(InjectSimplePage(), binding: InjectSimpleBinding());
現在,我們不必再擔心應用程序的內存管理,Get將爲我們做這件事。
上面我們注入依賴解耦了,但是獲取還是略顯不方便,GetX 也爲我們考慮到了。GetView
完美的搭配 Bindings。
class InjectSimplePage extends GetView<InjectSimpleController> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('MyPage')),
body: Center(
child: Obx(() => Text(controller.obj.toString())),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.getAge();
},
child: Icon(Icons.add),
),
);
}
}
這裏完全沒有Get.find
,但是可以直接使用controller
,因爲GetView
裏封裝好了:
abstract class GetView<T> extends StatelessWidget {
const GetView({Key key}) : super(key: key);
final String tag = null;
T get controller => GetInstance().find<T>(tag: tag);
@override
Widget build(BuildContext context);
}
不在需要 StatelessWidget 和 StatefulWidget。這也是開發最常用的模式,推薦大家使用。
當然,也許有時候覺得每次聲明一個 Bingings 類也很麻煩,那麼可以使用 BindingsBuilder ,這樣就可以簡單地使用一個函數來實例化任何想要注入的東西。
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() => {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
就是這麼簡單,Bingings 都不需要創建。兩種方式都可以,大家根據自己的編碼習慣選擇最適合的風格。
Bindings的工作原理
Bindings 會創建過渡性工廠,在點擊進入另一個頁面的那一刻,這些工廠就會被創建,一旦路由過渡動畫發生,就會被銷燬。 工廠佔用的內存很少,它們並不持有實例,而是一個具有我們想要的那個類的 "形狀"的函數。 這在內存上的成本很低,但由於這個庫的目的是用最少的資源獲得最大的性能,所以Get連工廠都默認刪除。
智能管理
GetX 默認情況下會將未使用的控制器從內存中移除。 但是如果想改變GetX控制類的銷燬方式怎麼辦呢,可以用SmartManagement 類設置不同的行爲。
如何改變
如果想改變這個配置(通常不需要),就用這個方法。
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilders //這裏
home: Home(),
)
)
}
SmartManagement.full
這是默認的。銷燬那些沒有被使用的、沒有被設置爲永久的類。在大多數情況下,我們都使用這個,不需要更改。-
SmartManagement.onlyBuilders
使用該選項,只有在init:
中啓動的控制器或用Get.lazyPut()
加載到Binding中的控制器纔會被銷燬。如果使用Get.put()或Get.putAsync()或任何其他方法,SmartManagement 沒有權限也就是不能移除這個依賴。
SmartManagement.keepFactory
就像SmartManagement.full一樣,當它不再被使用時,它將刪除它的依賴關係,但它將保留它們的工廠,這意味着如果再次需要該實例,它將重新創建該依賴關係。