控制反转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()越来越多会出现以下问题:

  1. 单例代码太多,且都是重复代码
  2. 有些不需要单例的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,我们则需要对containerget方法进行一下包装

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);
    }
}

代码重构完成后带来了一下优点:

  1. 去除了各个service类中的冗余代码
  2. 所用创建任务均交给容器进行管理
  3. 容器可以快速构建需要的数据,便于单元测试

IoC容器和工厂区别

  1. 解决问题不同:IoC容器是为了解决多个点对点之间连线,将其剪断并统一管理。工厂则是封装创建对象的复杂逻辑,提供代码抽象程度。
  2. container中获取的对象是属于container的,而工厂创建的对象在创建完成后就与工厂没有任何关系了
  3. container中获取对象时需要先注入对象,而工厂不需要这一步骤。换句话说container并不真的负责创建对象(当然也可以通过集成工厂来实行创建),而是负责管理对象。