1. 程式人生 > >Android快速依賴注入框架Dagger2使用1

Android快速依賴注入框架Dagger2使用1

一、啥是Dagger2

Dagger2的內容有點多,一點得有耐心。

1.1 簡介

Dagger2是一個Android/Java平臺上快速依賴注入框架,由谷歌開發,最早的版本Dagger1 由Square公司開發。依賴注入框架主要用於模組間解耦,提高程式碼的健壯性和可維護性。

幾大優點:

  • 全域性物件例項的簡單訪問方式,@Inject
  • 複雜的依賴關係只需要簡單的配置
  • 讓單元測試和整合測試更加方便
  • 輕鬆指定作用域

1.1 主要元素

關係圖:
這裡寫圖片描述

主要元素有以下三個:

  • Container: 相當於Android的Activity,在activity裡面獲取其他類的例項

  • Component: 一個介面,告訴activty你要獲取例項的類在哪裡找

  • Module: activty要的東西就在這裡初始化。

1.2 主要註解

看不懂下面的註解可以先看例子使用了,然後回來看就懂了。還有其他註解,在後面會講到。

1. @Inject

通常在需要依賴的地方使用這個註解。你用它告訴Dagger這個類或欄位需要依賴注入,,Dagger就會構建一個這個類的例項並滿足他們的依賴。

2. @Module

用來修飾modules類。 所以我們定義一個類,用@Module註解,這樣Dagger在構造類的例項時候,就知道從哪裡去找到需要的依賴。modules的一個重要特徵是它們設計為分割槽並組合在一起(比如說,我們的app中可以有很多在一起的modules)

3. @Provide

我們在modules中定義的方法就是是用這個註解來修飾,以此來告訴Dagger我們想要構造物件並提供這些依賴。

4. @Component

Components從根本上來說就是一個注入器,也可以說是@Inject@Module的橋樑,他的主要作用就是連結這兩個部分。Components可以提供所有定義了的型別的例項,比如: 我們必須用@Component註解一個介面然後列出所有的

二、Dagger2基本使用

2.1 導包

在專案的build.gradle新增:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
//下面新增apt classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } ....

在需要的Module的build.gradle新增兩個部分:

apply plugin: 'com.android.application'
//新增的第一部分
apply plugin: 'com.neenbedankt.android-apt'
android {
    ......
}

dependencies {
    ......

    //新增的第二部分,版本太高會導致編譯失敗,這裡用2.8就可以了
    compile 'com.google.dagger:dagger:2.8'      //dagger的api
    apt "com.google.dagger:dagger-compiler:2.8"  //指定註解處理器
    compile 'org.glassfish:javax.annotation:10.0-b28'  //Adnroid缺失的部分javax註解
}

2.2 建立你要例項化的類

這裡假如我想在Activity裡面例項化一個LoginCtrl的類。於是建立一個LoginCtrl類

public class LoginCtrl {


    public void login(String name,String pass){
        Log.e("[email protected]@", "name:"+name+" pass:"+pass);
    }

}

2.3 建立module類

在這個類裡面 真正的例項化LoginCtrl類。建立一個LoginModule類,類使用@Module修飾,然後裡面新增方法provideLoginCtrl (方法名隨便),返回型別為LoginCtrl,使用@Provides修飾方法名。如下:

@Module
public class LoginModule {

    @Provides LoginCtrl provideLoginCtrl(){
        return new LoginCtrl();
    }

}

2.4 建立Component介面

建立一個LoginComponent介面,用來告訴Activity你要例項化的東西在這裡。 @Component的引數提供Module進行聯絡, 接口裡面的方法和activty進行聯絡。 這樣形成了橋樑

PS:注意接口裡面的方法必須要有引數,不然會編譯錯誤

@Component(modules = LoginModule.class)
public interface LoginComponent {

    //要新增方法,方法必須新增引數,引數型別必須和呼叫時候一致
    void inject(TestActivity activity);

}

2.5 構建

搞完上面的步驟之後,點選選單欄的Build -> Build Project 或者 Build Module。稍等一會兒。 等構建完成之後,就會在module -> 的build -> generated -> source -> apt -> debug -> 包名/路徑 -> 看到生成對應的檔案
這裡寫圖片描述

2.6 在Activity注入

接下來就可以使用了,在activity中使用@Inject修飾你要例項化的類,然後使用類Dagger+Compontent介面的類名初始化Dagger,就成功注入了,我這裡是新建的一個TestAtivity。

public class TestActivity extends Activity {


    @Inject
    LoginCtrl loginCtrl;    //注入的方式例項化LoginCtrl

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化注入,inject是LoginComponent接口裡面自定義的方法;
        DaggerLoginComponent.create().inject(this);

        // 然後就可以呼叫loginCtrl裡面的方法了  
        loginCtrl.login("tianping","pass");

    }
}

就可以看到輸出

com.tpnet.dagger2test E/[email protected]@: name:tianping pass:pass

PS注意:Component介面方法裡面的引數,在Activity傳遞的時候必須型別一致,MainActivity就是MainActivity.this。如果引數型別是Context,你傳遞了MainActivity.this過去就會導致注入失敗,例項化物件為空。

三、Module引數傳遞

把上面的栗子修改一下,新增兩個類,在LoginCrtl裡面進行控制這兩個類。

LoginStore.java類,看作為本地儲存登入資訊的類。

public class LoginStore {


    private Context mContext;

    public LoginStore(Context mContext) {
        this.mContext = mContext;
    }

    public void login(String name,String pass){
        Log.e("@@", "LoginStore進行儲存: name="+name+",pass="+pass);
        SharedPreferences.Editor editor = mContext.getSharedPreferences("login",Context.MODE_PRIVATE).edit();
        editor.putString("name",name);
        editor.putString("pass",pass);
        editor.apply();
    }
}

LoginService.java類,看作為連結網路登入的類。

public class LoginService {

    public void login(String name,String pass){
        //網路請求登入....
        Log.e("@@", "LoginService登入: name="+name+",pass="+pass);

    }

}

LoginCtrl修改為:

public class LoginCtrl {


    private LoginStore mLoginStore;
    private LoginService mLoginService;

    public LoginCtrl(LoginService service, LoginStore store) {
        this.mLoginStore = store;
        this.mLoginService = service;
    }

    public void login(String name,String pass){

        mLoginService.login(name,pass);

        mLoginStore.login(name,pass);
    }

}

LoginModule修改為:

@Module
public class LoginModule {

    private Context mContext;   //供給LoginStore使用


    public LoginModule(Context mContext) {
        this.mContext = mContext;
    }

    @Provides LoginCtrl provideLoginCtrl(LoginService service, LoginStore store){
        return new LoginCtrl(service,store);
    }

}

3.1 Module構造方法傳參

看了上面修改完之後的程式碼,LoginModule類裡面需要在構造方法裡面傳參,怎麼傳呢?
在activity初始化的時候使用builder:

public class TestActivity extends Activity {


    @Inject
    LoginCtrl loginCtrl;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //DaggerLoginComponent.create().inject(this);

        //當Module需要構造方法傳參的時候,使用builder的方式初始化Dagger。
        DaggerLoginComponent.builder()
                .loginModule(new LoginModule(this))  //loginModule這個方法是構建之後才有的
                .build()
                .inject(this);


        loginCtrl.login("天平","密碼");

    }

}

總結就是:
當Module需要構造方法傳參的時候,使用builder的方式初始化Dagger

3.2 module裡面的方法引數

上面的修改完的程式碼執行肯定會報錯的,報錯資訊如下:

原因就是在LoginModule裡面,provideLoginCtrl方法有兩個引數:LoginService和LoginStore,這倆引數並沒有註解例項化,所以這裡就報錯了。 解決方法是下面兩個。

  • 通過構造方法@Inject
  • 在Module類裡面@Provide

3.2.1 通過構造方法Inject

module裡面的方法需要引數的解決方法1: 通過構造方法@Inject

又分兩種情況,帶引數的構造方法和不帶引數的。

  1. 不帶引數的

如果Moudle裡面的方法的引數這個類的構造方法不需要引數的,直接在構造方法新增@Inject即可。

例如Loginservice,在LoginService裡新增一個Inject構造方法:

public class LoginService {

    //構造方法沒有引數的,直接在構造方法用@Injext修飾即可
    @Inject
    public LoginService() {

    }

    public void login(String name, String pass){
        //網路請求登入....
        Log.e("@@", "LoginService登入: name="+name+",pass="+pass);

    }

}
  1. 帶引數的

如果Moudle裡面的方法的引數這個類的構造方法需要帶引數的。例如LoginStore,構造方法需要提供Context引數。Inject之後,還需要在Module類裡面@Provide一個String型別的方法,作為LoginStore構造方法的引數

LoginStore.java修改為:

public class LoginStore {

    private Context mContext;

    //這裡@Inject構造方法,然後在Module類裡面還需要Provide一個String的方法
    @Inject
    public LoginStore(Context mContext) {
        this.mContext = mContext;
    }

    public void login(String name,String pass){
        Log.e("@@", "LoginStore進行儲存: name="+name+",pass="+pass);
        SharedPreferences.Editor editor = mContext.getSharedPreferences("login",Context.MODE_PRIVATE).edit();
        editor.putString("name",name);
        editor.putString("pass",pass);
        editor.apply();
    }
}

LoginModule.java修改為i:

@Module
public class LoginModule {

    private Context mContext;   //供給LoginStore使用


    public LoginModule(Context mContext) {
        this.mContext = mContext;
    }

    //為LoginStore提供構造引數
    @Provides Context provideStoreContext(){
        return mContext;
    }

    @Provides LoginCtrl provideLoginCtrl(LoginService service, LoginStore store){
        return new LoginCtrl(service,store);
    }

}

好了,程式正常,這時候執行程式就會看到輸出:

03-04 19:14:00.124 31170-31170/? E/@@: LoginService登入: name=天平,pass=密碼
03-04 19:14:00.124 31170-31170/? E/@@: LoginStore進行儲存: name=天平,pass=密碼

3.2.2 在Module類裡面@Provide

module裡面的方法需要引數的解決方法1: 在Module類裡面@Provide一個引數型別。

  1. 我們把程式碼改回3.2.1之前那樣
  2. 修改LoginModule增加兩個方法
@Module
public class LoginModule {

    private Context mContext;   //供給LoginStore使用


    public LoginModule(Context mContext) {
        this.mContext = mContext;
    }


    /**
     * 為provideLoginCtrl方法的service引數提供例項化
     * @return
     */
    @Provides LoginService provideLoginService(){
        return new LoginService();
    }


    /**
     * 為provideLoginCtrl方法的store引數提供例項化
     * @return
     */
    @Provides LoginStore provideLoginStore(){
        return new LoginStore(mContext);
    }


    @Provides LoginCtrl provideLoginCtrl(LoginService service, LoginStore store){
        return new LoginCtrl(service,store);
    }

}

程式正常執行,看到輸出內容為:

03-04 19:20:40.795 31739-31739/com.tpnet.dagger2test E/@@: LoginService登入: name=天平,pass=密碼
03-04 19:20:40.795 31739-31739/com.tpnet.dagger2test E/@@: LoginStore進行儲存: name=天平,pass=密碼

簡單概括: 有需有求,activity的Inject需要什麼物件,Module類就提供什麼物件。

四、Dagger2模組化

在1.1的關係圖上面有說到多個Module , 說明了一個Component是可以依賴多個Module的,方法有三種:

  • 多個@Module修飾類
  • include @Moudle修飾類
  • dependencies依賴Component

來逐個理解。

4.1 多個@Module修飾類

Component的值為@Module修飾類, 在@Component的接口裡面需要新增Module類,如果需要依賴多個module類,用陣列就行了。

再新建一個getInfoModule.java類:

@Module
public class getInfoModule {

}

在Logincomponent裡面新增module陣列即可

@Component(modules = {LoginModule.class,getInfoModule.class})
public interface LoginComponent {

    void inject(TestActivity activity);

}

這是第一種模組化方法

4.2 include @Moudle修飾類

@Moudle修飾的類include @Moudle修飾類, 這裡是在LoginModule類裡面的@Module修飾符新增includes module

把4.1在LoginModule.java新增的程式碼刪掉, 然後修改LoginModule的程式碼:

//這裡includes了需要的Module
@Module(includes = getInfoModule.class)
public class LoginModule {

    private Context mContext;   //供給LoginStore使用


    public LoginModule(Context mContext) {
        this.mContext = mContext;
    }


    ....(下面的程式碼就不拷貝了)

}

4.3 dependencies依賴Component

Component 依賴dependencies Component, 新建一個GetInfoComponent.java 介面,在裡面依賴需要的Module:

@Component(modules = getInfoModule.class)
public interface GetInfoComponent {

}

然後在LoginComponent.java裡面使用dependencies依賴GetInfoComponent.class

@Component(modules = LoginModule.class,dependencies = GetInfoComponent.class)
public interface LoginComponent {

    void inject(TestActivity activity);

}

五、建立區分不同例項

問題1. Activity裡面@Inject的類是根據什麼初始化的呢?

其實是根據Module類裡面的方法的返回型別進行判斷。

問題2. 那麼問題就來了,加入我在activity需要注入兩個相同型別的類呢? 怎麼區分呢?

有兩種方法:

  • 在Module裡面的方法和Activity Inject的類上面新增@Named(value)修飾,value就是用以區分
  • 自定義註解。

5.1 方法1:@Named(value)區分

在3.2.2的程式碼上進行修改,在TestActivity再Inject一個LoginCtrl:

public class TestActivity extends Activity {

    @Named("one")   //用以區分LoginCtrl例項
    @Inject
    LoginCtrl loginCtrlOne;

    @Named("two")  //用以區分LoginCtrl例項
    @Inject
    LoginCtrl loginCtrlTwo;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //DaggerLoginComponent.create().inject(this);

        //當Module需要構造方法傳參的時候,使用builder的方式初始化Dagger。
        DaggerLoginComponent.builder()
                .loginModule(new LoginModule(this))  //loginModule這個方法是構建之後才有的
                .build()
                .inject(this);

        loginCtrlOne.login("天平one","密碼one");

        loginCtrlTwo.login("天平two","密碼two");

    }

}

在LoginModule.java裡面編輯新增一個返回LoginCtrl的方法

@Module
public class LoginModule {

    private Context mContext;   //供給LoginStore使用


    public LoginModule(Context mContext) {
        this.mContext = mContext;
    }


    /**
     * 為provideLoginCtrl方法的service引數提供例項化
     * @return
     */
    @Provides
    LoginService provideLoginService(){
        return new LoginService();
    }


    /**
     * 為provideLoginCtrl方法的store引數提供例項化
     * @return
     */
    @Provides
    LoginStore provideLoginStore(){
        return new LoginStore(mContext);
    }


    @Named("one")  //用以區分LoginCtrl例項
    @Provides
    LoginCtrl provideLoginCtrlOne(LoginService service, LoginStore store){
        Log.e("@@", "provideLoginCtrlOne被呼叫: ");
        return new LoginCtrl(service,store);
    }

    @Named("two")  //用以區分LoginCtrl例項
    @Provides
    LoginCtrl provideLoginCtrlTwo(LoginService service, LoginStore store){
        Log.e("@@", "provideLoginCtrlTwo被呼叫: ");
        return new LoginCtrl(service,store);
    }

}

可以看到輸出內容為一下,程式正常:

03-04 22:30:24.051 13833-13833/? E/@@: provideLoginCtrlOne被呼叫: 
03-04 22:30:24.051 13833-13833/? E/@@: provideLoginCtrlTwo被呼叫: 
03-04 22:30:24.051 13833-13833/? E/@@: LoginService登入: name=天平one,pass=密碼one
03-04 22:30:24.051 13833-13833/? E/@@: LoginStore進行儲存: name=天平one,pass=密碼one
03-04 22:30:24.061 13833-13833/? E/@@: LoginService登入: name=天平two,pass=密碼two
03-04 22:30:24.061 13833-13833/? E/@@: LoginStore進行儲存: name=天平two,pass=密碼two

5.2 方法2:自定義註解

我們按住Ctrl,然後滑鼠點選@Named,可以看到他的註解原始碼為:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

主要就是@Qualifier這個註解,我們也可以自己定義註解來區分。

5.2.1 使用value
建立一個註解,例如TPTest.java:

@Qualifier
@Retention(RUNTIME)
public @interface TPTest {
    String value() default "";
}

然後把5.1的例子的@Named改為@TPTest,你會發現也是可以的。。

5.2.2 不使用value
5.2.1是定義了一個註解,利用裡面的value進行區分,也可以用兩個註解進行區分。

新建一個One註解:

@Qualifier
@Retention(RUNTIME)
public @interface One {
    //這裡沒有value
}

再新建一個Two註解

@Qualifier
@Retention(RUNTIME)
public @interface Two {
    //這裡沒有value
}

然後把之前的@TPTest("one")改為@One@TPTest("two")改為@Two , 執行你會發現還是一樣的。

5.3 @Qualifier註解

在剛剛自定義註解的時候可以看到Qualifier這個關鍵詞,這個關鍵詞的作用就是: 用來區分不同的物件例項@Named@Qualifier的一種實現而已。

相關推薦

Android快速依賴注入框架Dagger2使用1

一、啥是Dagger2 Dagger2的內容有點多,一點得有耐心。 1.1 簡介 Dagger2是一個Android/Java平臺上快速依賴注入框架,由谷歌開發,最早的版本Dagger1 由Square公司開發。依賴注入框架主要用於模組間解耦,提高程式

Android ButterKnife依賴注入框架簡單使用

Butter Knife 通過註解的方式,將Android View與成員變數和方法繫結起來,為你形成一種模板樣式的程式碼。 在成員變數上使用@BindView替換掉 findView

android依賴注入框架Dagger和Butterknife實戰

依賴注入(DependencyInjection):在類A中要用到一個B的物件(依賴),需要通過新建B的例項或其他一些主動的方式來獲取物件,然後才能呼叫。而通過外部的方式自動將B的物件分配給A(注入),實現相對被動的獲取物件,這個過程稱為依賴注入。 依賴注入的一些需要理解

Android:dagger2讓你愛不釋手-基礎依賴注入框架

前言dagger2的大名我想大家都已經很熟了,它是解決Android或java中依賴注入的一個類庫(DI類庫)。當我看到一些開源的專案在使用dagger2時,我也有種匆匆欲動的感覺,因此就立馬想一探它的究竟,到底能給我帶來怎樣的好處。在學習使用dagger2的過程中,我遇到了以下的一些困惑:dagger2中的

Android依賴注入框架三、AndroidAnnotations

AndroidAnnotations是一個能夠讓你快速進行Android開發的開源框架,它能讓你專注於真正重要的地方。使程式碼更加精簡,使專案更容易維護。相比原生的Android App程式碼量,幾乎可以少一半。 用com.github.barteksc:and

Android依賴注入框架二、ButterKnife

簡述: ButterKnife 是出自Android大神JakeWharton之手的一個開源庫,它的作用就是通過註解繫結檢視的方法,從而簡化程式碼量。 題外話: Java中Spring提供ioc的功能,而且Spring的HttpInvoker可以實現直接呼叫後端的物件。

Android依賴注入框架Dagger2學習筆記

依賴注入 面向物件程式設計的一種設計模式,目的是為了降低程式中類與類之間的依賴引起的耦合。 在Java中,依賴注入有 通過介面注入 通過set方法注入 通過構造方法注入 通過註解注入 Dagger2 官網 為什麼要

依賴注入框架-ButterKnife使用方法總結

ButterKnife 2018-9-6 10:45 - QG2017移動組 - 張藝雋 ButterKnife是JakeWharton大神出品的用於View的注入框架。提供註解來簡單快捷地完成View的繫結、點選事件的分離等。 來自官方的說明

依賴注入框架Dagger2—1.入門

1.依賴注入 1.1.什麼是依賴? 如果在 Class A 中,有 Class B 的例項,則稱 Class A 對 Class B 有一個依賴。 例如下面類 A 中用到一個 B 物件,我們就說類 A 對類 B 有一個依賴。 同時,這也是一個典型的"依賴非注入

依賴注入框架Dagger2—2.各註解用法

0.前言 接上一篇入門文章,這篇主要是各屬性實戰。 1.Dagger2各屬性瞭解 必要屬性 @inject//注入,@Component,@Moudle,@Provider 為什麼說這個幾個是必要屬性,因為只要想用dagger2這幾個屬性是繞不開的。 高階屬

dagger2 讓你愛不釋手:基礎依賴注入框架

前言 dagger2的大名我想大家都已經很熟了,它是解決Android或java中依賴注入的一個類庫(DI類庫)。當我看到一些開源的專案在使用dagger2時,我也有種匆匆欲動的感覺,因此就立馬想一探它的究竟,到底能給我帶來怎樣的好處。在學習使用dagger2的過程中,我遇到了

Android Butterknife view注入框架使用

前言 ButterKnife 簡介 ButterKnife是一個專注於Android系統的View注入框架,可以減少大量的findViewById以及setOnClickListener程式碼,視覺化一鍵生成。 專案github地址: ButterK

從零開始開發IoC依賴注入框架 -- containerx (深入研究Spring原始碼)(含github原始碼)

摘要: 自己寫了一個開源的IoC控制反轉(依賴注入)框架,名為containerx。初學Spring原始碼的同學,可以先研究下這個小專案。更容易理解Spring的原始碼 自己寫了一個開源的IoC控制反轉(依賴注入)框架,名為containerx。初學Spring原始碼的同學,可以先研究下這個

依賴注入框架 ----Dagger2 使用詳解及原始碼分析

在開始說Dagger之前先說下什麼叫依賴注入。 依賴: 在建立物件A的過程中,需要用到物件B的例項,這種情況較呼叫者A對被呼叫者B有一個依賴。 例如下面的例子: 組裝一臺電腦時,要用到Cpu,那麼電腦這個物件,依賴Cpu物件。 public cl

.net 主要 依賴注入 框架 比較

Castle Windsor - Castle Windsor is best of breed, mature Inversion of Control container available for .NET and SilverlightUnity - Lightwei

[ASP.NET Core 3框架揭祕] 依賴注入:一個Mini版的依賴注入框架

在前面的章節中,我們從純理論的角度對依賴注入進行了深入論述,我們接下來會對.NET Core依賴注入框架進行單獨介紹。為了讓讀者朋友能夠更好地理解.NET Core依賴注入框架的設計與實現,我們按照類似的原理建立了一個簡易版本的依賴注入框架,也就是我們在前面多次提及的Cat。原始碼下載普通服務的註冊與消費泛型

[ASP.NET Core 3框架揭祕] 依賴注入[10]:與第三方依賴注入框架的適配

.NET Core具有一個承載(Hosting)系統,承載需要在後臺長時間執行的服務,一個ASP.NET Core應用僅僅是該系統承載的一種服務而已。承載系統總是採用依賴注入的方式來消費它在服務承載過程所需的服務。對於承載系統來說,原始的服務註冊總是體現為一個IServiceCollection集合,最終的依

給微軟的依賴注入框架寫一些擴充套件方法

給微軟的依賴注入框架寫一些擴充套件方法 Intro 現在在專案裡大多都是直接使用微軟的依賴注入框架,而微軟的注入方式比較簡單,不如 AutoFac 使用起來靈活,於是想給微軟的依賴注入增加一些擴充套件,使得可以像AutoFac 一樣比較靈活的註冊服務 Extensions RegisterTypeAsImpl

ASP.NET Core技術研究-探祕依賴注入框架

ASP.NET Core在底層內建了一個依賴注入框架,通過依賴注入的方式註冊服務、提供服務。依賴注入不僅服務於ASP.NET Core自身,同時也是應用程式的服務提供者。 毫不誇張的說,ASP.NET Core通過依賴注入實現了各種服務物件的註冊和建立,同時也實現了面向抽象的程式設計模式和程式設計體驗,提升了

放棄dagger?Anrdoi依賴注入框架koin

### Koin 是什麼 Koin 是為 Kotlin 開發者提供的一個實用型輕量級依賴注入框架,採用純 Kotlin 語言編寫而成,僅使用功能解析,無代理、無程式碼生成、無反射。 [官網地址](https://insert-koin.io/) ### 優勢 **依賴注入好處** * 增加開發效率、省