我有一个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
}
}
}
我已经看到了这个Plural sight video,其中作者-Zoran Horvat 说,我们可以将几乎所有的If检查都转向面向对象的解决方案,并且正如您所见,我的代码包含很多if检查而明天,如果添加的Conditions
数量超过此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);
}
答案 0 :(得分:6)
首先,要专注于目标。把更多的东西做成OO会使它更好的信念是一种信念结构,我称之为“对象幸福障碍”。请记住,OO代码的目的是通过非常清楚一段代码提供什么服务,它的公共接口是什么,以及降低大型团队工作的大型程序的成本。它如何与其他组件交互。这不是使小型代码更好的通用技术。
您的目标不应是使程序“更多面向对象”;它应该是为了降低成本,所以问自己“与该计划相关的成本是多少?”您在哪里花钱,还记得您的薪水可能是大部分钱?
例如:
当业务流程更改时,我们花费太多时间来更新代码。
如果这是问题,那么我将使该程序更具面向对象,而不是通过“用多态替换条件”。仅仅因为它是多态的就不能使其成为OO。使其成为OO的原因是我们已经确定了业务领域中的基本概念,并将这些概念封装到仅在业务流程发生变化时才需要更改的对象中。
要看的关键是您的非常有用的图表,显示:
因此,将其编成代码。创建一个基类EventTrigger
。您已经有一个表示状态的类型。制作一个名为EventAction
的课程。制作课程Condition
。现在我们的流程是什么?
for each trigger in eventTriggers
if any trigger condition is met
execute all trigger actions
现在,您可以根据需要使用一个if
语句。现在,您可以为每个条件编写一个类,为每个动作编写一个类,并将触发器绑定在一起。
如果要更改与特定触发器关联的操作,请在一个位置而不是大量的意大利面条代码中进行更改。
此外,此技术还可以进行许多其他改进。您可以轻松添加日志记录;记录只是另一项操作。您可以组合动作;做出一个同时执行两个动作的动作。依此类推。
您甚至可以制作如下配置文件:
TRIGGER PaymentReceivedTrigger HAS CONDITION AskForPayment WITH ACTIONS SetFullPayment, EmailAdmin
…
现在,您可以基于配置文件来设置整个系统,而无需编写C#代码。
但是如果不是 问题怎么办?如果问题是什么呢?
我们花太多时间来查找错误
或
我们的表现很差,我们也不知道为什么
或
我们完全与一个数据库供应商联系在一起,但是它们太昂贵了;我们如何降低切换后端的成本?
还是其他一百万种东西?在这些情况下,您不想浪费任何时间来构建OO业务流程引擎;您想专注于实际上使您付出金钱的问题,并找出降低成本的方法。