代码之家  ›  专栏  ›  技术社区  ›  Isaiah Shiner

设计模式:基于输入参数选择任意功能,无需使用大型switch/if语句

  •  1
  • Isaiah Shiner  · 技术社区  · 7 年前

    编辑:我在这里谈了很多多态性,但这不是问题的焦点。我不需要使用多态性/继承。主要问题是将分类法与算法相匹配。如果某物是凳子,它需要一个算法,如果它是地毯,它需要另一个算法。接口、字典等都不能直接帮助我。我必须得到“Stool”,这意味着我需要getSizeForTool(),而不需要使用ifs。

    我正在制作一个系统,为将出现在网站上的产品设置值。我提出的方法似乎太复杂了,所以我正在寻找这种方法的最佳实践。它有设计模式吗?

    所有产品略有不同。例如,家具的尺寸通常为24英寸x 56英寸x 78英寸,但地毯通常更像8'6英寸x 10'6英寸。

    所以,我需要一个方法来解决每一个问题,对吗?但是,如果产品是牛皮,需要估计尺寸,因为它们各不相同,该怎么办?还是一盏枝形吊灯,只有长度才重要?

    所以我对每种产品都做了明确的分类。在这个例子中,我使用的是王国、家族和物种,所以只有3个类别。理想情况下,这将是一个数组。一种产品可能是家具->表->餐桌。

    所以我知道每个产品是什么,也知道每个产品使用什么方法。但是我如何从分类法转到正确的方法呢?我希望能够告诉特定的产品组做一些事情,或者可能是非常特定的产品组

    朴素的解决方案:嵌套的switch/if语句

    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;
    }
    

    这对我来说似乎注定要失败。此外,必须为每个需要根据其分类进行检查的值(如价格、属性、描述等)复制它,这将使其更加混乱。

    如果一个字典包含每个分类法作为键,而正确的Func作为值运行,那该怎么办?

    我没有这方面的代码片段,因为它确实比switch语句更糟糕。它最终变得不那么可读,因为在格式化过程开始时必须构建大量的字典,因此如果要添加新类型或修改现有类型,则必须修改除放置实现位置之外的其他类。还有很多代码重复,因为函数的类型必须是显式的,不能通用化。如果你能概括这一点而不使它变得非常混乱,我会感兴趣的。

    多态性就是答案吗?

    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;
        }
    }
    

    这看起来很完美。我可以根据抽象类或Any\u Any\u Any类中的分类法定义所有不应更改的内容,并根据需要配置尽可能多的功能。但我不认为这实际上解决了我最初的案例陈述问题!

    如果我想得到这样的分类组:

    TaxonomicGroup taxonomy = Functions.GetTaxonomicGroup(kingdom, family, species);
    

    我需要各种可能的分类组的列表或字典,并在其中搜索。该字典将非常庞大,每次添加新类时都需要对其进行维护。这还假设属性是静态的,这在我当前的实现中不起作用。

    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
    }
    

    总之,是否有一种将任意复杂类型连接到正确功能的设计模式?一个适配器,连接器,其他一些我可以抛出的OOP?

    4 回复  |  直到 7 年前
        1
  •  0
  •   Rufus L    7 年前

    这是我刚才谈到的OO方法,但根据您对我的评论,我不确定它是否满足您的需求。

    第一个a Size 类,该类表示一个大小可能具有的所有属性(显然,这只是一个示例):

    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; }
    }
    

    您可以创建一个转换类,该类对于转换可能很方便 200 inches 一个合适的字符串 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;
        }
    }
    

    然后是一个“Kingdom”类,它以默认方式显示大小:

    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}";
        }
    }
    

    两个“家族”类,一个继承了王国版本的 GetSize 另一个是用自己的实现来隐藏它:

    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
        }
    }
    

    然后是一个“物种”类 Family 实施:

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

    这是一个如何以不同方式显示其大小的示例(即使使用相同的 大小 对象)看起来像:

    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();
    }
    

    输出

    enter image description here

        2
  •  0
  •   Faruq    7 年前

    战略模式在这里很有用。为不同的计算方法创建策略,并根据给定的 王国 ,则, 家庭

    首先创建一个接口来定义 GetSize 方法

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

    然后确定您需要的不同计算方法,并为每种方法创建单独的类。给这些类一些自解释的名称。

    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();
        }
    }
    
    ...
    ...
    

    然后根据给定的王国/家族/物种值指定适当的类实例。你不必为王国/家族/物种的每一个组合创造条件。创建默认计算,并仅为需要特殊计算的对象创建条件。

    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);
        }
    }
    

    我看不到使用 if else 这是本案的一个问题。其他替代方案可能会降低代码的可读性或导致性能问题。

        3
  •  0
  •   P.Brian.Mackey    7 年前

    首先,我同意你的观点,你正在某个地方走到尽头。

    你说过

    所有产品略有不同。例如,的大小 家具通常为

    你举了很多例子 不同的算法 它们都做同样的事情,那就是计算大小。解决这类问题所需的只是使用 Size 方法或者可能是一种装饰图案,具体取决于您的实际需要。解决这种问题不需要分类学或层次关系。所以我觉得剩下的问题是事情偏离了轨道。

    OO并不能解决所有问题。建议阅读: https://www.sicpers.info/2018/03/why-inheritance-never-made-any-sense/

        4
  •  0
  •   InBetween    7 年前

    总之,是否有一种将任意复杂类型连接到正确功能的设计模式?一个适配器,连接器,其他一些我可以抛出的OOP?

    是的,但它需要将类型映射到功能,这似乎是您希望通过一些神奇的模式来避免的。没有,如果您要将某些特定功能添加到具体类型,那么您必须在某个地方说什么功能与什么类型匹配。。。你打算如何避免这种情况?

    您完全放弃的另一个选项:

    我不需要使用多态性/继承

    就是让每个对象都知道如何做自己的事情。如果这不是一个选项,那么您必须使用某种映射基础架构。