SOLID原则以及如何实际实现它们

时间:2012-10-17 07:48:17

标签: c# dependency-injection solid-principles

最近我一直在修补基于SOLID原则的TDD和编码。我有一个场景如下:

  • 可以IRecurringProfileIRecurringProfileTransaction,每隔一段时间执行一系列付款,例如每月
  • 当付款尝试通过且失败时,会创建与IRecurringProfilePayment相关联的IRecurringProfile,以表明付款未通过。
  • 付款的失败次数增加。
  • 还会更新重试付款并发送其他失败通知的日期/时间(如果付款失败)。
  • 如果付款的失败次数达到最大阈值(例如3次尝试失败),public class RecurringPaymentMarkAsFailure { private readonly IPaymentFailedNotificationSender paymentFailedNotificationSender; private readonly IRecurringProfileFailureNextNotificationDateUpdater failureNotificationDateUpdater; private readonly IRecurringProfileSuspender recurringProfileSuspender; public RecurringPaymentMarkAsFailure(IPaymentFailedNotificationSender paymentFailedNotificationSender, IRecurringProfileSuspender recurringProfileSuspender, IRecurringProfileFailureNextNotificationDateUpdater failureNotificationDateUpdater) { this.paymentFailedNotificationSender = paymentFailedNotificationSender; this.failureNotificationDateUpdater = failureNotificationDateUpdater; this.recurringProfileSuspender = recurringProfileSuspender; } private void checkProfileStatus(IRecurringProfile profile) { if (profile.Status != Enums.RecurringProfileStatus.Active) { throw new Exceptions.RecurringProfileException("This cannot be called when the profile is not marked as active"); } } private void incrementFailureCount(IRecurringProfilePayment payment) { payment.FailureCount++; } public IRecurringProfileTransaction MarkPaymentAsFailed(IRecurringProfilePayment payment, string failureData) { using (var t = BeginTransaction()) { checkProfileStatus(payment.RecurringProfile); IRecurringProfileTransaction transaction = payment.Transactions.CreateNewItem(); transaction.OtherData = failureData; transaction.Status = Enums.RecurringProfileTransactionStatus.Failure; paymentFailedNotificationSender.CreateAndQueueNotification(transaction); failureNotificationDateUpdater.UpdateNextFailureNotificationDate(payment); incrementFailureCount(payment); if (payment.FailureCount >= payment.RecurringProfile.MaximumFailedAttempts) { recurringProfileSuspender.SuspendRecurringProfile(payment.RecurringProfile); } transaction.Save(); t.Commit(); return transaction; } } } 将被暂停。
  • 此外,每次失败时,都会发送通知,通知客户付款未通过,并会再次重试。

下面是我创建的一些示例代码,主要处理将定期配置文件付款标记为失败的任务。我试图遵循SOLID原则,以及构造函数注入。想知道这是否违反任何这些原则或任何编程最佳实践,并使代码受到任何形式的审查,以便可以改进。该代码还使用NHibernate作为ORM。

{{1}}

-

作为旁注,这个问题补充了我最近关于类似主题的帖子。

1 个答案:

答案 0 :(得分:3)

在我看来,你违反了单一责任原则,因为你的RecurringPaymentMarkAsFailure有两个责任。除了将付款视为失败的责任外,它还增加了管理交易的责任(using (BeginTransaction);这是一个贯穿各领域的关注。

在处理业务逻辑的系统中,您可能会有许多这样的类,并且可能所有(或许多)具有完全相同的事务代码。相反,考虑通过允许将此行为添加为装饰器来考虑遵循开放/封闭原则。这很有可能,因为事务是此代码中的第一个AND最后一个操作。这个装饰器的天真实现可能如下所示:

public class TransactionRecurringPaymentMarkAsFailureDecorator
    : RecurringPaymentMarkAsFailure
{
    private RecurringPaymentMarkAsFailure decoratedInstance;

    public RecurringPaymentMarkAsFailure(
        RecurringPaymentMarkAsFailure decoratedInstance)
    {
        this.decoratedInstance = decoratedInstance;
    }

    public override IRecurringProfileTransaction MarkPaymentAsFailed(
        IRecurringProfilePayment payment, string failureData)
    {
        using (var t = BeginTransaction())
        {
            var transaction = this.decoratedInstance
                .MarkPaymentAsFailed(payment, failureData);

            t.Commit();

            return transaction;
        }
    }
}

这个装饰器允许你按如下方式包装类:

var marker =
    new TransactionRecurringPaymentMarkAsFailureDecorator(
        new RecurringPaymentMarkAsFailure(
            /* dependencies */    
        ));

正如我所说,这个实现有点天真,因为你可能会有许多需要包装的类,这意味着每个类都会得到自己的装饰器。虽然这完全是SOLID,但它不是DRY。

相反,让所有执行用例的类实现一个通用接口,如ICommandHandler<TCommand>。这允许您创建一个通用的TransactionCommandHandlerDecorator<TCommand>类来包装所有这些实例。请查看此文章,详细了解此模型:Meanwhile… on the command side of my architecture