2

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?

Isaiah Shiner
  • 443
  • 6
  • 18
  • I believe you are looking for this? https://stackoverflow.com/questions/11107536/convert-string-to-type-in-c-sharp – Camilo Terevinto Mar 28 '18 at 20:48
  • Why won't you just use inheritance? You can subclass TaxonomicGroup with Furniture, and further subclass Furniture with Table. Each can override the getSize to the best of his ability, and also have "getIdentifier" that can be stored in a hashmap of some factory to parse the user input. – alex440 Mar 28 '18 at 20:49
  • What is Kingdom, Family, and Species? You have them specified as strings. Do they actually have a real relationship? If so, then that should be enforced in code. It seems like they have an "is-a" relationship, which would imply inheritance (i.e. a `Stool` is-a `Chair` is-a `Furniture`). This sounds like the inheritance you would use. – Rufus L Mar 28 '18 at 20:54
  • You might consider creating a class called `Size` that has all the properties that a size might have (length, width, height, depth, volume, etc.) Size could have a property called `Units` as well, which would help indicate how it should be displayed. Then each Kindgom/Family/Species would have a `Size` property, and you would just set the values that you care about. The `Furniture` class would have a default way to display a size for all types furniture, but sub classes could override it if they needed to. – Rufus L Mar 28 '18 at 20:59
  • @CamiloTerevinto That was one of the first things I considered, but I thought it would be too fragile. I will return to it if these other suggestions do not pan out. – Isaiah Shiner Mar 28 '18 at 21:11
  • @RufusL I should have been more clear that the OO is not really the focus, its just a possibility. Your OO implementation definitely works better than mine, but it really doesn't solve the matching taxonomy to algorithm issue. – Isaiah Shiner Mar 28 '18 at 21:31
  • It seems like the forest is getting lost for the trees... A taxonomic structure seems quite brittle to me. So, what are the similarities between each item, and what's the end result you want? It sounds like you could achieve what you want by focusing on a generic "unit" metric, and each item will have some quantity of that unit. A simple int and a double, and a string for UI-display to say what the unit is. Maybe the unit is area, length, bd-ft, volume, count, weight, longest-dimension, shelf-area, etc. For a rug, it'll have an area (qty x unit) (a sortable double), and a description(LxW). – Kevin Fichter Mar 28 '18 at 22:58

4 Answers4

0

Strategy pattern can be useful here. Create strategies for different ways for calculations and plug them in during run-time based on the given Kingdom, Family and Species.

First create an interface to define the GetSize method.

public interface ISizeCalculator
{
    string GetSize(int width, int length, int height);
}

Then identify what are the different ways of calculation you need and create separate class for each of them. Give the classes some self-explanatory names.

public class ASimpleCalculation : ISizeCalculator
{
    public string GetSize(int width, int length, int height)
    {
        return string.Format("{0} x {1} x {2}", width, length, height);
    }
}

public class AnotherCalculation : ISizeCalculator
{
    public string GetSize(int width, int length, int height)
    {
        return (width * length * height).ToString();
    }
}

...
...

Then assign the appropriate class instance based on the given Kingdom/Family/Species value. You don't have to create condition for every combination of Kingdom/Family/Species. Create a default calculation and create condition only for those which need special calculation.

public class TestClass
{
    public void Test(string kingdom, string family, string species)
    {
        ISizeCalculator calculator = null;
        if (kingdom == "A")
        {
            calculator = new ASimpleCalculation();
        }
        else if (kingdom == "B" && family == "123")
        {
            calculator = new AnotherCalculation();
        }
        else if (new string[] { "X", "Y", "Z" }.Contains(species))
        {
            calculator = new ASimpleCalculation();
        }
        // add more criteria as per your requirements
        else
        {
            calculator = new DefaultCalculation();
        }
        string result = calculator.GetSize(1, 2, 3);
    }
}

I don't see using if else as a problem for this case. The other alternatives can reduce the code readability or can cause performance issue.

Faruq
  • 1,361
  • 1
  • 11
  • 23
  • How does this solve the multi-level switch with an if? The first part of the answer is right, the second is not – Camilo Terevinto Mar 28 '18 at 21:12
  • @CamiloTerevinto With this approach, multi-level switch case is not required. I don't believe the OP will have separate calculation logic each combination of kingdom/family/species. So, he can assign the most common logic as the default one. Then based on the criteria he can add conditions for the special cases. For instance, if all the families and species of *KingdomA* has the same logic, there will be only one condition for that and without any multi-level nested switch/if-else. – Faruq Mar 28 '18 at 21:23
  • In any case, if you believe my answer will solve *half* of the problem, there is no reason to down vote it. – Faruq Mar 28 '18 at 21:24
  • Yes, this is exactly what I was trying to avoid. That method will balloon to be extremely complicated. Remember that there are an arbitrarily large number of ways to calculate the size (or anything else) of a product, even down to the individual exact product, not just the product family. – Isaiah Shiner Mar 28 '18 at 21:26
0

First of all, I agree with you in that you are going off the deep end somewhere.

You said

All products are slightly different. For example, the size of furniture is usually given as

You gave many examples of different algorithms that all do the same thing which is calculate size. All you need to solve this kind of a problem is various implementations of an interface with a Size method. Or possibly a Decorator Pattern depending on your actual needs. There's no need for taxonomy or hierarchical relationships to solve this kind of a problem. So I feel the remainder of the question is where things go off track.

OO doesn't solve everything. Recommended reading: https://www.sicpers.info/2018/03/why-inheritance-never-made-any-sense/

P.Brian.Mackey
  • 43,228
  • 68
  • 238
  • 348
  • That does not solve my problem, because i still need a way to choose the correct interface to use. There could be 50 different implementations of the size interface. How do I choose the right one? – Isaiah Shiner Mar 28 '18 at 21:23
  • 50 isn't that much. What about a simple Factory pattern? – P.Brian.Mackey Mar 28 '18 at 21:26
0

Here's the OO approach I was talking about, though I'm not sure it meets your needs based on your comment to me.

First a Size class that represents all the properties a size could have (obviously this is just an example, though):

enum UnitOfMeasure
{
    Inches, Feet, Yards, Centimeters, Meters
}

class Size
{
    public int Length { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
    public int Depth { get; set; }
    public UnitOfMeasure Units { get; set; }
}

And you could create a conversion class that might come in handy for converting 200 inches to a proper string that looks like 16'8":

static class Convert
{
    public static string ToFeetAndInches(int inches)
    {
        var feet = inches / 12;
        var remainingInches = inches % 12;

        var footStr = feet > 0 ? $"{feet}'" : "";
        var inchStr = remainingInches > 0 ? $"{remainingInches}\"" : "";

        return footStr + inchStr;
    }

    public static string ToMetersAndCentimeters(int centimeters)
    {
        var meters = centimeters / 10;
        var remainingCm = centimeters % 10;

        var meterStr = meters > 0 ? $"{meters}m " : "";
        var centimeterStr = remainingCm > 0 ? $"{remainingCm}cm" : "";

        return meterStr + centimeterStr;
    }
}

Then a "Kingdom" class that has a default way to present the size:

class Decor
{
    public Size Size { get; set; }

    public string GetSize()
    {
        switch (Size.Units)
        {
            case UnitOfMeasure.Inches:
                return string.Format("{0} x {1} x {2}",
                    Convert.ToFeetAndInches(Size.Width),
                    Convert.ToFeetAndInches(Size.Length),
                    Convert.ToFeetAndInches(Size.Height));
            case UnitOfMeasure.Feet:
                return $"{Size.Width}' x {Size.Length}' x {Size.Height}'";
            case UnitOfMeasure.Centimeters:
                return string.Format("{0} x {1} x {2}",
                    Convert.ToMetersAndCentimeters(Size.Width),
                    Convert.ToMetersAndCentimeters(Size.Length),
                    Convert.ToMetersAndCentimeters(Size.Height));
            case UnitOfMeasure.Meters:
                return $"{Size.Width}m x {Size.Length}m x {Size.Height}m";
        }

        return $"{Size.Width} x {Size.Length} x {Size.Height}";
    }
}

A couple of "Family" classes, one that just inherits the Kingdom version of GetSize and another that hides it with it's own implementation:

class Lamp : Decor
{
}

class Rug : Decor
{
    public new string GetSize()
    {
        var baseSize = base.GetSize();
        var lastX = baseSize.LastIndexOf("x");
        return baseSize.Substring(0, lastX - 1).Trim();  // Only show Width and Length
    }
}

And then a "Species" class that hides it's Family implementation:

class Chandelier : Lamp
{
    public new string GetSize()
    {
        var baseSize = base.GetSize();
        return baseSize.Split('x')[1].Trim();  // Only show the length
    }
}

An example of how these all display their sizes differently (even when using the same Size object) would look like:

static void Main()
{
    var genericSize = new Size
    {
        Width = 20,
        Height = 30,
        Length = 40,
        Units = UnitOfMeasure.Inches
    };

    var brassLamp = new Lamp { Size = genericSize };
    var glassChandelier = new Chandelier { Size = genericSize };
    var plushRug = new Rug { Size = genericSize };

    Console.WriteLine($"Brass lamp: {brassLamp.GetSize()}");
    Console.WriteLine($"Chandelier: {glassChandelier.GetSize()}");
    Console.WriteLine($"Plush Rug: {plushRug.GetSize()}");

    Console.WriteLine("\nDone!\nPress any key to exit...");
    Console.ReadKey();
}

Output

enter image description here

Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • This is the most helpful answer here. I think I will structure my OO like this, but the truth is that it still doesn't solve my original problem of going from string kingdom, string family, and string species to the correct class. I think the best bet is the very first answer(comment) on here, which was to get the class directly, using Type.Get("Plush Rug"); Thank you for the help though! – Isaiah Shiner Mar 29 '18 at 14:32
0

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?

Yes, but it entails mapping types to functionality, something you seem to want to avoid through some magical pattern. There isn't, if you are slapping some specific functionality to concrete types, you'll have to somewhere say what functionality goes with what type... how are you planning to avoid this?

Another option you've outright discarded:

I do NOT need to use polymorphism/inheritance

Is simply having each object know how to do its thing. If this is not an option then your stuck with some kind of mapping infrastructure.

InBetween
  • 32,319
  • 3
  • 50
  • 90