Real Life与SOLID开发合作

时间:2013-10-26 15:06:49

标签: .net asp.net-mvc architecture dependency-injection solid-principles

最近我学习了SOLID开发,现在我遇到了一些挑战,当它是一个好的做法时,当它不是。

例如,我开发了一个包含成员的网站。我构建了一个Authentication Business Logic类,它可以解决身份验证方案,它们是:

  1. 登录用户
  2. 登录用户
  3. 注册用户
  4. 恢复用户密码
  5. 这个类有4个依赖项:

    1. DB /服务
    2. HttpContext(用户状态)
    3. ValidationRules(一些其他业务逻辑规则,以便登录用户)
    4. SMTP - 用于发送
    5. 现在感觉有些代码有难闻的气味,因为没有使用依赖注入。

      我不确定的一些问题:

      1. 我可以直接将SMTP依赖注入Restore password&注册方法,因为所有其他方法都与它无关。我应该吗?
      2. 某些方法(例如Logout)没有使用数据库,当我知道不需要使用它时,为什么要启动数据库依赖项。
      3. 这是我的一些业务逻辑。在我的控制器中,这个问题要大得多,因为我需要加载一个以上的BL并且我的所有业务类都有同样的难闻气味......
      4. 我觉得我做错了什么,请帮忙!

1 个答案:

答案 0 :(得分:14)

  

一个认证业务逻辑类,应该可以解决这个问题   认证方案,包括:登录用户,登录用户,   注册用户,恢复用户密码。

您的身份验证业务逻辑类已违反Single Responsibility Principle。虽然您可能认为该类的唯一责任是“认证”,但这很难被称为一种责任,因为您可以实现一百个处理身份验证的用例,这会导致一个丑陋的大屁股类。那个班级还有一个责任吗?相反,每个类实现一个用例。在这种情况下,你的课程将有一个非常明确和狭隘的责任。

  

HttpContext(用户状态)

您的业务逻辑不应该对所使用的技术有任何了解(除了它当然是.NET,我们不能真正抽象.NET),因此它不应该依赖于{{1 }}。取决于HttpContext违反了Dependency Inversion Principle(DIP)和Interface Segregation Principle(ISP)。

DIP说"高级模块应该......取决于抽象。"。您的业​​务层是"更高级别的模块",但它不依赖于抽象,而是依赖于较低级别的模块(HttpContext)。这将业务层类与实际的Web逻辑紧密结合在一起。

此外,即使您使用HttpContext抽象,您仍将依赖于由较低级别模块定义的抽象,而根据DIP"摘要由上层/政策层"。这背后的想法是你不希望你的代码强烈依赖于较低层,因为这会使更高级别的层更依赖于此,除此之外,较低级别的层不能定义抽象这对你的申请是正确的。这个问题接下来就表达出来了。

ISP声明"不应该强迫任何客户端依赖它不使用的方法"。换句话说,抽象应该根据客户的特殊需求而定制,并且应该是狭窄的。 "这种缩小的界面也称为角色界面"。 System.Web.HttpContextBaseHttpContext都违反了ISP,因为它们非常广泛,适合一般使用。他们是一个大的国家坏,一切都是一个字符串。这会导致您的代码使用非常广泛的API,这使得该API更难使用。它还会阻碍可测试性,因为伪造HttpContextBase是不可能的,甚至抽象HttpContext也不会令人不愉快。如果您应用ISP,您的生产代码和测试代码将变得更加清洁。它还会阻碍应用程序的可重用性,因为您可能希望稍后在后台进程中运行业务逻辑的某些部分,而不会出现Web请求的概念。它会阻碍灵活性,因为有时您可能想要拦截或修饰您在HttpContextBase上执行的某些操作。例如,您可能希望在从HttpContext请求用户详细信息时记录。但是,当您的代码直接依赖于HttpContext时,这意味着您必须在整个代码库中进行彻底的更改。必须在整个代码中进行彻底更改,这表明您违反了Open/Closed Principle

相反,在BL中定义自己的HttpContext抽象,并在Web应用程序中创建IUserContext实现(实现AspNetUserContext)。请注意,您不应该定义IUserContext抽象,因为这仍然会违反SRP和ISP,并且基本上是漏洞抽象(漏洞抽象是DIP违规),因为业务层不应该“我必须知道网络请求的存在。

  

我可以直接注入SMTP依赖

执行此操作时,您将再次违反Dependency Inversion Principle。您的业​​务层不依赖于抽象,而是依赖于较低级别的模块(您的SMTP类)。这将业务层类与实际的SMTP逻辑紧密结合在一起。相反,您应该为此定义自己的抽象,例如IHttpContext

这可能听起来很奇怪,您可能会觉得永远不会改变邮件的发送方式,因为电子邮件是唯一的方法。但即使电子邮件多年来一直存在,您也可能希望改变处理电子邮件的方式,因为在您当前的实现中,电子邮件不会随业务事务原子发送。这意味着您可能已将邮件推送到SMTP服务器(无法回滚的操作),但此后总操作仍可能失败。当它失败时,意味着回滚打开的数据库事务,但此时仍然发送邮件。您无法将其回拨,并且您最终会在以后再次发送该邮件。这将使您的接收者烦恼,甚至可能导致您在回滚事务后发送不再存在的信息(如包含数据库ID的URL)。

要缓解这种情况,您必须将邮件消息写入事务性队列(例如,包含应发送消息的数据库表)。此操作应在与业务逻辑运行相同的事务中运行。这意味着您可以在同一事务中对多个消息进行排队,并且在操作失败时它们都会回滚。当操作成功时,所有消息都可用,而其他一些(后台)进程可以接收并发送它们。

这只是一种可能的方式。邮件发送方式将来可能会发生变化,但如果没有IMailSender抽象,这将很难修复。

  

某些方法(例如Logout)没有使用DB,我为什么要这样做   当我知道我不需要使用它时,启动db依赖项。

一般情况下,当依赖关系并不总是被使用时,这不是问题,因为创建对象图(具有所有直接和间接依赖关系的服务)should be very fast。但在你的情况下,你所见证的是你的方法并不具备凝聚力。这表明单一责任违规。相反,您应该为IMailSender操作提供自己的类。由于此操作不需要db,因此您不需要db依赖项。

  

在我的控制器中,这个问题要大得多,因为我需要加载更多   然后一个BL和我所有的商业课都有同样的难闻气味..

您的控制器也可能违反单一责任原则。 SRP违规的一个很好的指示是一个类具有的依赖数。启发式是4或5.有超过5个依赖项,你的班级很可能有太多的责任。

当您按照SOLID进行操作时,您将获得许多小型且专注的课程。这看起来好像很多工作和代码,但实际上这会导致系统更容易维护。如果您在如何创建业务逻辑方面苦苦挣扎,请查看this article