这是单一责任原则的正确实施吗?

时间:2014-11-03 17:31:20

标签: c# asp.net asp.net-mvc entity-framework

我想知道这是否是单一责任原则的最佳方法。 此类的责任是提醒用户,由于不活动,帐户即将过期。

在30天不活动时,用户会收到一封电子邮件。 在45天不活动时,用户不会收到他们被禁用的电子邮件。

public class AccountReminder
{

    private readonly IUnitOfWork _uow;
    private readonly ISendExpiringEmail _sendExpiringEmail;

    public AccountReminder(IUnitOfWork uow, ISendExpiringEmail sendExpiringEmail)
    {
        _uow = uow;
        _sendExpiringEmail = sendExpiringEmail;
    }

    public void NotifyUpcomingExpiringAccounts()
    {

        // Establish dates
        DateTime accountWarningDateTime = DateTime.Now.AddDays(-30).Date;
        DateTime expirationDateTime = DateTime.Now.AddDays(-45).Date;

        // Retrieve users whose accounts are either about to expire or have already expired
        var users = _uow.UserRepository
            .FindBy(element => (DbFunctions.TruncateTime(element.LastLoginDate) == accountWarningDateTime));

        // Send a reminder
        foreach (var user in users)
        {
            string htmlBody = "<html xmlns:v=\"urn:...xmlns=\"http://www.w3.org/TR/REC-html40\" >";
            htmlBody += "<head><meta http-equiv=...</head>";
            htmlBody += "<body>";

            //following line for only outlook to display image
            htmlBody += "<v:shape id=\>...</v:shape>";
            htmlBody += "<table width='100%'...</table>";
            htmlBody += "</body></html>";

            AlternateView htmlView = AlternateView.CreateAlternateViewFromString(htmlBody, null, MediaTypeNames.Text.Html);
            AlternateView plainView = AlternateView.CreateAlternateViewFromString(htmlBody, null, "text/plain");
            LinkedResource pic1 = new LinkedResource("images/Logo.jpg", MediaTypeNames.Image.Jpeg);
            pic1.ContentId = "Logo";
            htmlView.LinkedResources.Add(pic1);

            _sendExpiringEmail.SendTo(new[] { user.Email }, "EmailSubject", htmlView, plainView);

        }

        DisableExpiringAccounts(expirationDateTime);
    }

    private void DisableExpiringAccounts(DateTime expirationDateTime)
    {
        var expiredUsers = _uow.UserRepository.FindBy(element => element.Enabled && (DbFunctions.TruncateTime(element.LastLoginDate) <= expirationDateTime));

        foreach (var user in expiredUsers)
        {
            user.Enabled = false;
        }

        if (expiredUsers.Count > 0)
        {
            _uow.SaveChanges();
        }
    }

}

2 个答案:

答案 0 :(得分:1)

我认为您在太高层次上解释责任。这是一个常见问题。罗伯特马丁建议解释它like this

  

单一责任原则(SRP)规定每个软件模块应该只有一个改变的原因

以及(同一链接):

  

当您编写软件模块时,您希望确保在请求更改时,这些更改只能来自一个人,或者更确切地说,来自一个紧密耦合的人群,代表一个狭义的业务功能。< / p>

在您的示例中,业务要求的更改可能会涉及:

  • 帐户到期机制背后的逻辑

  • 用于通知用户的通信渠道

  • 消息的格式

所以我在这里至少可以看到3个职责,至少3个层次结构(名称只是示例):

  • IAccountExpirationAgent - 验证帐户是否已过期的部分,如果是,则禁用它,可能只有一个实现

  • IAccountExpirationNotifier - 发送电子邮件的类(例如AccountExpirationEmailNotifier),或短信,或其他

  • IAccountExpirationMessageFormatter - 格式化消息的类(例如AccountExpirationMessageHtmlFormatterAccountExpirationMessagePlainTextFormatter

分别

答案 1 :(得分:0)

确实,这个问题应该发布在codereview.stackexchange.com上,但我仍然想提供一些指示......

这可能是错的;但我解释单一责任委托人的方式是“一种方法应该有一份工作”。这使我们无法编写单一的600行方法来完成所有操作并且维护起来非常麻烦。

更小,更专注(单一责任)的方法更容易维护,因为它们使更大的应用程序更易于理解。

如果我要重写你的课程,我可能会这样做:

  • 拆分电子邮件发送功能

  • 拆分HTML正文生成;可能会将用户模型发送到Razor视图


public class AccountReminder
{

    private readonly IUnitOfWork _uow;
    private readonly ISendExpiringEmail _sendExpiringEmail;

    public AccountReminder(IUnitOfWork uow, ISendExpiringEmail sendExpiringEmail)
    {
        _uow = uow;
        _sendExpiringEmail = sendExpiringEmail;
    }

    public void NotifyUpcomingExpiringAccounts()
    {
        // Establish dates
        DateTime accountWarningDateTime = DateTime.Now.AddDays(-30).Date;
        DateTime expirationDateTime = DateTime.Now.AddDays(-45).Date;

        // Retrieve users whose accounts are either about to expire or have already expired
        var users = _uow.UserRepository
            .FindBy(element => (DbFunctions.TruncateTime(element.LastLoginDate) == accountWarningDateTime));

        foreach (var user in users)
        {
            SendReminder(user);
        }

        DisableExpiringAccounts(expirationDateTime);
    }

    private void SendReminder(User user)
    {
        // pass a model to a view to generate the HTML
        // the razor implementation should also handle plain-text vs. html mail; you can have two separate templates
        string htmlBody = MyRazorImplementation.GenerateBody(user); 

        _sendExpiringEmail.SendTo(new[] { user.Email }, "EmailSubject", htmlView, plainView);
    }

    private void DisableExpiringAccounts(DateTime expirationDateTime)
    {
        var expiredUsers = _uow.UserRepository.FindBy(element => element.Enabled && (DbFunctions.TruncateTime(element.LastLoginDate) <= expirationDateTime));

        foreach (var user in expiredUsers)
        {
            user.Enabled = false;
        }

        if (expiredUsers.Count > 0)
        {
            _uow.SaveChanges();
        }
    }

}