I'm in the process of re-writing a method I've come across in our code base. It's pretty lengthy and a lot of it seemed redundant. It's a Discount
object class member. It operates on the discounts DiscountRules
property.
DiscountRule
can have behave as follows:
- Can apply to all brands and a single category
- Can apply to all categories and a single brand
- Can apply to a single category and a single brand
In addition to these few simple rules, there's also "include/exclude" functionality.
Example rules:
- All brands (include) in Televisions (exclude)
- Pioneer (include) in All categories (exclude)
- Pioneer (exclude) in All categories (include)
- Pioneer (include) in Televisions (include)
etc...
This would make the discount valid for any brands not in the 'Televisions' category (1st example). This is figured out by looking at the Brand + Category ID that a product in the basket falls under, if they match, it checks that the rule has both BrandInclude
and CategoryInclude
(include/exclude flags) set for the rule (either one of these being false would invalidate the whole).
Here is the original method:
public bool IsDiscountValidForProduct(int brandID, int categoryID)
{
foreach (DiscountRule rule in this.DiscountRules)
{
// Rule is all brands, no need to check brand id
if (rule.AllBrands && rule.CategoryId == categoryID)
{
// Check is passed, now we just need to check the inclusion/exclusion rules
if (rule.IncludesCategoriesAndBrands)
{
return true;
}
else if (!rule.IncludesCategoriesAndBrands)
{
return false;
}
else
{
// It's a mix of exclude + include
if (rule.BrandInclude && !rule.CategoryInclude)
{
// no need for brand lookup, it's all brands, just make sure categoryID from rule not = categoryID param
return rule.CategoryId != categoryID;
}
else if (rule.CategoryInclude && !rule.BrandInclude)
{
// Since this is all brands, and brands are excluded, no need to check category, return false
return false;
}
}
}
// Rule is all categories, no need to check category id
else if (rule.AllCategories && rule.BrandId == brandID)
{
if (rule.IncludesCategoriesAndBrands)
{
return true;
}
else if (!rule.IncludesCategoriesAndBrands)
{
return false;
}
else
{
// It's a mix of exclude + include - includes brand but not category
if (rule.BrandInclude && !rule.CategoryInclude)
{
// Rule is category exclude, since we're checking under all categories, no need for lookup, return false
return false;
}
else if (rule.CategoryInclude && !rule.BrandInclude)
{
// rule is all categories, no need to check category id, just look at the brand
return rule.BrandId != brandID;
}
}
}
else if (rule.BrandId == brandID && rule.CategoryId == categoryID)
{
if (rule.IncludesCategoriesAndBrands)
{
return true;
}
// If either category or brand exclude or both, rule is invalid
else if (!rule.IncludesCategoriesAndBrands || (!rule.CategoryInclude || !rule.BrandInclude))
{
return false;
}
}
}
return true;
}
Here is the refactored version:
public bool IsDiscountValidForProduct(int brandID, int categoryID)
{
foreach (DiscountRule rule in this.DiscountRules)
{
if (rule.AllBrands && rule.CategoryId == categoryID
|| rule.AllCategories && rule.BrandId == brandID
|| rule.BrandId == brandID && rule.CategoryId == categoryID)
{
return rule.IncludesCategoriesAndBrands;
}
}
return true;
}
How can I further improve this (short of adding comments)? What would you do differently and why?
IncludesCategoriesAndBrands
mean, and why is that being returned? Why istrue
returned if no rules match? \$\endgroup\$IncludesCategoriesAndBrands
is a property ofDiscountRule
. It's an&&
ofCategoryInclude
andBrandInclude
, the include/exclude flags. If a rule matches a product, it can only be valid if bothCategoryInclude
andBrandInclude
. You raise an excellent point about returning true when no rules match, looks like a bug to me. I suspect the logic should be "if no rules, return true". Good spot. It also highlights another issue in not checking rules if a match occurs on a single ID, e.g. if only the brand matches, it should check whether this is include/exclude or not. \$\endgroup\$DiscountRule
'sCategoryId
, but not itsBrandId
, but theDiscountRule
hasCategoryInclude
true andBrandId
false, shouldn't that give the result true rather than false? Also why would no rules return true? A rule seems like something inclusionary, rather than exclusionary. As in, adding a new rule could only add (brandId
,categoryId
) pairs which would be discounted (or leave them the same), never remove any \$\endgroup\$DiscountRule
is not required on a discount, if it has no restrictions it should just be true by default. \$\endgroup\$