原文地址——D is for the Dependency Inversion Principle——Donn Felker

欢迎来到SOLID在Android中的实践最后一章。最后,我们来介绍SOLID的D字母,它代表了依赖反转原则(The Dependency Inversion Principle ——DIP)。

如果你错过了前面的篇章:

  • 单一职责原则在Android中的实践
  • 开/闭原则在Android中的实践
  • 利斯科夫替换原则在Android中的实践
  • 接口隔离原则在Android中的实践

不再浪费时间,第五个也是最后的原则介绍:
依赖反转原则告诫开发者必须遵守两项原则:

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Abstractions should not depend on details. Details should depend on abstractions.

简单地,依赖反转可以这样解释:

Depend on Abstractions. Do not depend on concretions.

迁移实现依赖反转

为了完全理解这个原则,重点需要一下介绍传统软件的分层模式。让我们先回顾传统分层架构,接着做出一些修改使其符合依赖反转原则。

在传统软件的分层架构中,高层次的模块需要依赖于低层次模块来实现功能。例如下面这个常见的结构(或者你的应用也在使用):

Android UI → Business Rules → Data Layer

这是三层架构,UI层(UI Layer,例如Android UI)包括我们所有的控件——listtextview,动画等界面相关的东西。之后是业务层(Business Layer),它是实现核心功能逻辑的一层,有时候也被叫做Domain Layer或者Service Layer。最后是数据层(Data Layer),它是所有应用数据存储的地方,有可能是数据库,API或者文件等负责储存和取出数据。

假设我们做一个记账应用,可以帮助用户记录开销。使用传统的架构,当用户进行一次记账时,我们需要三种不同的工作:

  • UI层——输入数量
  • 业务层——校验数据合法性
  • 数据层——持久化数据

代码这样写

findViewById(R.id.save_expense).setOnClickListener(new View.OnClickListener() {    public void onClick(View v) {        ExpenseModel expense = //... create the model from the view values        BusinessLayer bl = new BusinessLayer();        if (bl.isValid(expense)) {           // Woo hoo! Save it and Continue to next screen/etc        } else {           Toast.makeText(context, "Shucks, couldnt save expense. Erorr: " + bl.getValidationErrorFor(expense), Toast.LENGTH_SHORT).show();        }    } });

在业务层这样写一些伪代码

// in the business layer, return an ID of the expensepublic int saveExpense(Expense expense) {    // ... some code to check for validity ... then save    // ... do some other logic, like check for duplicates/etc    DataLayer dl = new DataLayer();     return dl.insert(expense);      }

这些代码的问题在于违反了依赖反转原则:高层次的模块依赖不应该依赖于低层次的模块,它们都应该依赖于抽象。下面这行代码,UI层依赖于业务层的实例:

    BusinessLayer bl = new BusinessLayer();

它把UI层与业务层永远耦合在一起,一旦脱离了业务层,UI层就无法正常地工作。

同样,业务层也违反了依赖反转原则——依赖于数据层的实例,看这行代码:

DataLayer dl = new DataLayer();

如何打破这个依赖链呢?如果高层次模块不依赖于低层次模块,那如何工作呢?

我们不需要一个万能类,记住SOLID的第一条原则——单一职责。

幸运的是,在应用中我们可以通过抽象来连接各个层级,这符合依赖反转原则。把传统的层级架构转换成依赖反转架构,需要使用控制反转。

实现控制反转

控制反转并不是把架构翻转过来,低层次模块肯定不能依赖于高层次模块。我们要从两端把整个关系倒置过来。

怎么做呢?抽象。

Java语言中,有几种方式可以创建抽象,例如抽象类和接口。我更推荐使用接口,使得层级之间的连接更整洁。接口定义了一份所有可能实现方法的协议

因此,每个层级都能依赖于接口,也就是抽象,而不是依赖于具体实现。

Android Studio里很容易实现,假设有个数据层长这样:

如果需要依赖于抽象,可以从类中抽取接口,像这样:

现在你就可以依赖于接口了。然而,DataLayer的具体实现仍旧被业务层使用。回到业务层,在其构造方法中使用依赖注入:

    public class BusinessLayer {        private IDataLayer dataLayer;        public BusinessLayer(IDataLayer dataLayer) {            this.dataLayer = dataLayer;        }        // in the business layer, return an ID of the expense        public int saveExpense(Expense expense) {            // ... some code to check for validity ... then save            // ... do some other logic, like check for duplicates/etc            return dataLayer.insert(expense);        }    }

业务层这样就能依赖于抽象了——IDataLayer接口。数据层通过构造方法进行依赖注入的方法叫Constructor Injection

简单来说,为了创建BusinessLayer对象,要先创建继承于IDataLayer的实例。业务层并不关心是谁继承的,只需要这个实现类就够了。

那数据层从哪里来呢?它是由创建业务层的对象生成的。在这个例子中,就是Android UI创建了业务层对象。在上面的例子中,UI层因为创建了业务层的具体实现产生了耦合,所以需要业务层也是一个抽象。

这时我进行相同的步骤Refactor–>Extract–>Extract Interface ,同样在Android UI层创建了这样一个抽象

// This could be a fragment too ... public class MainActivity extends AppCompatActivity {    IBusinessLayer businessLayer;     @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }}

最终,高层次的模块依赖于抽象(接口),更进一步,抽象并不依赖于实现,而是依赖于抽象。

记住,UI层依赖于业务层的抽象接口,业务层依赖于数据层的抽象接口。拥抱抽象吧。

在Android中聚集

有个难题是,应用存在一个入口,典型的是ActivityFramgent(Application是不行的,我们只关注活动的屏幕会话)。你也许在想,如果Android UI是最顶层,那它怎么依赖于抽象呢?

嗯,这里有几种方法可以解决,例如使用Android的创建模式 factory , factory method pattern,或者其他依赖注入框架。

我建议使用依赖注入框架,这样就无需手动创建对象,可以这样写代码

    public class MainActivity extends AppCompatActivity {        @Inject IBusinessLayer businessLayer;        @Override        protected void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);            setContentView(R.layout.activity_main);            // businessLayer is null at this point, can't use it.              getInjector().inject(this); // this does the injection            // businessLayer field is now injected valid and NOT NULL, we can use it            // do something with the business layer ...             businessLayer.foo()        }    }

建议使用 Dagger 作为依赖注入框架,有很多视频和教程指导如何实现依赖注入。

如果不使用任何模式或框架,代码看起来长这样

public class MainActivity extends AppCompatActivity {    IBusinessLayer businessLayer;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        businessLayer = new BusinessLayer(new DataLayer());        businessLayer.foo()    }}

当然这里看起来并不是很糟,最终你会发现对象会变得臃肿,初始化实例会容易出错,容易违反SOLID原则,而且改动代码的时候会很容易出错。如果没有使用框架,UI层最后还是会违背依赖反转原则。

接口隔离模式

有两种方法,可以根据喜好决定:

  • 把接口写在离类很近的地方并实现
  • 把接口写在特定的包里

第一种方法比较简单,易于理解,缺点是共享接口比较麻烦。
第二种方法是将所有的抽象接口放进自己的包,让你的实现者引用这个包来访问的接口。这具有更高的扩展性。缺点是多一个包需要维护可能实现的类,增加了复杂性。
决定权还是在应用构建的模式。

结论

依赖反转原则是我写应用必须实践的原则。每个应用最终都使用了框架,想Dragger,帮助管理对象的生命周期。依赖抽象让我写出了简单,更易于测试的应用程序代码。

推荐学习并使用Dragger,学成之日,就是你掌握依赖反转原则之时。

一旦你越过依赖注入和依赖倒置的鸿沟,你会想知道以往没有他们是如何都能够度日的。

更多相关文章

  1. Android嵌入式系统程序开发
  2. 第二章 IPC机制
  3. android Camera架构介绍
  4. Android(安卓)底部导航栏
  5. Android(安卓)WatchDog分析
  6. 高质量技术文章
  7. Android的Camera架构介绍
  8. Android(安卓)匿名共享内存Java接口分析
  9. Android(安卓)API中常用的包

随机推荐

  1. 【Android】android 输入框EditText禁止
  2. Android动态设置View的位置和大小
  3. android 添加menu 菜单项
  4. [APP] Android(安卓)开发笔记 001-环境搭
  5. Android(安卓)触屏播放音效与释放
  6. Android监听应用程序安装和卸载
  7. Android(安卓)实现上下滚动TextSwitcher
  8. kotlin 开发 android 程序中网络http请求
  9. Android(安卓)zip文件压缩
  10. android test