依赖注入(Dependency Injection)
依赖注入是面向对象开发中对象间解耦合的一种策略,它也可称为控制反转(Inversion of Control)或者依赖倒置原则(Dependence Inversion Principle)。
这个概念说白了就是:我只做我关注的事情,其他不确定的事情你们其他人去实现吧。
控制反转,反转的是什么,反转的是对程序的控制权。程序组件为调用者提供服务的同时,还需要能让调用者定制一些功能,只要符合组件的接口,调用者就可以将定制的功能设置给组件,这就相当于将控制权转交给了调用者,这样组件不会依赖于一些可变的功能,对于调用者来说使用组件也可以更加灵活更加可扩展。
正如依赖倒置原则说的要依赖于抽象,不要依赖于具体,要对抽象进行编程。作为一个功能或者组件的开发者来说,要尽量将自己的一些具有可变性的依赖进行抽象。
下面来遐想一个场景举例说明:
class MediaPlayer { private FileLogger logger = new FileLogger(@"c:\log.txt"); public void Play(string filePath) { try { // do play ... logger.log("正在播放"); } catch { logger.error("播放失败"); } } }
上边的代码意图去实现一个媒体播放功能的组件,为了记录播放状态MediaPlayer依赖于FileLogger,使用者角度来说只能通过c:\log.txt查看播放状态,并无权更改。如果需要通过Trace或者Console方式输出播放状态,组件开发者就需要修改代码。这时就需要依赖注入,实现方式可以分为三种:
1. 构造注入
class MediaPlayer { private Logger logger; public MediaPlayer(Logger logger) { this.logger = logger; } public void Play() { try { // do play ... logger.log("正在播放"); } catch { logger.error("播放失败"); } } } // 调用 new MediaPlayer(new FileLogger(@"c:\log.txt")).Play(); new MediaPlayer(new TraceLogger()).Play(); new MediaPlayer(new ConsoleLogger()).Play();
2. 赋值注入
class MediaPlayer { private Logger logger; public void SetLogger(Logger logger) { this.logger = logger; } public void Play() { try { // do play ... logger.log("正在播放"); } catch { logger.error("播放失败"); } } } // 调用 MediaPlayer player = new MediaPlayer(); player.SetLogger(new ConsoleLogger()); player.Play();
3. 接口注入
interface ILoggerProvider { void AddLogger(Logger logger); } class MediaPlayer : ILoggerProvider { private Logger logger; public void AddLogger(Logger logger) { this.logger = logger; } public void Play() { try { // do play ... logger.log("正在播放"); } catch { logger.error("播放失败"); } } } // 调用 MediaPlayer player = new MediaPlayer(); player.AddLogger(new ConsoleLogger()); player.Play();
通过依赖注入方式,MediaPlayer将Logger进行了抽象,依赖于一个抽象的Logger,将Logger的实现丢给了调用者,调用者可以通过硬编码或者通过XML配置文件方式来创建不同的Logger,然后注入到MediaPlayer中。