C# 坚实的原则以及如何实际实施这些原则
最近,我一直在修补TDD和基于可靠原则的编码。我有一个场景如下:C# 坚实的原则以及如何实际实施这些原则,c#,dependency-injection,solid-principles,C#,Dependency Injection,Solid Principles,最近,我一直在修补TDD和基于可靠原则的编码。我有一个场景如下: var marker = new TransactionRecurringPaymentMarkAsFailureDecorator( new RecurringPaymentMarkAsFailure( /* dependencies */ )); 您可以使用IRecurringProfile,它每隔一段时间(例如每月)执行一系列付款 当付款尝试通过但失
var marker =
new TransactionRecurringPaymentMarkAsFailureDecorator(
new RecurringPaymentMarkAsFailure(
/* dependencies */
));
- 您可以使用
,它每隔一段时间(例如每月)执行一系列付款IRecurringProfile
- 当付款尝试通过但失败时,将创建一个链接到
的IRecurringProfilePayment
,以显示付款未通过李>IRecurringProfileTransaction
- 付款的失败计数将增加
- 重试付款并发送另一个失败通知(如果付款失败)的日期/时间也会更新
- 如果支付的失败计数达到最大阈值(例如3次失败的尝试),则将暂停
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;
}
}
}
--
作为旁注,这个问题补充了我最近关于类似主题的帖子。在我看来,你违反了单一责任原则,因为你的
重复支付MarkasFailure
有两个责任。除了将付款视为失败的责任外,它还增加了管理交易的责任(使用(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 */
));
正如我所说的,这个实现有点幼稚,因为您可能会有许多需要包装的类,这意味着每个类都会有自己的decorator
相反,让所有执行用例的类实现一个通用接口,类似于
ICommandHandler
。这允许您创建一个通用TransactionCommandlerDecorator
类来包装所有这些实例。请看这篇文章以了解有关此模型的更多信息:。这两个接口是什么职责?这篇链接的文章读得很好-我目前正在研究如何为发布的代码实现一个通用的装饰器模式。第一个职责是实际的业务逻辑。另一个职责是运行数据库事务中的所有内容(这是一个交叉关注点)。更改事务策略并不是在更改业务逻辑的同时进行更改。我一直在尝试decorator&command方法,这是有意义的。但是,我的主要问题是,如果存在乐观并发异常,在这种情况下,我通常要做的是刷新数据库项,然后执行相同的代码。我尝试创建了一个dboptimisticconcurrentyretrycorator
,它可以工作。但是,当它调用MarkAsFailureCommand.Handle()时
,由于乐观并发错误,这无法知道它需要刷新数据库项。有没有办法解决这个问题?@Steven,你的博客太棒了。非常感谢!@KarlCassar:对我来说,这似乎是过早的优化。在过去的几年中,我体验到命令的性能是h从来都不是问题。在你的情况下,你想要什么,一个快速的系统还是一个正确的系统?我打赌它会足够快。此外,从业务层的角度来看,这只是巧合,这些数据已经加载。关于在使用id时失去编译时支持,有一些简单的方法可以解决这个问题。例如,使用cons接受实体但存储其id的tructor。