I want to avoid the use of "if" and "switch" statements which deal with specific cases and instead taking a more generic approach.
Requirement: Based on a number of requirements which are detailed below, the status of an Order may be determined to be "Confirmed", "Closed" or "AuthorisationRequired".
Inputs:
IsRushOrder: bool
OrderType: Enum (Repair, Hire)
IsNewCustomer: bool
IsLargeOrder: bool
Outputs:
OrderStatus: Enum (Confirmed, Closed, AuthorisationRequired)
Requirements (Applied in priority order from top to bottom):
-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
==========================================================================
Avoiding Obvious answer with IF statement
This violates O of SOLID principle. I am sure there is better way to do this than adding lots of IF statements
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();
}
One way i could think of implementing without using IF is by populating OrderStatus in collection and querying using LINQ This implementaion will get complicated quickly as we add more statuses.
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"
},
new OrderStatus
{
IsLargeOrder = true, IsNewCustomer = null, OrderType = OrderTypeEnum.Repair,
IsRushOrder = null,
Priority = 3, OrderStatusType = "VehicleRepairOrder.Order.LargeRepairOrder"
},
new OrderStatus
{
IsLargeOrder = null, IsNewCustomer = true, OrderType = OrderTypeEnum.Any, IsRushOrder = true,
Priority = 4, OrderStatusType = "VehicleRepairOrder.Order.AllRushNewCustomerOrder"
},
};
return orderStatuses;
}
}
It's not entirely clear why the 'no ifs' rule should be imposed. Even a set of principles like SOLID are meant to help solving particular problems. If you don't have those problems, you don't need SOLID. As stated, the problem is so simple that you don't need SOLID.
I'm guessing, however, that this question is a substitute for a larger problem, and that you'd like to be able to change rules independently of each other, which reminds me a bit of this article.
Depending on the actual constraints, you can address the concerns in several ways. One that came easily to my mind when I read the question is the Chain of Responsibility pattern.
You could start by defining an interface:
public interface IPolicy
{
OrderStatus DecideStatus(
bool isRushOrder,
OrderType orderType,
bool isNewCustomer,
bool isLargeOrder);
}
Then you implement a set of concrete policies, e.g.:
public class LargeRepairOrderForNewCustomersPolicy : IPolicy
{
public LargeRepairOrderForNewCustomersPolicy(IPolicy next)
{
Next = next;
}
public IPolicy Next { get; }
public OrderStatus DecideStatus(
bool isRushOrder,
OrderType orderType,
bool isNewCustomer,
bool isLargeOrder)
{
if (isLargeOrder && orderType == OrderType.Repair && isNewCustomer)
return OrderStatus.Closed;
return Next.DecideStatus(isRushOrder, orderType, isNewCustomer, isLargeOrder);
}
}
or:
public class LargeRushHireOrderPolicy : IPolicy
{
public LargeRushHireOrderPolicy(IPolicy next)
{
Next = next;
}
public IPolicy Next { get; }
public OrderStatus DecideStatus(
bool isRushOrder,
OrderType orderType,
bool isNewCustomer,
bool isLargeOrder)
{
if (isLargeOrder && isRushOrder && orderType == OrderType.Hire)
return OrderStatus.Closed;
return Next.DecideStatus(isRushOrder, orderType, isNewCustomer, isLargeOrder);
}
}
To use them, compose the chain:
private IPolicy CreatePolicy()
{
return new LargeRepairOrderForNewCustomersPolicy(
new LargeRushHireOrderPolicy(
new LargeRepairOrderPolicy(
new RushOrderForNewCustomersPolicy(
new DefaultPolicy()))));
}
The object is now ready for use:
IPolicy policy = CreatePolicy();
var status = policy.DecideStatus(isRushOrder, orderType, isNewCustomer, isLargeOrder);
If you need to change the policies, you can edit individual policies, add new classes, or reorder the chain.