Dependency Injection 筆記 (3)
续上集。接着要来进一步了解的是 DI 的实现技术,也就是注入相依对象的方式。这里介绍的依赖注入方式,又称为「穷人的 DI」(poor man’s DI),因为这些用法都与特定 DI 工具无关,亦即不使用任何现成的 DI 框架(例如 Unity、Autofac)。毕竟,DI 只是一组设计原则与模式,不依赖任何工具也能实现。
(本文摘自電子書:《.NET 依賴注入》)
设计模式梗概
每个模式都描述了一个不断发生在我们周遭的问题,然后描述该问题的核心解法,于是你便可以一再使用该解法,而无须对同样的事情做两次工。
—— Christopher Alexander. A Pattern Language.
除了第 1 章提到的 S.O.L.I.D. 设计原则,在运用 DI 技术时,也经常需要搭配一些设计模式(design patterns),例如 Factory Method(工厂方法)、Decorator(装饰)、Composite(组合)、Adapter(转换器)等等。基于后续章节讨论的必要,本节将介绍几个相关的设计模式。如需比较完整深入的介绍,可参考相关书籍,例如:《面向对象设计模式》、《深入浅出设计模式》、《重构-向范式前进》等等。
小引-电器与接口
日常生活中,四处可见电器用品,例如电视、微波炉、计算机等等。这些电器通常都有条电线,电线尾端是个插头,而当我们要使用这些电器时,就把插头插在墙壁或电源插座上,电器便能够获得所需之电力。一般情况下,没有人会舍插座不用,而把电器的电源线固定焊在墙壁的电源插座。假使真这么做,万一有一天电视或计算机故障而需要维修,那可就麻烦了。

不只电源插座,计算机的 USB 插槽也一样——它们都具备宽松耦合的特性。这里的电源插座或 USB 插槽,对应到软件世界里的概念,便是接口。一个接口就等于是一份规格,而各家厂商所生产的各式各样的电源插座或 USB 插槽,就是遵照其标准规格(接口)所实现出来的产品,或简称实现品。用软件的术语来说,这些实现品就是类型——实现了特定接口的类型。
接口的威力即在于一旦订出标准规格,各家厂商便可依照标准接口来制作各类产品。对使用者来说,好处则是享有多种选择,因为他们不会被特定厂商的产品绑住;只要他们高兴,随时可以更换不同的产品,而且通常是即插即用。在软件的世界里,接口也有同样的好处:让类型与类型之间保持宽松耦合,以便提供随时抽换实现类型的弹性。
Null Object 模式
回到电源插座的例子。如果我们将计算机的电源线从插座上拔起,它们就只是彼此不再连接而已,计算机和插座并不会因此而着火或爆炸。但是在软件程序的世界里,若对象 A 会调用对象 B(对象 A 依赖对象 B),而当你将对象 B 移除,亦即对象 B 不存在时,程序就会发生 NullReferenceException 类型的错误。于是,我们常常会在程序里面加入检查对象参考是否为 null 的逻辑,例如:
if (anObject != null)
anObject.DoSomething();
else
DoSomethingElse();
如果在程序中一再重复写这些检查 null 的逻辑,代码便会膨胀,而且在解读程序的主要逻辑时,常常得要跳过这些检查逻辑,多少会形成阅读代码的阻碍。针对此问题,我们可以设计一个空的、完全不做任何事的类型,然后在变量有可能是 null 的地方,让它们指向那个空的对象。这种模式叫做 Null Object。
Null Object 的优点:可减少编写判断对象参考是否为 null 的防错逻辑。但前提是开发人员得知道有 Null Object 可用,否则还是会写出多余的防错代码。
Null Object 类型通常要实现某个接口(或继承自抽象类型),但实现代码完全没做任何事,即所有方法都只是个空壳子,或仅提供无害的默认行为。以程序中常用的 logging(日志)机制为例,我们可以将写入日志的操作定义成一个 ILogger 接口,然后依实际需要实现不同的 logging 类型,例如用来将日志讯息输出至 Console 的 ConsoleLogger。此外,考虑到应用程序有时候可能不需要纪录任何讯息,我们可以实现一个 NullLogger 类型,当作 Null Object 使用。结构图如下。

底下分别是 ILoger 接口以及 NullLogger 和 ConsoleLogger 类型的代码:
public interface ILogger
{
Log(string msg);
} public class NullLogger : ILogger
{
public void Log(string msg)
{
// 不做任何事
}
} public class ConsoleLogger : ILogger
{
public void Log(string msg)
{
Console.WriteLine(msg);
}
}
像底下这个函式,调用端只要传入 ConsoleLogger 对象,日志讯息就会输出至 Console;而当调用端想要停止记录日志,便可传入 NullLogger 对象。如此一来,就不用在每次写入日志讯息时都重复写一遍检查 logger 对象是否为 null 的防错逻辑。
void DoSomething(ILogger logger)
{ logger.Log("开始执行 DoSomething 函式。");
....
}
Note: Null Object 本身并不需要「进化」成真正有做事的对象,因为它的存在就是为了提供一个完全不做任何事、不具任何意义的对象。
Decorator 模式

延续前面的 logging 范例,假设想要在每次输出 log 讯息时额外加上当时的日期时间,而且前提是不可修改现有的 ILogger 和 ConsoleLogger 类型,该怎么做?
我们可以使用 Decorator 模式。作法为:设计一个新的类型,此类型不仅要实现 ILogger 接口,而且还需要使用现有的 ConsoleLogger 对象来输出 log 讯息。简单起见,我就把这个类型命名为 DecoratedLogger。代码如下:
public class DecoratedLogger : ILogger
{
private ILogger logger; public DecoratedLogger(ILogger aLogger)
{
logger = aLogger;
} public void Log(string msg)
{
logger.Log(DateTime.Now.ToString() + " - " + msg);
}
}

void DoSomething()
{
ILogger logger = new DecoratedLogger(new ConsoleLogger());
logger.Log("Hello, 裝飾模式!");
}
Dependency Injection 筆記 (3)的更多相关文章
- Dependency Injection 筆記 (2)
续上集,接着要说明如何运用 DI 来让刚才的范例程序具备执行时期切换实现类型的能力. (本文摘自電子書<.NET 依賴注入>) 入门范例—DI 版本 为了让 AuthenticationS ...
- Dependency Injection 筆記 (1)
<.NET 依賴注入>連載 (1) 本文从一个基本的问题开始,点出软件需求变动的常态,以说明为什么我们需要学习「依赖注入」(dependency injection:简称 DI)来改善设计 ...
- Dependency Injection 筆記 (4)
续上集未完的相关设计模式... (本文摘自電子書:<.NET 依賴注入> Composite 模式 延续先前的电器比喻.现在,如果希望 UPS 不只接计算机,还要接电风扇.除湿机,可是 U ...
- Dependency Injection
Inversion of Control - Dependency Injection - Dependency Lookup loose coupling/maintainability/ late ...
- Ninject学习(一) - Dependency Injection By Hand
大体上是把官网上的翻译下而已. http://www.ninject.90iogjkdcrorg/wiki.html Dependency Injection By Hand So what's Ni ...
- MVC Controller Dependency Injection for Beginners【翻译】
在codeproject看到一篇文章,群里的一个朋友要帮忙我翻译一下顺便贴出来,这篇文章适合新手,也算是对MEF的一个简单用法的介绍. Introduction In a simple stateme ...
- 控制反转Inversion of Control (IoC) 与 依赖注入Dependency Injection (DI)
控制反转和依赖注入 控制反转和依赖注入是两个密不可分的方法用来分离你应用程序中的依赖性.控制反转Inversion of Control (IoC) 意味着一个对象不会新创建一个对象并依赖着它来完成工 ...
- [转载][翻译] IoC 容器和 Dependency Injection 模式
原文地址:Inversion of Control Containers and the Dependency Injection pattern 中文翻译版本是网上的PDF文档,发布在这里仅为方便查 ...
- Inversion of Control Containers and the Dependency Injection pattern(转)
In the Java community there's been a rush of lightweight containers that help to assemble components ...
随机推荐
- sparksql 用反射的方式将rdd转换成dataset/dataframe
java public class ReflectionDemo { private static SparkConf conf = new SparkConf().setAppName(" ...
- SharePoint 2010/2013 隐藏的速度下拉菜单列表项
SharePoint 2010/2013 隐藏的速度下拉菜单列表项 有时为了防止一些用户编辑列表项.需要隐藏下拉菜单列表项.,仅仅须要添加一个内容编辑器控件,将css代码写入其HTML ...
- Android能够获取到唯一的设备ID吗?
Android是否有唯一的设备ID,如果有的话,该怎样快速有效获取? Settings.Secure#ANDROID_ID 返回Android ID ,是一个64位的16进制字符串 1 2 3 imp ...
- Android中使用JUnit测试
package com.meritit.lottery.test; import java.util.List; import android.test.AndroidTestCase; import ...
- NYOJ781 又见回文数
又见回文数 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描写叙述 冷淡的回文数被水了,各种被水,然后他非常生气,然后... 一个数从左边读和从右边读一样,就说这个数是回文数 ...
- 用 theano 求解 Logistic Regression (SGD 优化算法)
1. model 这里待求解的是一个 binary logistic regression,它是一个分类模型,参数是权值矩阵 W 和偏置向量 b.该模型所要估计的是概率 P(Y=1|x),简记为 p, ...
- Matlab Tricks(二十)—— Hilbert matrix 的创建
Hij=1i+j−1 N = 5; A = ones(N, 1)*(1:N); B = A'; H = 1./(M+N-1);
- MySQL TIMESTAMP(时间戳)详细解释
当你创建一个表假设表中有类型的字段TIMESTAMP,该字段默认情况下,语句生成: CREATE TABLE `test` ( `id` int(11) DEFAULT NULL, `ctime` t ...
- WPF Binding妙处-既无Path也无Source
<Window x:Class="XamlTest.Window12" xmlns="http://schemas.microsoft.com/win ...
- C++杂记:运行时类型识别(RTTI)与动态类型转换原理
运行时类型识别(RTTI)的引入有三个作用: 配合typeid操作符的实现: 实现异常处理中catch的匹配过程: 实现动态类型转换dynamic_cast. 1. typeid操作符的实现 1.1. ...