什么是 IoC (Inversion-of-Control)

IoC 翻译过来就是控制反转,是面向对象编程时的一类解耦技术。

谁的控制被反转了?

复杂对象直接控制内部成员的创建和回收这件事被反转给了外部代码。简而言之,就是创造更多中间层,使得对象和对象的属性之间的依赖关系变长,从而让我们有更多机会重构。

举例: 某个游戏主角

我们先不考虑大部分游戏要素,只考虑一个平面游戏角色的行动所需的属性:

class Bjorn
    WALK_ACCELERATION = 0.1
    attr_accessor :velocity, :x, :y
    def initialize()
        @x = @y = @velocity = 0
        @sprite_stand = ...
        @sprite_walk_left = ...
        @sprite_walk_right = ...
    end
    def update()
        # 处理输入
        case Input.dir4
        when 4 then @velocity -= WALK_ACCELERATION
        when 6 then @velocity += WALK_ACCELERATION
        end
        @x += @velocity
        # 其他游戏逻辑
        ...
        # 绘制图形
        sprite =
            case
            when velocity < 0 then @sprite_walk_left
            when velocity > 0 then @sprite_walk_right
            else                   @sprite_stand
            end
        Graphics.render(sprite, @x, @y)
    end
end

很显然,input() 方法太长了,即使划分成更小的方法也难以复用和维护(上下文耦合一旦产生很难解决)。于是我们可以把一部分逻辑挪到别的类/模块里,就像这样:

module BjornInputComponent
    def self.update(bjorn)
        case Input.dir4
        when 4 then bjorn.velocity -= WALK_ACCELERATION
        when 6 then bjorn.velocity += WALK_ACCELERATION
        end
        bjorn.x += bjorn.velocity
    end
end
class Bjorn
    attr_accessor :input # = BjornInputComponent
    def update()
        @input.update(self)
        # ...
    end
end

这样就能挪出去一部分代码了。问题在于 @input 是怎么来的,这一差别导致了两种 IoC 的实现方法(其实都是发明术语吓唬人):

依赖注入 (DI):由创建方传入

def game_main
    player = Bjorn.new
    player.input = BjornInputComponent # <-
    # ...
end

Spring 等框架提供了多种 DI 方式,目的当然是尽量减少手动成本,不过对于 Java 本身的手动成本仍然相当于杯水车薪 ;w;

依赖查找 (DL):服务定位器

class Bjorn
    def initialize()
        @input = Locator.input_for(Bjorn) # <-
        # ...
    end
end

读者可能会想:难道 DI 里就不能写 player.input = Locator.xxx_for(player) 了吗?当然可以写,所以 DI 和 DL 的区分其实并不明显,只是 Martin Fowler 等大佬随口发明了几个词罢了。

我们在实际业务中,不要为了用某个技术而专门去用,平时多积累一些设计与重构方法,代码质量自然会在不断迭代中更加适合完成目标。