最近我一直在修补基于SOLID原则的TDD和编码。我有一个场景如下:
IRecurringProfile
,IRecurringProfileTransaction
,每隔一段时间执行一系列付款,例如每月IRecurringProfilePayment
相关联的IRecurringProfile
,以表明付款未通过。 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}}
-
作为旁注,这个问题补充了我最近关于类似主题的帖子。
答案 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。