避免 if 和 switch 语句和更通用的方法

问题描述

我想避免使用处理特定情况的“if”和“switch”语句,而是采用更通用的方法

要求: 根据以下详述的许多要求,订单的状态可能被确定为“已确认”、“已关闭”或“需要授权”。

输入:

IsrushOrder: bool
OrderType: Enum (Repair,Hire)
IsNewCustomer: bool
IsLargeOrder: bool

输出

OrderStatus: Enum (Confirmed,Closed,Authorisationrequired)

要求(按照从上到下的优先顺序):

-Large repair orders for new customers should be closed
-Large rush hire orders should always be closed
-Large repair orders always require authorisation
-All rush orders for new customers always require authorisation
-All other orders should be confirmed

================================================ ============================

避免使用 IF 语句的明显答案

这违反了 SOLID 原则的 O。我确信有比添加大量 IF 语句更好的方法

        public IOrder Getorder(OrderInput orderInput)
        {

            // Apply rules based on the priority 
            if (orderInput.IsLargeOrder == true && orderInput.OrderType == OrderTypeEnum.Repair && orderInput.IsNewCustomer == true)
            {
                return new LargeRepairNewCustomerOrder();
            }

            if (orderInput.IsLargeOrder == true && orderInput.OrderType == OrderTypeEnum.Hire && orderInput.IsrushOrder == true)
            {
                return new LargerushHireOrder();
            }

            if (orderInput.IsLargeOrder == true && orderInput.OrderType == OrderTypeEnum.Repair)
            {
                return new LargeRepairOrder();
            }

            if (orderInput.IsrushOrder == true && orderInput.IsNewCustomer == true)
            {
                return new AllrushNewCustomerOrder();
            }

            return new AllOtherOrders();
        }

我能想到的一种不使用 IF 实现的方法是在集合中填充 OrderStatus 并使用 LINQ 进行查询 随着我们添加更多状态,此实现将很快变得复杂。

public class OrderStatusAnalyzer
{
    protected static List<OrderStatus> OrderStatuses;

    public OrderStatusAnalyzer()
    {
        OrderStatuses = OrderStatusCollection.Get();
    }

    public string GetorderStatusTypeByOrderInput(OrderInput orderInput)
    {
        var orderStatusTypes = from orderStatus in OrderStatuses
                               where orderStatus.IsNewCustomer == orderInput.IsNewCustomer
                                     && orderStatus.IsLargeOrder == orderInput.IsLargeOrder
                                     && orderStatus.IsrushOrder == orderInput.IsrushOrder
                                     && orderStatus.OrderType == orderInput.OrderType
                               orderby orderStatus.Priority ascending
                               select orderStatus.OrderStatusType;

        var statusTypesList = orderStatusTypes.ToList();
        var orderStatusType = !statusTypesList.Any() ? "VehicleRepairOrder.Order.AllOtherOrders" : statusTypesList[0];

        return orderStatusType;
    }
}
public static class OrderStatusCollection
{
    public static List<OrderStatus> Get()
    {
        // Note:
        // 1) If we have to avoid referencing null,then we can populate the data with probabilities. Populating data with probabilities will become hard Maintain as we add more status   
        // 2) this is also violating O(Open for extension and closed for modification) of SOLID principle 
        // 3) instead of passing null,if you pass actual unit test will fail
        var orderStatuses = new List<OrderStatus>
        {
            new OrderStatus
            {
                IsLargeOrder = true,IsNewCustomer = true,OrderType = OrderTypeEnum.Repair,IsrushOrder = null,Priority = 1,OrderStatusType = "VehicleRepairOrder.Order.LargeRepairNewCustomerOrder"
            },new OrderStatus
            {
                IsLargeOrder = true,IsNewCustomer = null,OrderType = OrderTypeEnum.Hire,IsrushOrder = true,Priority = 2,OrderStatusType = "VehicleRepairOrder.Order.LargerushHireOrder"
            },Priority = 3,OrderStatusType = "VehicleRepairOrder.Order.LargeRepairOrder"
            },new OrderStatus
            {
                IsLargeOrder = null,OrderType = OrderTypeEnum.Any,Priority = 4,OrderStatusType = "VehicleRepairOrder.Order.AllrushNewCustomerOrder"
            },};

        return orderStatuses;
    }
}

解决方法

我将根据您陈述的要求而不是您的示例代码来回答,因为两者似乎相互冲突。

将这样的需求转化为代码最重要的是确保代码清晰明确地实现了需求。

不要试图变得聪明,只是要显而易见。这意味着您更有可能是正确的,并且随着需求的变化,您肯定更易于维护。

对于你的情况...

// Large repair orders for new customers should be closed
if (order.IsLargeOrder && order.OrderType == OrderType.Repair && order.IsNewCustomer)
{
    return OrderStatus.Closed;
}

// Large rush hire orders should always be closed
if (order.IsLargeOrder && order.IsRushOrder && order.OrderType == OrderType.Hire)
{
    return OrderStatus.Closed;
}

// Large repair orders always require authorisation
if (order.IsLargeOrder && order.OrderType == OrderType.Repair)
{
    return OrderStatus.AuthorisationRequired;
}

// All rush orders for new customers always require authorisation
if (order.IsRushOrder && order.IsNewCustomer)
{
    return OrderStatus.AuthorisationRequired;
}

// All other orders should be confirmed
return OrderType.Confirmed;

了解代码与需求的匹配程度,以及在需求发生变化时找到要更改哪部分代码的难易程度?


我想说您的问题根本与开放/封闭原则无关。您的版本具有硬编码的条件列表,与包含一些 if 语句的方法相比,修改起来并不容易:这是执行相同操作的一种不太明显的方法。

如果您考虑一下 Martin 对开放/封闭原则的解释,其核心是确保如果您需要对单个需求进行修改,则将它们放在一个地方,并且您不需要追捕并修改大量令人沮丧的代码。

例如,您使用 OrderStatus 枚举本身可能违反开放/封闭原则,具体取决于它的使用方式。如果您有大量单独的代码,它们都可以:

switch (orderStatus)
{
    case OrderStatus.Closed:
        // Do stuff
        break;
    ...
}

...然后添加新的订单状态类型将意味着查找所有这些 switch 语句并添加新条件,这将违反开/关原则。

值得一提的是,您确实需要对开/关原则持保留态度。 Meyer 的版本绝对不适用于像 C# 这样的语言,其中添加字段/方法不是二进制破坏性更改。 (即使是这样,为了将 Meyer 的开放/封闭原则应用到您的代码中,您需要将 GetOrderStatusTypeByOrderInput 方法设为虚拟,如果之后需求发生变化,您将子类化 OrderStatusAnalyzer 并实现新的需求集在那里。)

多态解释也不适用于您的情况:这表示接口是不可变的(“关闭修改”),而应该使用抽象基类。这曾经适用于 C#,其中向接口添加方法是一种二进制破坏性更改(这就是为什么 SqlConnection 之类的东西是基类而不是接口的原因),但对于 DIM,这不再适用。

对于像 C# 这样的语言来说,剩下的是 Martin 解释的一个版本,它是为了确保如果您需要进行有针对性的更改,您不需要触及整个代码库中的数十个不同的代码位。您的需求实现代码本身根本不违反这一点,因为任何需求更改都可以通过修改单个方法进行。使用术语“开放”和“封闭”是一种延伸,我认为人们只是热衷于继续使用术语“SOLID”,即使“开放/封闭原则”的原始定义不再适用于新语言,所以他们不断制定新的规则,这些规则大致(但不是真的)符合相同的名称。

事实上,我什至会说你的带有 OrderStatusCollection 的版本实际上比我回答中的版本更违反开放/封闭原则!如果您对 OrderStatusCollection 进行任何更改(例如添加属性或更改属性类型),您还必须修改 OrderStatusAnalyzer 中那个可怕的 linq 查询:您已经从有一位代码要修改,有两位!这是 Martin 的开放/封闭原则告诉你要避免的事情。


说了这么多,最重要的是运用常识。 SOLID 是指导原则,而不是严格的法律。如果您试图虔诚地遵循它们,从而使您的代码变得更糟,那么您就做错了。

,

不完全清楚为什么应该强加“没有如果”规则。即使是像 SOLID 这样的一套原则也旨在帮助解决特定问题。如果您没有这些问题,则不需要 SOLID。如前所述,问题很简单,您不需要 SOLID。

不过,我猜这个问题可以替代一个更大的问题,并且您希望能够相互独立地更改规则,这让我想起了this article

根据实际限制,您可以通过多种方式解决问题。当我读到这个问题时,我很容易想到的是 Chain of Responsibility 模式。

你可以从定义一个接口开始:

public interface IPolicy
{
    OrderStatus DecideStatus(
        bool isRushOrder,OrderType orderType,bool isNewCustomer,bool isLargeOrder);
}

然后您实施一组具体的政策,例如:

public class LargeRepairOrderForNewCustomersPolicy : IPolicy
{
    public LargeRepairOrderForNewCustomersPolicy(IPolicy next)
    {
        Next = next;
    }

    public IPolicy Next { get; }

    public OrderStatus DecideStatus(
        bool isRushOrder,bool isLargeOrder)
    {
        if (isLargeOrder && orderType == OrderType.Repair && isNewCustomer)
            return OrderStatus.Closed;

        return Next.DecideStatus(isRushOrder,orderType,isNewCustomer,isLargeOrder);
    }
}

或:

public class LargeRushHireOrderPolicy : IPolicy
{
    public LargeRushHireOrderPolicy(IPolicy next)
    {
        Next = next;
    }

    public IPolicy Next { get; }

    public OrderStatus DecideStatus(
        bool isRushOrder,bool isLargeOrder)
    {
        if (isLargeOrder && isRushOrder && orderType == OrderType.Hire)
            return OrderStatus.Closed;

        return Next.DecideStatus(isRushOrder,isLargeOrder);
    }
}

要使用它们,请组成链:

private IPolicy CreatePolicy()
{
    return new LargeRepairOrderForNewCustomersPolicy(
        new LargeRushHireOrderPolicy(
            new LargeRepairOrderPolicy(
                new RushOrderForNewCustomersPolicy(
                    new DefaultPolicy()))));
}

对象现在可以使用了:

IPolicy policy = CreatePolicy();
var status = policy.DecideStatus(isRushOrder,isLargeOrder);

如果您需要更改策略,您可以编辑单个策略、添加新类或重新排序链。