1.4 真的实现了吗

工厂、服务定位器和依赖注入是通常人们用于消除模块间依赖的三大法宝。在检视这些模式的代码时,不知道聪明的读者有没有产生一个疑问:播放器对编解码器的依赖真的消除了吗?

1.4.1 依赖的传递性

a, b, cX: (aRbbRc)⇒aRc

相信很多朋友对上面表达式中符号的含义还有印象,它是传递关系(Transitive relation)的数学定义。一个集合X上的二元关系R被称为是传递的,如果集合中任意元素ab有此关系,而bc又有此关系时,ac就有此关系。我们都知道很多这样的关系,例如有序关系:如果a>bb>c,就有a>c。程序中对象间的依赖也是一种传递关系。如果对象a依赖对象bb又依赖c,那么ac也有依赖。

再来看上一节的三种模式。播放器的代码中都没有对编解码器具体类型的依赖。但是,在工厂模式中,工厂创建了具体编解码器的实例;在服务定位器模式中,负责注册服务的控制器创建了具体编解码器的实例;在依赖注入模式中,负责注入的控制器创建了具体编解码器的实例。也就是说,三种模式下仍然有某个对象依赖具体编解码器。在工厂模式中,播放器依赖工厂;在服务定位器模式中,控制器依赖播放器;在依赖注入模式中,控制器依赖播放器。因为依赖有传递性,所以在工厂模式下,播放器依然依赖具体编解码器。在后两种模式下,播放器虽然不依赖播放器了,但是控制器和播放器一起都属于对编解码器模块的调用模块,将播放器的依赖转移到控制器,并没有解决问题。所以结论就是工厂、服务定位器和依赖注入模式并没有消除模块间的依赖。有人或许会质疑,我上面的代码写得不够好,但是它们的确体现了三种模式的内涵,这是毫无疑问的。

1.4.2 依赖的形式

既然没有消除依赖,就要继续想办法。调用方的代码不能引用具体的编解码器类型,但总归要以某种形式知道这些类型。用这些类型的名称似乎是个不错的方案,字符串记录的名称无须引用对应的类型,使用反射创建这些类型的实例,将调用者对被调用者的类型依赖从编译时移除,而仅出现在运行时。编解码器的类型名称可以和之前的媒体文件类型名称一样,由播放器传递给工厂或服务定位器。不过依据这些名称利用反射创建编解码器实例的代码太简单,不如直接写在播放器内。

与编解码器的类型名称一起出现的还有它所在的程序集的名称,如果是Java,此名称就会以Java包的信息形式被包装在某个ClassLoader里。

然后再认真想想,这下真的消除了依赖吗?消除依赖的本质是调用者和被调用者在都不知道对方具体实现的状态下通过接口来合作。换言之,除了接口,双方对对方一无所知。调用者不能引用被调用者的具体类型,就能知道这些类型的名称吗?

依赖的形式不仅包括直接使用被调用者的具体类型,还包括使用被调用者接口之外的任何信息。

所以,上述播放器依然依赖具体的编解码器。