我有一个windows service
检查日期并向用户发送通知余款,以供用户订阅服务的付款。
它是每月付款服务系统,用户必须在月末付款,并且该系统向用户发送2笔余款通知:
1)如果未付款,则在截止日期前的N天之前。
2)如果未收到付款,请在截止日期之前发送余款。
代码:
public enum PaymentStatusEnum
{
Ask_For_Payment = 0,
Payment_Remainder_Sent = 1,
Full_Payment_Done = 2,
Payment_Not_Done = 3,
}
public class ServicePaymentModel
{
public int PaymentId { get; set; }
public string Email { get; set; }
public int PaymentStatus { get; set; }
public string AdminId { get; set; }
public int NoOfDaysPassed { get; set; }
public decimal DueAmount { get; set; }
public decimal PaymentMade { get; set; }
}
public void SendNotification()
{
int daysBeforeDeadline = 10;
int deadlineDays = 20;
var paymentModel = new PaymentModel
{
Today = 10/5/2019,
DaysBeforeDeadline = daysBeforeDeadline,
DeadlineDays = deadlineDays
};
//Get all the payments whose daysBeforeDeadline or deadlineDays condition is met.
//For eg: If some users subscription started from 1/5/2019 and Todays date is 10/5/2019 then this users will be will be fetched because of daysBeforeDeadline.
//For eg: If some users subscription started from 20/4/2019 and Todays date is 10/5/2019 then this users will be will be fetched because deadlineDays condition
List<ServicePaymentModel> payments = MyRepo.GetPayments(paymentModel);
if (payments != null && payments.Count == 0)
return;
UserPayment userPayment = null;
foreach (var payment in payments)
{
try
{
if (payment.DueAmount > 0) //Payment not done
{
if (paymentModel.DeadlineDays == payment.NoOfDaysPassed
&& payment.PaymentStatus == (int)PaymentStatusEnum.Payment_Remainder_Sent) // payment not made on deadline
{
userPayment = new UserPayment
{
PaymentId = payment.Id,
PaymentStatus = (int)PaymentStatusEnum.Payment_Not_Done
}
SendNotificationToUser(payment);//method handles email sending and different email template for user
SendNotificationToAdmin(payment)//method handles email sending and different email template for Admin telling him about which user payment has not been received
}
else if (paymentModel.DaysBeforeDeadline == payment.NoOfDaysPassed
&& payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)//payment not done after N days
{
userPayment = new UserPayment
{
PaymentId = payment.Id,
PaymentStatus = (int)PaymentStatusEnum.Payment_Remainder_Sent
}
SendNotificationToUser(payment);//method handles email sending and different email template for user
SendNotificationToAdmin(payment)//method handles email sending and different email template for Admin telling him about which user payment has not been received
}
}
else if (payment.DueAmount == 0) // payment done
{
userPayment = new UserPayment
{
PaymentId = payment.Id,
PaymentStatus = (int)PaymentStatusEnum.Full_Payment_Done
}
if ((paymentModel.DeadlineDays == payment.NoOfDaysPassed
&& payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)// payment made on deadline
{
SendNotificationToUser(payment);//method handles email sending and different email template for user along with message and body
SendNotificationToAdmin(payment)//method handles email sending and different email template for admin along with message and body
}
else if (paymentModel.DaysBeforeDeadline == payment.NoOfDaysPassed
&& payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)//payment done before XX days
{
SendNotificationToAdmin(payment)//method handles email sending and different email template for admin along with message and body
}
}
PaymentRepo.UpdateUserPaymentStatus(userPayment);
}
catch (Exception ex)
{
//do nothing and continue processing other payment
}
}
}
Run Code Online (Sandbox Code Playgroud)
我看过这个多元的视讯视频,其中作者-Zoran Horvat说我们可以将几乎所有的If支票转换为面向对象的解决方案,并且您可以看到我的代码包含很多if支票,明天是否Conditions
还会添加更多的if支票,If将会大大增长创造维护之夜的母马。
我Conditions and PaymentStatus
的所有工作都是基于If检查进行的,但是在这里,如果条件转换为面向对象的解决方案,以及在这种情况下是否真的可行,那么我将无法解决这个问题。
那么是否有可能通过删除if检查或其他更好的方法来使此代码面向对象呢?
public void SendNotificationRefactor2()
{
int daysBeforeDeadline = 10;
int deadlineDays = 20;
var paymentModel = new PaymentModel
{
Today = 10 / 5 / 2019,
DaysBeforeDeadline = daysBeforeDeadline,
DeadlineDays = deadlineDays
};
//Get all the payments whose daysBeforeDeadline or deadlineDays condition is met.
//For eg: If some users subscription started from 1/5/2019 and Todays date is 10/5/2019 then this users will be will be fetched because of daysBeforeDeadline.
//For eg: If some users subscription started from 20/4/2019 and Todays date is 10/5/2019 then this users will be will be fetched because deadlineDays condition
List<ServicePaymentModel> payments = MyRepo.GetPayments(paymentModel);
if (payments != null && payments.Count == 0)
return;
//UserPayment userPayment = null;
foreach (var payment in payments)
{
try
{
// Breaking this out into a method is optional, really, because there's little chance it'll
HandlePayment(paymentModel, payment);
}
catch (Exception ex)
{
// SWALLOWING EXCEPTIONS IS AN INDESCRIBABLY BAD IDEA. DON'T DO THIS.
}
}
}
protected void HandlePayment(PaymentModel paymentModel, ServicePaymentModel payment)
{
var userPayment = new UserPayment
{
PaymentId = payment.Id
};
if (payment.DueAmount > 0) //Payment not done
{
if (paymentModel.DeadlineDays == payment.NoOfDaysPassed)
{
if (payment.PaymentStatus == (int)PaymentStatusEnum.Payment_Remainder_Sent)
{
userPayment.PaymentStatus = (int)PaymentStatusEnum.Payment_Not_Done;
}
else if (payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)//payment not done after N days
{
userPayment.PaymentStatus = (int)PaymentStatusEnum.Payment_Remainder_Sent;
}
SendNotificationToUser(payment);//method handles email sending and different email template for user
SendNotificationToAdmin(payment);//method handles email sending and different email template for Admin telling him about which user payment has not been received
}
}
else if (payment.DueAmount == 0) // payment done
{
userPayment.PaymentStatus = (int)PaymentStatusEnum.Full_Payment_Done;
if (paymentModel.DeadlineDays == payment.NoOfDaysPassed)
{
if (payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)
{
SendNotificationToUser(payment);//method handles email sending and different email template for user along with message and body
SendNotificationToAdmin(payment);//method handles email sending and different email template for admin along with message and body
}
else if (payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)
{
SendNotificationToAdmin(payment);//method handles email sending and different email template for admin along with message and body
}
}
}
PaymentRepo.UpdateUserPaymentStatus(userPayment);
}
Run Code Online (Sandbox Code Playgroud)
首先:专注于目标。把更多的东西做成OO会使它更好的信念是一种信念结构,我称之为“对象幸福障碍”。请记住,OO代码的目的是通过非常清楚一段代码提供什么服务,它的公共接口是什么以及它如何与其他组件交互来降低大型团队工作的大型程序的成本。这不是使小型代码更好的通用技术。
您的目标不应该是使程序“更多面向对象”。它应该是为了降低成本,所以问自己“与该计划相关的成本是多少?” 您在哪里花钱,还记得您的薪水可能是大部分钱?
例如:
当业务流程发生变化时,我们将花费太多时间来更新代码。
如果这是问题所在,那么我将使该程序更加面向对象,而不是通过“用多态替换条件”。仅仅因为它是多态的就不能使其成为OO。之所以成为OO,是因为我们已经确定了业务领域中的基本概念,并将这些概念封装到仅在业务流程发生变化时才需要更改的对象中。
要看的关键是您的非常有用的图表,其中显示:
因此,将其编纂。做一个基础课EventTrigger
。您已经有一个表示状态的类型。制作一个名为的课程EventAction
。上课Condition
。现在我们的流程是什么?
for each trigger in eventTriggers
if any trigger condition is met
execute all trigger actions
Run Code Online (Sandbox Code Playgroud)
现在,我们只需要一个if
语句,就像您想要的一样。现在,您可以为每个条件编写一个类,为每个动作编写一个类,并将触发器绑定在一起。
如果要更改与特定触发器关联的操作,请在一个位置而不是大量的意大利面条代码中进行更改。
而且,该技术可以进行许多其他改进。您可以轻松添加日志记录;记录只是另一项操作。您可以组合动作;做出一个同时执行两个动作的动作。等等。
您甚至可以制作一个如下的配置文件:
TRIGGER PaymentReceivedTrigger HAS CONDITION AskForPayment WITH ACTIONS SetFullPayment, EmailAdmin
…
Run Code Online (Sandbox Code Playgroud)
现在,您可以基于配置文件来设置整个系统,而无需编写C#代码。
但是如果那不是问题怎么办?如果问题是:
我们花太多时间来追踪错误
要么
我们的表现很差,我们也不知道为什么
要么
我们完全与一个数据库供应商联系在一起,但是它们太昂贵了。我们如何降低切换后端的成本?
还是其他一百万件事?在这些情况下,您不想浪费任何时间来构建OO业务流程引擎;您想集中精力解决实际上会花钱的问题,并找出降低成本的方法。