3

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

  • 一个可以有IRecurringProfile,它会按一定时间间隔(例如按月)执行一系列付款
  • 当付款尝试通过但失败时,IRecurringProfileTransaction会创建一个链接到IRecurringProfilePayment以显示付款未通过。
  • 付款失败计数增加。
  • 重试付款并发送另一个失败通知(如果付款失败)的日期/时间也会更新。
  • 如果支付的失败计数达到最大阈值(例如 3 次失败尝试),IRecurringProfile则暂停。
  • 此外,每次失败时,都会发送通知通知客户付款未通过,并将再次重试。

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

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;
        }

    }

}

--

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

4

1 回答 1

3

在我看来,您违反了单一职责原则,因为您RecurringPaymentMarkAsFailure有两个职责。除了将支付视为失败的责任外,它还增加了管理交易的责任(using (BeginTransaction)这是一个横切关注点。

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

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>类来包装所有这些实例。查看这篇文章以了解有关此模型的更多信息:同时……在我的架构的命令方面

于 2012-10-17T08:45:52.947 回答