从New到依赖注入的思维模式转变

当你使用类似于C#或VisualBasic这种强类型语言,在实例化对象时,你往往会使用New这个关键字。我敢保证超过90%的开发者都会不假思索的使用New这个非常重要的特性。大部分开发者,有时听过,在进行软件应用构建的实践过程中,就像搭建乐高积木。(作者曾经写过一篇《乐高积木和软件工程的关系(http://ardalis.com/Tight-Coupling,-Legos,-and-Super-Glue)》)几天前,我突发奇想,打算用三个词来整理我的思想,我希望这三个词能让开发者更加印象深刻。

        New Is Glue.(直接翻译应该是,New是胶水。。但是这样不符合翻译信达雅的要求,译者翻译成,“New,如阴魂不散,也许有些欠妥当,但求拍砖,哈哈.

        说明:在第一次发表这篇文章之前,好莱坞出品了一个电影也是关于这个主题,一个乐高积木坏蛋总是用胶水把其他物品粘起来。千万不要做这个坏蛋啊!

        无论何时,当你使用New这个关键词时,你正在将你的代码和你的细节实现粘在一起。你是在永久性硬编码的方式来实现你的应用程序!

        这个东西,最终成为了一个庞然大物啊!

        写代码就像一段婚姻,亘古永恒,至死无休。一旦你写下关键字New、开始实例化类型从元数据就是交换戒指,而分发你的应用程序,就是离开教堂,而改变你这段写代码生涯中的伴侣,将成为一个非常昂贵的行为。这是一个非常重大的决定。你需要非常明确的知道,这是唯一的一种实现的方法,或者你也可以思考一下调用的过程。

          "使用New这个关键词,并不是一种错误,他只是一种设计策略。就像所有的设计策略,他也许是一种经典的策略,而不是一种符合实践的策略"。

        用婚姻这个比喻来形容这个事情也许有一点过分,让我们在把这个想法抛诸脑后之前,先仔细思考一下这个事情吧。这就像你的代码需要有很多种行为,这些行为来源于很多个类。事实上,使用其他的类的一种常用的做法是用抽象化你的应用中的每一个类。而使用New这种方式,意味着会有很多个重载才能让你的应用程序能够更加高效的执行。

      但是你要使用的这种行为(behavior)、或者服务(service)只有在一个地方出现了么?也许他不是一种完美、从头用到尾的类,但是他是一种你需要马上就使用的类吗?或者像我想的那样,也许你抛弃了其他的类,只是为了实现一些特殊的东西?没关系,让我们在思考另外一个比喻如何。

        重服务商、轻资产(原文为Favor Contrators over Employees,翻译出来是重客户而轻员工,但是依然不符合信达雅)

        你的应用程序需要特定的服务层代码(services)。假设你的应用程序就像一个企业。下面有一些事情是你的应用需要完成的一些事情,以及这些事情是如何实现的。一个小企业,也许需要办公场所,除此之外,还需要特殊的业务应用,例如电能、电话服务、网络服务、采购服务等等。在企业中,你有很多服务的提供者和客户这些关系,这些也许很容易改变,当然你也有很多员工,这些关系也许让你很难改变(就像我们讨论一个国家,这个国家的消防员雇佣费用非常高、要雇佣一个消防员非常难)。现在让我们来看看,这个小企业是如何运作的吧

      第一个段子,你的企业需要很多全职的资源。你有Bob,他是一位电工,他将确保你的电力供应正常;Joan,通信专家,他将确保你的电话不会断线;Joe,卡车司机,他将保证你的产品能够随时送达。你需要租用一个让他们能够正常办公,也许你需要租五年,你将付出很多日常开销。

     第二个段子,你需要许多服务商或者说是客户,你可以使用很多专业提供技术服务的企业为你提供电力、电话、互联网,而且你可以随时更换到其他的服务商。你可以使用FedEx、USPS\UPS等等任何快递公司,只要有一家满足你的需求。这是,你就会发现,随着你的公司逐渐成长,你无法确保你的需求不会改变,而你的办公室也许对你来说已经足够大了,你可以按照每个月付租金的方式来改变你的办公室了。

    这两个段子中,哪个段子可以提供更大的自由,能够让你的公司适应未来的变化?哪一个让你的业务功能实现过于僵化、从而让试图根据需求改变的你面临了许多困难?

按服务提供者解耦吧

    New,阴魂不散。它把你的代码与上下文绑定在一起。如果你需要想让你的代码实现更加的弹性化,使用接口也许将让你的代码更加方便的松耦合。服务也许正是你所需要的,也许他无关紧要-你可能经常使用接口来替换你的代码,哪怕这个类只使用了一次。例如,你发送一个邮件,可能会使用下面的代码:

using (var client = new SmtpClient())using (var message = new MailMessage(fromEmail, toEmail))
{
    message.Subject = subject;
    message.Body = bodyHtml;
    message.IsBodyHtml = true;
    client.Send(message);
}

    请注意到,这里你使用了两个New关键字,而这个类只是让你实现消息的发送。你可以按照下面这种写法来使用接口进行改造。

public interface IEmailClient
{    void SendHtmlEmail(string fromEmail, string toEmail,                       string subject, string bodyHtml);
}
  在这段代码中,第一个代码端已经简单的使用接口进行了重写。
public class SomeService
{    private readonly IEmailClient _emailClient;    public SomeService(IEmailClient emailClient)
    {
        _emailClient = emailClient;
    }    public void DoStuff(User user)
    {        string subject = "Test Subject";        string bodyHtml = GetBody(user);
        _emailClient.SendHtmlEmail("noreply@whatever.com", user.EmailAddress, subject, bodyHtml);
    }
}

  特别说明:假定DoStuff()方法实现了一些有用的值运算工作,发送邮件只是他一个方面的工作而已,执行成功了,这也是主要的意图,但是这是否让你更加清楚的理解了将SmtpClient的依赖关系进行移除的意义了?

  让我们来看看,在这个类中出现了多少个New关键字?使用VisualStudio来查找你的解决方案中,你使用了多少个New关键字来进行实例化吧。使用ctrl-shift-f来查找所有文件,搜索“new”,正规解决方案、全字匹配、文件类型为.cs的文件。

从New到依赖注入的思维模式转变

  来看一眼在所有文件中的总数吧。在我用的一个简单的asp.net mvc项目中,在47个搜索文件中,有25个文件、出现了280行匹配的结果。在我的测试项目中,有四个文件、28行代码满足搜索的结果。这个匹配结果中,最多的是出现在SqlHelper.cs文件中。而我的控制器中,出现了总共7行匹配结果。其他的都是创建model/viewmodel,而不是服务层代码。

何时使用New关键字?

   使用New关键字,并不意味这错误,他只是一种设计策略。跟所有的设计策略一样,它也许是一种经典的策略,而不是一种符合实践的策略。你可以在未来不需要更弹性化的时候来使用new。使用它的使用,你要确保他不会影响你的代码带来不利的影响。试着考虑转换一下你代码中类的依赖关系-那就是,如何让你的类与类之间的依赖关系进行控制反转。多考虑集中设计策略、今天的考虑也许会改变你的未来。一种常见的例子是,我们的应用中有很多本地资源的硬编码关系,如文件系统、如本地内存(session,cache,global collections),以及一起写本地服务,例如邮件服务。当我们的应用需要迁移到前后端分离的架构、应用到前端时,很多的问题产生了。将这些资源进行松耦合,使用webfarm或者云端优化,而不是数以千计的new语句这种方式开发那些让我们不得不花费大量的实践才能修复的紧耦合实现。

  除此之外,如果你使用new关键字去搜索,也许你会发现很多重复的代码。记住两个原则,Don’t Repeat Yourself (DRY) and Single Responsibility两个原则。如果你超过一个地方使用相同的方式、实现了相同的代码(例如,有10个地方用到了new SqlConnection),这就违反了DRY原则。如果你有10个不同的类,采用这种类似的方式,同样需要创建SQL数据库连接,这就违反了SRP原则。你必须掌握这个知识,即一个数据库实例化,只允许出现在一个位置,用一个类来实现这个原则。同样也适用于其他你需要对复杂类进行实例化的情况。像strings、datetimes等内部对象是引发的异常也许会非常低级,而将这些异常使用特定的异常处理类来进行封装是一种不错的方式。

更进一步

  学会去搜索一下你的代码中出现的new关键字、以及在代码审查时看看团队中使用的new关键字。思考一下,这种使用是否适合。将你的代码按照职能进行梳理、让你的代码行尽可能的少,看一看你的代码是否变得越来越松耦合,越来越容易维护吧!   

   作者简介:Adalis.com是作者的个人主页。Steve先生是一位经验丰富的软件架构师、主要聚焦于DDD和ASP.NETCore。他在Pluralsight 和 DevIQ做出了非凡的成就,让开发者能够开发出更好、更利于维护的代码。他善于进行应用程序的评估和团队活动方面的指导。

  译者:溪源,长沙.NET开发者。