Edit: I talked a lot about polymorphism here, but that is not the focus of the problem. I do NOT need to use polymorphism/inheritance. The main problem is matching the taxonomy to the algorithm. If something is a stool, it needs one algorithm, and if its a rug, it needs another algorithm. Interfaces, dictionaries, etc. don't directly help me. I have to get that "Stool" means I need GetSizeForSool() without using ifs.
I'm making a system to set values for products that will appear on a website. The methods I have come up with seem way too complicated, so I'm looking for what the best practices for something like this are. Is there a design pattern for it?
All products are slightly different. For example, the size of furniture is usually given as 24" x 56" x 78", but a rug is usually more like 8'6" x 10'6".
So, I need a method for each one, right? But what if the product is a Cow Hide, that needs an estimated size, since they vary? Or a chandelier where only the length matters?
So I made a clear taxonomy for each product. For this example, I'm using Kingdom, Family, and Species, so only 3 categories. Ideally this would be an array. One product might be Furniture -> Table -> Dining Table.
So I know what every product is, and know what method to use for each one. But how do I go from the taxonomy to the correct method? I want to be able to tell certain groups of products to do something, or possibly very specific groups of products
Naive solution: nested switch/if statements
public string GetSize(int width, int length, int height, string productKingdom, string productFamily, string productSpecies)
{
switch(productKingdom)
{
case "Furniture":
switch (productFamily)
{
case "Table":
return GetSize_Furniture_Table_Any(width, length, height);
default:
return null;
}
case "Rug":
return GetSize_Rug_Any_Any(width, length, height);
default:
return null;
}
}
public static string GetSize_Furniture_Table_Any(int width, int length, int height)
{
return width + " x " + length + " x " + height;
}
This seems doomed to fail to me. Plus, it must be replicated for every value (such as price, attributes, description, etc) that requires checking based on its taxonomy, which will make it even messier.
What about a dictionary that contains each taxonomy as the key, and the correct Func to run as the value?
I don't have a code snippet for this, because it really was worse than the switch statement. It ended up being less readable because the enormous dictionaries had to be constructed when the format process began, and so if you wanted to add a new type or modify an existing one, you had to modify other classes besides where you put the implementation. There was also a lot of code duplication because the types of the functions had to be explicit and could not be generalized. If you can generalize this without making it extremely confusing, I would be interested.
Surely polymorphism is the answer?
public abstract class TaxonomicGroup
{
abstract public string Kingdom { get; }
abstract public string Family { get; }
abstract public string Species { get; }
public abstract string GetSize(int width, int length, int height);
}
public class Furniture_Any_Any : TaxonomicGroup
{
public override string Kingdom { get; }
public override string Family { get; }
public override string Species { get; }
public Furniture_Any_Any()
{
Kingdom = "Furniture";
Family = null;
Species = null;
}
public override string GetSize(int width, int length, int height)
{
return width + " x " + length + " x " + height;
}
}
public class Decor_Rug_Any : TaxonomicGroup
{
public override string Kingdom { get; }
public override string Family { get; }
public override string Species { get; }
public Decor_Rug_Any()
{
Kingdom = "Decor";
Family = "Rug";
Species = null;
}
public override string GetSize(int width, int length, int height)
{
string widthFootInch = GetFootInchFromInch(width);
string lengthFootInch = GetFootInchFromInch(length);
return widthFootInch + " x " + lengthFootInch;
}
}
This seems perfect. I can define all the things that should not change depending on the taxonomy in the abstract class, or maybe the Any_Any_Any class, and configure as much functionality as I want. But I don't think this actually solves my original problem of case statements!
If I want to get the TaxonomicGroup like this:
TaxonomicGroup taxonomy = Functions.GetTaxonomicGroup(kingdom, family, species);
I need some kind of list or dictionary of every possible TaxonomicGroup, and search through them. That dictionary will be enormous, and need to be maintained every time you add a new class. This also assumes that the properties are static, which doesn't work with my current implementation.
public TaxonomicGroup GetTaxonomicGroup(string kingdom, string family, string species)
{
Type[] allProductTypes = { typeof(Any_Any_Any), typeof(Rug_Rug_Any) };
//Some kind of search Function?
foreach(TaxonomicGroup tax in allProductTypes)
if(tax.Kingdom.Equals(kingdom)
//More evaluation logic
}
In summary, is there a design pattern for connecting arbitrarily complex types to correct functionality? An adapter, connector, some other additional OOP that I can throw at this?