控制反转IoC
控制反转的目的是将创建对象的任务交给外部程序完成,内部使用创建好的对象。
其实现方式有两种:
- 依赖注入
- 依赖查找(Service Locator)
依赖注入
依赖注入即使用的对象由外部提供,如属性注入-将所需对象当参数传入而不是自己创建
class NeedaServcie {
Service service;
// other opt
void setService(Service service) {
this.service = service;
}
}
创建对象操作可结合工厂方法一起使用。这种实现方式较为简单且对语言没有要求。
Service Locator
通过IoC容器统一管理对象的创建、生命周期,大多数情况下会使用反射机制,对编程语言有一定要求。
假设我们有许多service
需要管理且中间许多都要设为单例模式,如
class ConfigService {
private static ConfigService sharedService = null;
public static ConfigService getService() {
if (sharedService == null)
sharedService = new ConfigService();
return sharedServce;
}
private ConfigService() {}
// ...
}
外部访问时需要通过ConfigService.getService()
访问,随着*.getService()
越来越多会出现以下问题:
- 单例代码太多,且都是重复代码
- 有些不需要单例的
service
也可能照搬*.getService()
格式
为此我们可以创建一个IoC容器来同意管理这些service
,其核心是一个哈希表(字典), java中可使用Map
实现:
Map<String, Object> container = new HashMap<>();
创建之后,在其中添加所有单例service
,使用其名字作为key
container.add("ConfigService", new ConfigService());
container.add("DataBaseService", new DataBaseService());
container.add("NetWorkService", new NetWorkService());
当然我们可以创建一个枚举类型来保存所有名字,避免我们还要记住服务名字
enum ServiceName {
ConfigService,
DataBaseService,
NetWorkService
}
这样我们在需要configService
的时候就可以直接到容器中取就行了
ConfigService configService = container.get(ServiceName.ConfigService);
// 注意使用枚举类型后,添加服务到容器中时要需要更换对于的key,如
// container.add(ServiceName.ConfigService, new ConfigService);
这时我们可以去掉服务中所有单例代码,在添加服务时,直接使用构造方法。而单例则通过container
的单例来保证(容器是单例的,其中保存的元素也一定是单例的):
static Map<String, Object> container = new HashMap<>();
对于那些不需要单例的service
,我们则需要对container
的get
方法进行一下包装
class ServiceLocator {
static Map<ServiceName, Service> container = new HashMap<>();
Service get(ServiceName key) {
if (container.keys().contains(key))
return container.get(key);
else
// 若不在container中则使用类名反射创建一个对象
// 这里可以通过集成一个工厂来实现,工厂通过外部注入
return CreateInstance(key);
}
}
代码重构完成后带来了一下优点:
- 去除了各个
service
类中的冗余代码 - 所用创建任务均交给容器进行管理
- 容器可以快速构建需要的数据,便于单元测试
IoC容器和工厂区别
- 解决问题不同:IoC容器是为了解决多个点对点之间连线,将其剪断并统一管理。工厂则是封装创建对象的复杂逻辑,提供代码抽象程度。
- 从
container
中获取的对象是属于container
的,而工厂创建的对象在创建完成后就与工厂没有任何关系了 - 从
container
中获取对象时需要先注入对象,而工厂不需要这一步骤。换句话说container
并不真的负责创建对象(当然也可以通过集成工厂来实行创建),而是负责管理对象。