设计模式:依赖注入

尽管依赖注入是一个很少为初学者讲授的话题,但它是一种值得更多关注的设计模式。 许多开发人员避免依赖注入,因为他们不知道它意味着什么,或者因为他们认为自己不需要它。

在本文中,我将尝试说服依赖注入的价值。 为此,我将以最简单的形式向您介绍依赖注入的简单性。

1.什么是依赖注入?

关于依赖关系注入的文章很多,有许多旨在简化依赖关系注入的工具和库。 然而,有一句话引述了许多人对依赖注入的困惑。

“依赖性注入”是5美分概念的25美元术语。 - 詹姆斯·肖尔

一旦掌握了依赖注入基础的思想,您还将理解上面的引用。 让我们从一个例子开始说明这个概念。

iOS应用程序具有许多依赖关系,您的应用程序可能依赖于您甚至不知道的依赖关系,即您不认为它们具有依赖关系。 以下代码段显示了名为ViewControllerUIViewController子类的实现。 该实现包括一个名为saveList:的方法。 你能发现依赖吗?

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)saveList:(NSArray *)list {
    if ([list isKindOfClass:[NSArray class]]) {
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
        [userDefaults setObject:list forKey:@"list"];
    }
}

@end

最常被忽略的依赖项是我们最依赖的依赖项。 在saveList:方法中,我们将数组存储在用户默认数据库中,该数据库可通过NSUserDefaults类进行访问。 我们通过调用standardUserDefaults方法访问共享的默认对象。 如果您对iOS或OS X的开发有所了解,则可能对NSUserDefaults类很熟悉。

在用户默认数据库中存储数据是快速,简便和可靠的。 多亏了standardUserDefaults方法,我们可以从项目中的任何地方访问用户默认数据库。 该方法返回一个单例,我们可以随时随地使用它。 生活可以美丽。

单身人士? 无论何时何地? 你闻到什么了吗? 我不仅闻到依赖,还闻到不良习惯。 在本文中,我不想通过讨论单例的使用和误用来打开蠕虫病毒的罐头,但重要的是要了解应该谨慎使用单例。

我们大多数人已经习惯了用户默认数据库,因此我们不认为它是依赖项。 但这当然是一个。 通知中心也是如此,我们通常通过defaultCenter方法通过单例进行访问。 请看以下示例进行说明。

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

#pragma mark -
#pragma mark Initialization
- (instancetype)init {
    self = [super init];
    
    if (self) {
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
    }
    
    return self;
}

#pragma mark -
#pragma mark Memory Management
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark -
#pragma mark Notification Handling
- (void)applicationWillEnterForeground:(NSNotification *)notification {
    // ...
}

@end

上面的情况很常见。 我们将视图控制器添加为名称为UIApplicationWillEnterForegroundNotification通知的观察者,并在类的dealloc方法中将其作为观察者移除。 这为ViewController类添加了另一个依赖关系,该依赖关系经常被忽略或忽略。

您可能会问自己的问题是“什么问题?” 或更佳的“有问题吗?” 让我们从第一个问题开始。

问题是什么?

根据以上示例,似乎没有问题。 但是,这并非完全正确。 视图控制器依靠共享的默认对象和默认通知中心来完成其工作。

那是问题吗? 几乎每个对象都依靠其他对象来完成其工作。 问题是依赖关系是隐式的 。 该项目的新开发人员不知道视图控制器通过检查类接口依赖于这些依赖关系。

由于我们不控制NSUserDefaultsNSNotificationCenter类,因此测试ViewController类也将很棘手。 让我们看一下这个问题的一些解决方案。 换句话说,让我们看看依赖注入如何帮助我们解决这个问题。

2.注入依赖

正如我在简介中所提到的,依赖注入是一个非常简单的概念。 James Shore撰写了一篇很棒的文章,介绍了依赖注入的简单性。 詹姆斯·肖尔(James Shore)引用了另一句有关依赖注入本质上的信息。

依赖注入意味着给对象一个实例变量。 真。 而已。 -詹姆斯·肖尔

有多种方法可以完成此操作,但首先要了解以上引用的含义很重要。 让我们看看如何将其应用于ViewController类。

我们不是通过defaultCenter类方法通过init方法访问默认通知中心,而是在ViewController类中为通知中心创建属性。 这就是添加后的ViewController类的更新接口。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (weak, nonatomic) NSNotificationCenter *defaultCenter;

@end

这也意味着我们在初始化ViewController类的实例时需要做一些额外的工作。 正如James所写,我们将ViewController实例传递给它的实例变量。 那就是简单的依赖注入。 这是一个简单明了的概念的名字。

// Initialize View Controller
ViewController *viewController = [[ViewController alloc] init];

// Configure View Controller
[viewController setNotificationCenter:[NSNotificationCenter defaultCenter]];

作为此更改的结果, ViewController类的实现也发生了更改。 这是注入默认通知中心时initdealloc方法的外观。

#pragma mark -
#pragma mark Initialization
- (instancetype)init {
    self = [super init];
    
    if (self) {
        [self.notificationCenter addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
    }
    
    return self;
}

#pragma mark -
#pragma mark Memory Management
- (void)dealloc {
    [_notificationCenter removeObserver:self];
}

注意,我们在dealloc方法中不使用self 这被认为是不好的做法,因为它可能导致意外的结果。

有一个问题。 你能发现吗? ViewController类的初始化程序中,我们访问notificationCenter属性以将视图控制器添加为观察者。 但是,在初始化期间,尚未设置notificationCenter属性。 我们可以通过将依赖项作为初始化程序的参数来解决此问题。 看起来就是这样。

#pragma mark -
#pragma mark Initialization
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter {
    self = [super init];
    
    if (self) {
        // Set Notification Center
        [self setNotificationCenter:notificationCenter];
        
        // Add Observer
        [self.notificationCenter addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
    }
    
    return self;
}

为了使此工作有效,我们还需要更新ViewController类的接口。 我们省略了notificationCenter属性,并为创建的初始化程序添加了方法声明。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

#pragma mark -
#pragma mark Initialization
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter;

@end

在实现文件中,我们创建一个类扩展中,我们申报notificationCenter物业。 这样, notificationCenter属性是ViewController类的私有属性,并且只能通过调用新的初始化程序进行设置。 请记住,这是另一种最佳做法,只公开需要公开的属性。

#import "ViewController.h"

@interface ViewController ()

@property (strong, nonatomic) NSNotificationCenter *notificationCenter;

@end

要实例化ViewController类的实例,我们依赖于我们先前创建的初始化程序。

// Initialize View Controller
ViewController *viewController = [[ViewController alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];

3.好处

通过将通知中心对象显式注入为依赖项,我们完成了哪些工作?

明晰

ViewController类的接口明确表明该类依赖或依赖于 NSNotificationCenter类。 如果您不熟悉iOS或OS X开发,那么对于我们添加的复杂性来说,这似乎是一个小小的胜利。 但是,随着项目变得越来越复杂,您将学会欣赏可以添加到项目中的所有清晰度。 明确声明依赖项将帮助您。

模块化

当您开始使用依赖注入时,您的代码将变得更加模块化。 即使我们将特定的类注入到ViewController类中,也可以注入符合特定协议的对象。 如果采用这种方法,将一个实现替换为另一个实现将变得更加容易。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (strong, nonatomic) id<MyProtocol> someObject;

#pragma mark -
#pragma mark Initialization
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter;

@end

ViewController类的以上接口中,我们声明了另一个依赖关系。 依赖项是符合MyProtocol协议的对象。 这就是依赖注入真正力量的体现。 ViewController类不关心someObject的类型,只要求它采用MyProtocol协议。 这使得高度模块化,灵活和可测试的代码成为可能。

测验

尽管测试在iOS和OS X开发人员中的普及程度不如在其他社区中普遍,但测试是获得重要性和普及性的关键主题。 通过采用依赖注入,您将使代码的测试变得更加容易。 您将如何测试以下初始化程序? 这将是棘手的。 对?

#pragma mark -
#pragma mark Initialization
- (instancetype)init {
    self = [super init];
    
    if (self) {
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
    }
    
    return self;
}

但是,第二个初始化程序使此任务更加容易。 看一看初始化程序及其附带的测试。

#pragma mark -
#pragma mark Initialization
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter {
    self = [super init];
    
    if (self) {
        // Set Notification Center
        [self setNotificationCenter:notificationCenter];
        
        // Add Observer
        [self.notificationCenter addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
    }
    
    return self;
}
#pragma mark -
#pragma mark Tests for Initialization
- (void)testInitWithNotificationCenter {
    // Create Mock Notification Center
    id mockNotificationCenter = OCMClassMock([NSNotificationCenter class]);
    
    // Initialize View Controller
    ViewController *viewController = [[ViewController alloc] initWithNotificationCenter:mockNotificationCenter];
    
    XCTAssertNotNil(viewController, @"The view controller should not be nil.");
    
    OCMVerify([mockNotificationCenter addObserver:viewController selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]);
}

上面的测试使用了OCMock库 ,它是Objective-C的出色模拟库 而不是传递NSNotificationCenter类的实例,而是传递一个模拟对象并验证是否确实调用了需要在初始化程序中调用的方法。

有多种测试通知处理的方法,到目前为止,这是我所遇到的最简单的方法。 通过将通知中心对象作为依赖项进行注入,这会增加一些开销,但我认为好处胜于增加的复杂性。

4.第三方解决方案

我希望我已经说服了您,依赖注入是一个具有简单解决方案的简单概念。 但是,有许多流行的框架和库旨在使依赖注入更加强大,并且更易于管理复杂的项目。 最受欢迎的两个库是TyphoonObjection

如果您不熟悉依赖项注入,那么强烈建议您开始使用本教程中概述的技术。 在依靠第三方解决方案(例如台风或异议)之前,您首先需要正确理解概念。

结论

本文的目的是使依赖注入对于那些不熟悉编程的新手来说更容易理解。 我希望我已经使您相信依赖注入的价值以及基本思想的简单性。

关于依赖项注入,有许多出色的资源。 James Shore的有关依赖项注入的文章对于每个开发人员都是必读的。 Graham Lee还针对iOS和OS X开发人员写了一篇很棒的文章

翻译自: https://code.tutsplus.com/articles/design-patterns-dependency-injection--cms-23809

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值