GetX第三篇-依賴注入
為什么要使用依賴注入
依賴注入是什么
本來(lái)接受各種參數(shù)來(lái)構(gòu)造一個(gè)對(duì)象涩咖,現(xiàn)在只接受一個(gè)參數(shù)——已經(jīng)實(shí)例化的對(duì)象。
依賴注入的目的
依賴注入是為了將依賴組件的配置和使用分離開为居,以降低使用者與依賴之間的耦合度。
依賴注入的好處
實(shí)現(xiàn)依賴項(xiàng)注入可為您帶來(lái)以下優(yōu)勢(shì):
- 重用代碼
更容易換掉依賴項(xiàng)的實(shí)現(xiàn)讥巡。由于控制反轉(zhuǎn)肖爵,代碼重用得以改進(jìn),并且類不再控制其依賴項(xiàng)的創(chuàng)建方式悄泥,而是支持任何配置虏冻。 - 易于重構(gòu)
依賴項(xiàng)的創(chuàng)建分離,可以在創(chuàng)建對(duì)象時(shí)或編譯時(shí)進(jìn)行檢查弹囚、修改厨相,一處修改,使用處不需修改。 - 易于測(cè)試
類不管理其依賴項(xiàng)蛮穿,因此在測(cè)試時(shí)庶骄,您可以傳入不同的實(shí)現(xiàn)以測(cè)試所有不同用例。
舉個(gè)例子
老王的瑪莎拉蒂需要換個(gè)v8引擎践磅,他是自己拼裝個(gè)引擎呢還是去改裝店買一個(gè)呢单刁?
如果自己拼裝個(gè),引擎的構(gòu)造更新了府适,他需要學(xué)習(xí)改進(jìn)自己的技術(shù)羔飞,買新零件,而直接買一個(gè)成品檐春,就是依賴注入褥傍。
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 類如果構(gòu)造方法變動(dòng)了,也需要去 Car 類里更改喇聊。而使用依賴注入就不需要改動(dòng) Car 類恍风。
手動(dòng)實(shí)現(xiàn)依賴注入通常有兩種,構(gòu)造函數(shù)傳入和字段傳入誓篱。
構(gòu)造方法:
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()
}
上面雖然實(shí)現(xiàn)了依賴注入朋贬,但是增加了樣板代碼,如果注入實(shí)例多窜骄,也很麻煩锦募。Android 上有 Dagger 和Hilt 實(shí)現(xiàn)自動(dòng)注入, GetX 也給我們提供了 Binding 類實(shí)現(xiàn)邻遏。
使用依賴注入
Get有一個(gè)簡(jiǎn)單而強(qiáng)大的依賴管理器糠亩,它允許你只用1行代碼就能檢索到 Controller 或者需要依賴的類,不需要提供上下文准验,不需要在 inheritedWidget 的子節(jié)點(diǎn)赎线。
注入依賴:
Get.put<PutController>(PutController());
獲取依賴:
Get.find<PutController>();
就是這么簡(jiǎn)單。
Get.put()
這是個(gè)立即注入內(nèi)存的注入方法糊饱。調(diào)用后已經(jīng)注入到內(nèi)存中垂寥。
Get.put<S>(
// 必備:要注入的類。
// 注:" S "意味著它可以是任何類型的類另锋。
S dependency
// 可選:想要注入多個(gè)相同類型的類時(shí)滞项,可以用這個(gè)方法。
// 比如有兩個(gè)購(gòu)物車實(shí)例夭坪,就需要使用標(biāo)簽區(qū)分不同的實(shí)例文判。
// 必須是唯一的字符串。
String tag,
// 可選:默認(rèn)情況下室梅,get會(huì)在實(shí)例不再使用后進(jìn)行銷毀
// (例如:一個(gè)已經(jīng)銷毀的視圖的Controller)
// 如果需要這個(gè)實(shí)例在整個(gè)應(yīng)用生命周期中都存在戏仓,就像一個(gè)sharedPreferences的實(shí)例潭流。
// 默認(rèn)值為false
bool permanent = false,
// 可選:允許你在測(cè)試中使用一個(gè)抽象類后,用另一個(gè)抽象類代替它柜去,然后再進(jìn)行測(cè)試灰嫉。
// 默認(rèn)為false
bool overrideAbstract = false,
// 可選:允許你使用函數(shù)而不是依賴(dependency)本身來(lái)創(chuàng)建依賴。
// 這個(gè)不常用
InstanceBuilderCallback<S> builder,
)
permanent
是代表是否不銷毀嗓奢。通常Get.put()
的實(shí)例的生命周期和 put 所在的 Widget 生命周期綁定讼撒,如果在全局 (main 方法里)put,那么這個(gè)實(shí)例就一直存在股耽。如果在一個(gè) Widget 里 put 根盒,那么這個(gè)那么這個(gè) Widget 從內(nèi)存中刪除,這個(gè)實(shí)例也會(huì)被銷毀物蝙。注意炎滞,這里是刪除,并不是dispose
诬乞,具體看上一篇最后的部分册赛。
Get.lazyPut
懶加載一個(gè)依賴,只有在使用時(shí)才會(huì)被實(shí)例化震嫉。適用于不確定是否會(huì)被使用的依賴或者計(jì)算高昂的依賴森瘪。類似 Kotlin 的 Lazy 代理。
Get.lazyPut<LazyController>(() => LazyController());
LazyController 在這時(shí)候并不會(huì)被創(chuàng)建票堵,而是等到你使用的時(shí)候才會(huì)被 initialized
扼睬,也就是執(zhí)行下面這句話的時(shí)候才 initialized
:
Get.find<LazyController>();
在使用后,使用時(shí)的 Wdiget 的生命周期結(jié)束悴势,也就是這個(gè) Widgetdispose
窗宇,這個(gè)實(shí)例就會(huì)被銷毀。
如果在一個(gè) Widget 里 find特纤,然后退出這個(gè) widget军俊,此時(shí)這個(gè)實(shí)例也被銷毀,再進(jìn)入另一個(gè)路由的 Widget叫潦,再次 find蝇完,GetX會(huì)打印錯(cuò)誤信息官硝,提醒沒有 put 矗蕊。及時(shí)全局注入,也一樣氢架∩悼В可以理解為,Get.lazyPut
注入的實(shí)例的生命周期是和在Get.find
時(shí)的上下文所綁定岖研。
如果想每次 find 獲取到不同的實(shí)例卿操,可以借助fenix
參數(shù)警检。
Get.lazyPut<S>(
// 必須:當(dāng)你的類第一次被調(diào)用時(shí),將被執(zhí)行的方法害淤。
InstanceBuilderCallback builder,
// 可選:和Get.put()一樣扇雕,當(dāng)你想讓同一個(gè)類有多個(gè)不同的實(shí)例時(shí),就會(huì)用到它窥摄。
// 必須是唯一的
String tag,
// 可選:下次使用時(shí)是否重建镶奉,
// 當(dāng)不使用時(shí),實(shí)例會(huì)被丟棄崭放,但當(dāng)再次需要使用時(shí)哨苛,Get會(huì)重新創(chuàng)建實(shí)例,
// 就像 bindings api 中的 "SmartManagement.keepFactory "一樣币砂。
// 默認(rèn)值為false
bool fenix = false
)
Get.putAsync
注入一個(gè)異步創(chuàng)建的實(shí)例建峭。比如SharedPreferences
。
Get.putAsync<SharedPreferences>(() async {
final sp = await SharedPreferences.getInstance();
return sp;
});
作用域參考Get.put
决摧。
Get.create
這個(gè)方法可以創(chuàng)建很多實(shí)例亿蒸。很少用到≌谱可以當(dāng)做Get.put
祝懂。
Bindings類
上面實(shí)現(xiàn)了依賴注入和使用,但是和前面講的手動(dòng)注入一樣拘鞋,為了生命周期和使用的 Widget 綁定砚蓬,需要在 Widget 里注入和使用,并沒有完全解耦盆色。要實(shí)現(xiàn)自動(dòng)注入灰蛙,我們就需要這個(gè)類。
這個(gè)包最大的區(qū)別之一隔躲,也許就是可以將路由摩梧、狀態(tài)管理器和依賴管理器完全集成。 當(dāng)一個(gè)路由從Stack中移除時(shí)宣旱,所有與它相關(guān)的控制器仅父、變量和對(duì)象的實(shí)例都會(huì)從內(nèi)存中移除。如果你使用的是流或定時(shí)器浑吟,它們會(huì)自動(dòng)關(guān)閉笙纤,我們不必?fù)?dān)心這些。Bindings 類是一個(gè)解耦依賴注入的類组力,同時(shí) "Binding " 路由到狀態(tài)管理器和依賴管理器省容。 這使得 GetX 可以知道當(dāng)使用某個(gè)控制器時(shí),哪個(gè)頁(yè)面正在顯示燎字,并知道在哪里以及如何銷毀它腥椒。 此外阿宅,Bindings 類將允許我們利用 SmartManager 配置控制。
- 創(chuàng)建一個(gè)類并實(shí)現(xiàn)Binding
class InjectSimpleBinding implements Bindings {}
因?yàn)?code>Bindings是抽象方法笼蛛,所以要ide會(huì)提示要實(shí)現(xiàn)dependencies
洒放。在里面注入我們需要的實(shí)例:
class InjectSimpleBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<Api>(() => Api());
Get.lazyPut<InjectSimpleController>(() => InjectSimpleController());
}
}
- 通知路由,我們要使用該 Binding 來(lái)建立路由管理器滨砍、依賴關(guān)系和狀態(tài)之間的連接拉馋。
這里有兩種方式,如果使用的是命名路由表:
GetPage(
name: Routes.INJECT,
page: () => InjectSimplePage(),
binding:InjectSimpleBinding(),
),
如果是直接跳轉(zhuǎn):
Get.to(InjectSimplePage(), binding: InjectSimpleBinding());
現(xiàn)在惨好,我們不必再擔(dān)心應(yīng)用程序的內(nèi)存管理煌茴,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
,因?yàn)?code>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分歇。這也是開發(fā)最常用的模式傀蓉,推薦大家使用。
當(dāng)然职抡,也許有時(shí)候覺得每次聲明一個(gè) Bingings 類也很麻煩葬燎,那么可以使用 BindingsBuilder ,這樣就可以簡(jiǎn)單地使用一個(gè)函數(shù)來(lái)實(shí)例化任何想要注入的東西缚甩。
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() => {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
就是這么簡(jiǎn)單谱净,Bingings 都不需要?jiǎng)?chuàng)建。兩種方式都可以擅威,大家根據(jù)自己的編碼習(xí)慣選擇最適合的風(fēng)格壕探。
Bindings的工作原理
Bindings 會(huì)創(chuàng)建過(guò)渡性工廠,在點(diǎn)擊進(jìn)入另一個(gè)頁(yè)面的那一刻郊丛,這些工廠就會(huì)被創(chuàng)建李请,一旦路由過(guò)渡動(dòng)畫發(fā)生,就會(huì)被銷毀厉熟。 工廠占用的內(nèi)存很少导盅,它們并不持有實(shí)例,而是一個(gè)具有我們想要的那個(gè)類的 "形狀"的函數(shù)庆猫。 這在內(nèi)存上的成本很低认轨,但由于這個(gè)庫(kù)的目的是用最少的資源獲得最大的性能,所以Get連工廠都默認(rèn)刪除月培。
智能管理
GetX 默認(rèn)情況下會(huì)將未使用的控制器從內(nèi)存中移除嘁字。 但是如果想改變GetX控制類的銷毀方式怎么辦呢,可以用SmartManagement 類設(shè)置不同的行為杉畜。
如何改變
如果想改變這個(gè)配置(通常不需要)纪蜒,就用這個(gè)方法。
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilders //這里
home: Home(),
)
)
}
SmartManagement.full
這是默認(rèn)的此叠。銷毀那些沒有被使用的纯续、沒有被設(shè)置為永久的類。在大多數(shù)情況下灭袁,我們都使用這個(gè)猬错,不需要更改。-
SmartManagement.onlyBuilders
使用該選項(xiàng)茸歧,只有在init:
中啟動(dòng)的控制器或用Get.lazyPut()
加載到Binding中的控制器才會(huì)被銷毀倦炒。如果使用Get.put()或Get.putAsync()或任何其他方法,SmartManagement 沒有權(quán)限也就是不能移除這個(gè)依賴软瞎。
SmartManagement.keepFactory
就像SmartManagement.full一樣逢唤,當(dāng)它不再被使用時(shí),它將刪除它的依賴關(guān)系涤浇,但它將保留它們的工廠鳖藕,這意味著如果再次需要該實(shí)例,它將重新創(chuàng)建該依賴關(guān)系只锭。