利用依赖注入为Controller缩小体积
本文原载于我的博客:https://www.seekingmini.top/archives/利用依赖注入为controller缩小体积
0 写在前面
MVC模式写着写着,就变成Massive View Controller了…Massive不仅仅是因为Controller部分的代码量巨大,还意味着它与其他部分的联系过于紧密,以致在修改其他部分的代码时会“牵一发而动全身”,大幅增加维护复杂度。那么如何为Cotroller减小耦合度呢?依赖注入是一个好方法。
1 什么是依赖注入
*的解释如下:
在软件工程中,依赖注入(dependency injection)的意思为给予调用方它所需要的事物。 依赖是指可被方法调用的事物。依赖注入形式下,调用方不再直接指使用依赖,取而代之是注入 。注入是指将依赖传递给调用方的过程。在注入之后,调用方才会调用该依赖。传递依赖给调用方,而不是让调用方直接获得依赖,这个是该设计的根本需求。
上面的解释反正说的云里雾里的…我们在实例中慢慢领悟这个概念。
2 简单示例
通过一个简单的例子,来阐述一下我对依赖注入的理解。首先我们有两个类,分别为Owner
和Dog
类。
// Dog类
class Dog: Pet {
init() {
}
func bark() {
print("Wooo!")
}
}
// Owner类
class Owner {
var pet: Dog
init() {
pet = Dog()
}
func poke() {
pet.bark()
}
}
我们发现,Owner
是依赖Dog
的,因为Owner
类的poke()
方法调用了Dog
类的bark()
方法,这不叫依赖注入。这段代码看似没什么问题,但是如果我修改了Dog
类的代码,比如像下面这样:
class Dog: Pet {
init() {
}
// 把bark()改为woof()
func woof() {
print("Wooo!")
}
}
那么在Owner
类中我就不得不更改poke()
的代码。这说明Owner
类和Dog
类的耦合度较高。而通过依赖注入,我想达到的效果是:更改Dog
类的代码时不用更改Owner
类的代码。怎么实现呢?需要利用protocol
这个玩意儿。
通过protocol
,我们让Dog
类遵守Pet
这个协议,通过Pet
协议定义的方法,来调用Dog
类的方法。既保证了通用型,又很好地对Dog
类的属性和方法进行了隐藏和封装。Pet
协议的定义如下:
protocol Pet {
func isPoked()
}
Dog
类新的定义如下:
class Dog: Pet {
init() {
}
private func woof() {
print("Wooo!")
}
func isPoked() {
woof()
}
}
Owner
类新的定义如下:
class Owner {
// pet就是依赖
var pet: Pet
init() {
pet = Dog()
}
func poke() {
// 调用Pet协议定义的方法
pet.isPoked()
}
}
将三者画成UML图,如下:
我们把Owner
类会用到的服务进行抽象化(Dog
抽象为Pet
),再在生成Owner
类的实例时,把具体的类别(遵守Pet
协议的Dog
类)注入给实例,这就是最简单的依赖注入。我们可以看到,改用依赖注入以后,Owner
就不知道Dog
类了,与它交互的对象是一个遵守了Pet
协议的对象。
3 项目实例
接下来,我们通过一个小APP来实践一下依赖注入这种方法。先来看看这个APP的成品:
这是一个简单的信息展示类的APP,其中的数据是以JSON格式出现的。在APP载入界面之后,请求API获取数据再展示在界面上。
怎么使用依赖注入呢?我们首先要搞清楚依赖关系。很明显,ViewContrller
类依赖获取JSON数据的那个方法或者类,不妨定义为WebService
类。换句话说,我们在ViewController
类中需要调用WebService
类的某一个方法来获取JSON数据,再把数据展示在View
上。
搞清楚这个关系以后,我们来定义protocol
,把它命名为KivaLoanTableViewControllerDataSource
,代码如下:
protocol KivaLoanTableViewControllerDataSource {
func fetchData(_ completion: @escaping ([Loan]) -> Void)
}
然后让WebService
类来遵循这个协议:
class WebService: KivaLoanTableViewControllerDataSource {
private let url = "https://api.kivaws.org/v1/loans/newest.json"
init() {
}
func fetchData(_ completion: @escaping ([Loan]) -> Void) {
let decoder = JSONDecoder()
if let url = URL(string: url) {
let task = URLSession.shared.dataTask(with: URLRequest(url: url)) { (data, _, error) in
if let error = error {
print(error)
return
}
if let data = data {
do {
let store = try decoder.decode(LoanStore.self, from: data)
completion(store.loans)
} catch {
print(error)
}
}
}
task.resume()
}
}
}
再在ViewController
类中如此调用fetchData(_:@escaping ([Loan])->Void)
方法,我们的ViewContrller
类命名为KivaLoanTableViewController
:
class KivaLoanTableViewController: UITableViewController {
var loans: [Loan] = []
var dataSource: KivaLoanTableViewControllerDataSource?
override func viewDidLoad() {
super.viewDidLoad()
// fetch data from data source
dataSource = WebService()
DispatchQueue.main.async {
self.dataSource?.fetchData { loans in
self.loans = loans
// add an operation
OperationQueue.main.addOperation {
self.tableView.reloadData()
}
}
}
}
// ...
}
这样写的话,只要保证接口不变,我可以任意修改fetchData(_:@escaping ([Loan])->Void)
的代码内容而不用更改KivaLoanTableViewController
类的代码,起到了减少耦合性的作用。
具体代码点此下载。
参考
Massive View Controller 重構:透過依賴注入 (Dependency Injection) 減輕職責