c# - 如何通过删除 if 检查使代码面向对象?

标签 c# .net

我有一个 windows 服务,它检查日期并向用户发送通知剩余,以便用户订阅服务。

它是一个月度支付服务系统,用户必须在月底付款,并且该系统会向用户发送 2 个剩余通知:

1) 如果未付款,则在截止日期后 N 天内。

2) 如果未收到付款,请在截止日期前发送余款。

下面是向用户和管理员发送通知所依据的条件: enter image description here

代码:

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其中 Author - Zoran Horvat 说我们可以将几乎所有 If 检查转向面向对象的解决方案,正如您所看到的,我的代码包含大量 if 检查和明天 if 更多条件比这更重要的是,如果会极大地增长,就会造成维护方面的噩梦。

我所有的 Conditions 和 PaymentStatus 都是基于 If 检查来处理的,但是在这里我不知道如何将这个 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);
}

最佳答案

首先:专注于目标。让更多面向对象的东西变得更好的信念是我称之为“对象幸福障碍”的信念结构。请记住,面向对象代码的目的是降低大型团队开发大型程序的成本,方法是非常清楚一段代码提供什么服务,它的公共(public)接口(interface)是什么,以及它如何与其他组件交互。它不是使小代码变得更好的通用技术。

你的目标不应该是让程序“更面向对象”;应该是降低成本,所以问问自己“这个程序的相关成本是多少?”您将钱花在哪里,还记得您的薪水可能是这些钱的大部分吗?

例如:

We're spending too much time updating the code when business processes change.

如果这是问题所在,那么我会让程序更面向对象,但不是通过“用多态性替换条件”。仅仅因为它是多态的并不能使它成为 OO。使它成为 OO 的原因是我们已经确定了业务领域中的基本概念,并将这些概念封装到只有在业务流程发生变化时才需要更改的对象

要看的关键是您非常有用的图表,它显示:

  • 什么外生条件会触发状态变化?
  • 状态变化是什么?
  • 什么 Action 与状态变化相关?

所以,把它编成法典。创建一个基类 EventTrigger。您已经有了表示状态的类型。创建一个名为 EventAction 的类。创建一个类 Condition。现在我们的流程是什么?

for each trigger in eventTriggers
    if any trigger condition is met
        execute all trigger actions

现在我们只需要一个 if 语句,就像您想要的那样。现在您可以为每个条件编写一个类,为每个操作编写一个类,然后触发器将它们联系在一起。

如果您想更改与特定触发器关联的操作,您可以在一个地方更改它,而不是在一堆意大利面条代码中。

此外,此技术还可以进行许多其他改进。您可以轻松添加日志记录;日志记录只是另一个 Action 。您可以组合 Action ;做一个采取两个 Action 并同时运行它们的 Action 。等等。

您甚至可以制作如下所示的配置文档:

TRIGGER PaymentReceivedTrigger HAS CONDITION AskForPayment WITH ACTIONS  SetFullPayment, EmailAdmin
…

现在您可以基于配置文件设置整个系统,而无需编写 C# 代码。

但如果这不是问题呢?如果问题是:

We're spending too much time tracking down bugs

或者

Our performance is bad and we do not know why

或者

We're completely tied to one database vendor but they are too expensive; how can we lower the cost of switching back ends?

或者其他一百万个东西中的任何一个?在那些情况下,您不想浪费任何时间来构建 OO 业务流程引擎;您想专注于实际花费您金钱的问题,并找出降低成本的方法。

关于c# - 如何通过删除 if 检查使代码面向对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56342870/

相关文章:

javascript - 动态将从 Controller 返回的选择框值呈现为 json

.net - 绑定(bind)配置和行为有什么区别?

.net - 服务契约(Contract)属性中的命名空间 url 是什么

c# - 使用 LINQ 将三个列表连接成一个会引发异常

c# - 如何通过 powershell 或 C# 使用本地 IP 远程更新 azure webapp 防火墙

c# - 从 web.config 读取 AuthorizationSection 提供了不正确的值

c# - 如何使用 Silverlight 读取 XPS(固定页面)文件并在 UI 中显示

c# - 为什么 Object.GetType() 是方法而不是属性?

c# - 一个空的程序集属性元素

c# - SQL Convert(varchar, <fieldname>) 等效于 Crystal Reports 记录选择公式