5

最近我学习了 SOLID 开发,现在我面临着一些挑战,什么时候是好的实践,什么时候不是。

例如,我开发了一个有会员的网站。我构建了一个身份验证业务逻辑类,该类应该可以解决身份验证场景,它们是:

  1. 登录用户
  2. 获取登录用户
  3. 注册用户
  4. 恢复用户密码

这个类有 4 个依赖项:

  1. 数据库/服务
  2. HttpContext(用于用户状态)
  3. ValidationRules(为了登录用户的一些其他业务逻辑规则)
  4. SMTP - 用于发送

现在感觉有些代码有异味,因为没有使用依赖注入。

一些我不确定的问题:

  1. 我可以将 SMTP 依赖项直接注入到恢复密码和注册方法,因为所有其他方法都与它无关。我是不是该?
  2. Logout等一些方法没有使用DB,明知道不需要使用,为什么还要启动db依赖。
  3. 这是为了我的一些业务逻辑。在我的控制器中,这个问题要大得多,因为我需要加载不止一个 BL,而且我所有的业务类都有相同的难闻气味......

我觉得我做错了什么,请帮助!

4

1 回答 1

14

一个身份验证业务逻辑类,它应该制定身份验证方案,它们是:登录用户、获取登录用户、注册用户、恢复用户密码。

您的身份验证业务逻辑类已经违反了单一职责原则。尽管您可能认为该类的唯一职责是“身份验证”,但这很难称为一项职责,因为您可以实现数百个处理身份验证的用例,这会导致一个丑陋的大屁股类。那个班级还会有一个责任吗?相反,每个类实现一个用例。在这种情况下,您的班级将承担非常明确和狭窄的职责。

HttpContext(用于用户状态)

您的业​​务逻辑不应该对所使用的技术一无所知(当然,除了它是 .NET,我们不能真正抽象出 .NET),因此它不应该依赖于HttpContext. 依赖HttpContext违反了依赖倒置原则(DIP) 和接口隔离原则(ISP)。

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

此外,即使您改用System.Web.HttpContextBase抽象,您仍将依赖于由较低级别模块定义的抽象,而根据 DIP,“抽象由上层/策略层拥有”。这背后的想法是您不希望您的代码对较低层有很强的依赖性,因为这会使较高层更加依赖于此,除此之外,较低层不能定义一个抽象,即适合您的应用程序。接下来表达这个问题。

ISP 声明“不应强迫任何客户端依赖它不使用的方法”。换句话说,抽象应该针对客户的特殊需求进行定制,并且应该是狭窄的。“这种缩小的接口也称为角色接口”。两者都HttpContext违反HttpContextBase了 ISP,因为它们非常广泛并且适用于一般用途。他们是一个糟糕的大州,一切都是一串串。这会导致您的代码使用非常广泛的 API,从而使该 API 更难使用。它还阻碍了可测试性,因为不可能伪造 aHttpContext甚至是摘要HttpContextBase造假是不愉快的。如果您应用 ISP,您的生产代码和测试代码都会变得更加简洁。它还阻碍了您的应用程序的可重用性,因为您可能希望稍后在后台进程中运行您的业务逻辑的某些部分,其中没有 Web 请求的概念。而且它阻碍了灵活性,因为有时您可能想要拦截或装饰您在其上执行的某些操作HttpContext。例如,您可能希望在任何时候从 HttpContext 请求用户详细信息时记录。但是,当您的代码直接依赖于 HttpContext 时,这意味着您被迫在整个代码库中进行彻底的更改。必须在整个代码中进行彻底的更改表明您违反了开放/封闭原则

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

我可以直接注入 SMTP 依赖

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

这可能听起来很奇怪,您可能会觉得您永远不会改变发送消息的方式,因为电子邮件是这样做的唯一方式。但是即使电子邮件存在多年,您也可能希望改变处理电子邮件消息的方式,因为在您当前的实现中,电子邮件消息不会随业务事务自动发送。这意味着您可能已将消息推送到 SMTP 服务器(无法回滚的操作),但之后整个操作仍可能失败。当它失败时,意味着打开的数据库事务被回滚,但此时邮件仍然被发送。您无法将其回拨,并且稍后您将再次发送该邮件消息。

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

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

Logout等一些方法没有使用DB,明知道不需要使用,为什么还要启动db依赖。

一般来说,当依赖项并不总是被使用时,这不是问题,因为创建对象图(具有所有直接和间接依赖项的服务)应该非常快。但是在您的情况下,您所看到的是您的方法不是很有凝聚力。这表明单一责任违规。相反,您应该为Logout操作提供自己的类。由于这个操作不需要db,所以这里不需要db依赖。

在我的控制器中,这个问题要大得多,因为我需要加载不止一个 BL,而且我所有的业务类都有相同的难闻气味。

您的控制者也可能违反了单一职责原则。一个 SRP 违规的一个很好的迹象是一个类具有的依赖项的数量。启发式是 4 或 5。超过 5 个依赖项,很可能您的类有太多责任。

当您关注 SOLID 时,您将获得许多小型且重点突出的课程。乍一看,这似乎需要大量的工作和代码,但实际上这会使系统更易于维护。如果您正在为如何创建业务逻辑而苦恼,请查看这篇文章

于 2013-10-26T18:49:44.030 回答