none
不用switch-case或者if-else,如何实现类似分支的效果 RRS feed

  • 问题

  • 需求:取得输入数据后把数据发送到某个地址

    问题:发送的时候需要根据输入数据中的某个值可以确定发送的细节(如:GET或POST、编码格式)。以及收到请求回复后需要将回复中的信息展示出来,回复中的数据内容格式也是和参数相关的。

    我想到最初级的方法就是swicth-case,但这种方法肯定有很多不好,虽然我也说不出来。我觉得我其实是想实现一个多态的效果,可是多态也需要new出子类对象才能赋给父类对象,我怎么才能根据一个值初始化出对象呢,我不想用反射,因为担心速度,这个请求量是蛮大的。

    有没有更面向对象的模式可以解决这个问题呢?

    2017年7月18日 13:36

全部回复

  • Dictionary<TKey, Func<..., TResult>>
    2017年7月18日 13:56
  • 设计模式中提到,用swith-case的地方就可以用抽象和继承解决,参考对象工厂或工厂方法。

    反射,因为担心速度,考虑用MS Unity,Spring等容器技术,管理对象的创建。


    专注于.NET ERP/CRM开发框架,C/S架构,SQL Server + ORM(LLBL Gen Pro) + Infragistics WinForms

    2017年7月19日 0:16
  • @dnzhi

    如你所言,反射的效果多数情况下不如直接用switch……case……或者if……else……。那要看你从何角度看问题:

    1)如果你想使用设计模式为主,或许可以考虑使用Shuanghua Li的方式——用反射工厂(或者IOC等方式),把不同请求封装成不同的实现类,注册成为同一个接口,然后使用IOC方式做反射工厂。

    针对你的例子而言,假设你请求地址的格式固定的(譬如:http://www.XXXX.com),你完全可以使用“XXX”不同且唯一的名字为你实现不同逻辑的DLL命名,然后反射的时候直接使用传入的地址,截取“XXX”的部分反射或者用AutoFac等IOC方式依赖注入即可。

    使用反射或者IOC的时候建议一次性全部加载DLL包(静态公共变量,在Main入口函数中,这样仅启动的时候慢一点),这样以后只要使用这个变量+传入的地址的截取部分就可以实现反射完成任务。

    但是要注意:如果使用这样的设计模式,你的接口必须是稳定的,而且输入输出是确定的,只是过程内部处理机制不同。否则无法抽象成接口,谈不上反射工厂或者IOC了。

    @FAE9D91C-3F07-4090-91EB-E3F70E4D0617

    鄙人以为如果每一次请求都反射当然效率是不高,但是如果Main中预先把实现接口的DLL加载到一个公共的变量(比如IOC中当做一个大的缓存用呢)?以后每次请求,如我上面举例所言,截取部分字符串作为别名传入AutoFac等IOC工具,都根据别名获取不同实现的实现类完成任务即可,以空间换取时间。当然,IOC这样每次或许还要反射一次,所以可以设置IOC的LifeTime为Single(单例模式)。或者干脆直接使用你的那个Dictionary<Key,接口>人造简单注册方式注册即可。这样完全避免反射(最多只是在Main的时候一起反射加载一次),完全可以避免资源消耗。

    int SwitchCase(int arg)
    {
        switch (arg)
        {
            case 1:
                return 10;
            case 2:
                return 20;
            default:
                return 100;
        }
    }

    最后,至于你上面提问把上面的代码转换为反射。

    首先当然是可以的。

    1)定义一个接口IReturnValue,里边一个方法(int ReturnValue();)

    2)定义三个不同的类,名字就是1,2,Others,分别实现ReturnValue返回10,20和100……

    public int SwitchCase(int arg)
    {
         string[] foundFiles = Directory.GetFiles("程序DLL的目录",arg+".dll",SearchOption.TopDirectoryOnly);
        
        if(foundFiles==null)
        {
             foundFiles = Directory.GetFiles("程序DLL的目录","Others.dll",SearchOption.TopDirectoryOnly);
        }
    
       Assembly asm = Assembly.LoadFrom(foundFiles[0]);
       //做的反射的事情(此处省略……)
       return ((IReturnResult)asm.CreateConstructor(Type.Empty).Invoke(null)).ReturnValue();
    }

    3)其次,正如我上面所说,这样代码可以简化,而且容易扩展,但是性能不好(每次都反射)。所以还不如用IOC或者您的Dictionary一次性地在Main中反射全部的DLL,然后判断去做:

    拿Dictionary做例子:

    int args(string arg)
    {
       IReturnResult irr = null;
    
        if(!_dictionary.ContainsKey(arg))
       {
          irr = _dictionary["Others"];
       }
       else{
         irr = _dictionary[arg];
       }
      return irr;
    }

    假设_dictionary已经被反射了获取实现类了,且类名与输入的arg有必然转换关系。


    ASP.NET Forum
    StackOverFlow
    FreeRice Donate
    Issues to report



    2017年7月19日 3:53
    版主
  • FAE9D91C-3F07-4090-91EB-E3F70E4D0617

    如果你做到传入参数和反射的文件名字有某种逻辑关系(比如名字和文件名一一对应),那么实际上就是if(满足条件1){……}的逻辑。这是其一。

    其二,我给出简单的代码,如果你使用反射抽象工厂,必然会用到抽象和继承(如果我的SwitchCase是一个抽象类,其中有一个抽象方法ReturnValue呢?继承这个类实现不同方法,在抽象工厂照样可以类似做到与我接口的方法)。这个留给你自己去想。

    此外,接口当然可以用抽象类代替,这里只是我用了接口而已。而且你难道不认为接口本身就是对变化逻辑的方法的一种广义上的抽象?难道你只认为“抽象类”带有“抽象”字眼的才算抽象?如果这样,我觉得你理解OOP或许死板了一些。

    其三,最后我还说明:性能肯定没有if……switch case好,但是问题在于:楼主强调的是“有没有更面向对象的模式可以解决这个问题呢?”,在某些情况下,正如我上面的例子,当然是有的

    如果你纠结着性能问题,那么我现在问你一个问题,请正面回答:机器语言的效率性能是最快的,你为何不用机器语言编程?你为什么要来学习.NET,学习Java,学习其它高级编程语言呢?编程语言效率无论如何没有机器语言好呀!你还不如直接用0101去做打孔机写程序,或者你足够牛逼的话,干脆用0101写代码算了。

    广而言之,硬件执行的效率(如均衡负载分SLB和HLB两种,HLB显然明显高于SLB,按照你的说法Nginx,微软的Windows server之类软件实现均衡负载的全部可以淘汰了,因为F5什么都可以取代了)。为什么实际上不是这样呢?

    或许你可以说“机器语言可读性不佳”或者“不是所有人都买得起F5”等理由,我想说的是:用设计模式也是一个道理——你不用机器语言,必然牺牲性能作为代价;不买F5的好处是Nginx不需要任何Cost,但是也损失性能。有得必有失。看你究竟看中哪块而已,我觉得你有些钻牛角尖,咬文嚼字了。


    ASP.NET Forum
    StackOverFlow
    FreeRice Donate
    Issues to report




    2017年7月19日 4:53
    版主
  • 设计模式中提到,用swith-case的地方就可以用抽象和继承解决,参考对象工厂或工厂方法。

    我所有的疑问都在这句话上,Shuanghua Li提出的那个观点我无法理解,为什么说所有的switch-case都可以用抽象和继承解决。在我看来抽象和继承完全解决不了任何分支问题,这两者之间根本没有任何关系吗。而且不用分支语句能实现工厂方法吗?

    嗯,估计是Shuanghua Li是根据某本书的抽象模式来说明的,我只能说这种说法不严谨。严格而言,只有条件语句表达式结果与反射的文件名构成一定的关系的时候才可以用反射抽象工厂模式处理,所有的条件语句都用抽象工厂反射模式没有必要。之所以说用抽象反射工厂,是结合实际项目需要,当项目的实际需求预估发生变更,而且变更的输入输出有一定规律可以被抽象成接口的时候可以使用。而且一般也绝对不会每次请求调用方法都是反射,这样性能很大损失。

    就我工作经验而言,一般的话,反射工厂(比如数据库迁移不同的类型),反射工厂我一般是单例模式的反射(最多反射一次,然后静态变量保留着一直使用)。

    我刚才网上找过设计模式,大概是类似这个样子的(具体可以让Shuanghua Li来给出他看的《设计模式》):http://www.lai18.com/content/410141.html(我的来源:《大鸟小菜话设计模式》,这一篇是解决打折的条件的,因为有一大堆的打折条件,请耐心细看)。

    但是完全按照你的说的“一点关系都没有”或许也不严谨。——又或许我们观点不同,你是站在“作用面”看问题的吧?你或许认为说:反射的作用根本不是用来取代条件语句,或者说反射的初衷不是用来和if或者switch对等的……但是,实际上呢?如果反射类的类名经过某种运算恰好与输入参数形成唯一的判定条件,那么直接用输入函数反射相当于if(条件){……}的功能,应该也可以算是反射的一种用法。应该来说,这是我们接触反射最常见的手段之一。

    总而言之:“条件语句”在某种条件下的确可以用反射取代,但是话不能反过来说:反射可以取代条件语句(局部充分非必要关系)。哈哈!

    你看呢?


    ASP.NET Forum
    StackOverFlow
    FreeRice Donate
    Issues to report


    2017年7月19日 6:09
    版主
  • 和FAE9D91C-3F07-4090-91EB-E3F70E4D0617的讨论是很有意义的,请不要删除这些讨论贴(纯技术讨论,谢谢)。

    正是因为有争议而不是胡搅蛮缠,才有论坛的真正意义,一言堂是毫无意义的!

    不过,我也想到一点:我们看问题都只取对自己有利的方面阐述,这是人类的通病——哲学以前批评过人类“非此即彼”的二元论文化把一切事物看得实在过于简单,真的如此吗?

    在这个问题上,我觉得严格而言,就看你站在那个立场上看问题了——所以不存在对和错的问题。也有可能我们都太绝对化了。收集来自各方面的意见讯息,加以综合分析利弊,我觉得看楼主需要什么了。毕竟我们只能尽量做到“鱼和熊掌都兼得”而不保证说一定可以做到。


    ASP.NET Forum
    StackOverFlow
    FreeRice Donate
    Issues to report

    2017年7月19日 6:20
    版主
  • 泛型接口:

    T Get<T>(......)

    用一个Context类,包括参数的类型和内容,结合Factory工厂模式,构造不同的实现。


    ericzhou

    2017年7月19日 7:03
  • ericzhou:得空可以具体说说,结合伪代码吗?

    比如说Context是具体指代什么?


    ASP.NET Forum
    StackOverFlow
    FreeRice Donate
    Issues to report



    2017年7月19日 7:07
    版主
  • 只有类型名称可以和用户输入的内容一一对应时才能取代分支,对于多对一的分支就完全行不通了(也就是说一个类型没办法有多个名称)

    对。这是说反射不能完全取代条件判断的原因。

    你原先做C++开发,照理对底层应该很了解的,你谦虚了哦。


    ASP.NET Forum
    StackOverFlow
    FreeRice Donate
    Issues to report



    2017年7月19日 9:07
    版主
  • 那你看来是在质疑C#或者说带有反射性能语言为什么要有“反射”这个功能了。

    解释一下哈:

    假设我网站登录页面需要有发短信功能,上海目前有电信、移动和联通三个运营商,他们发短信的接口都不一致(不同厂商),但是老板也不确定到底那个运营商效果更好一点,所以让我们用一阵再换一个试试看。

    通常的做法是:因为接口不一致,方法不一致,或许你不得不大改代码。

    但是如果我们仔细想,把不同运营商的公共方法抽取出来成接口(无论什么运营商,都需要电话号码),抽象成一个方法的抽象类或者接口,然后分别实现3个不同运营商的方法,最后,把这些文件分别放到一个文件夹中,配置文件读取指定的文件名的DLL,进行反射,这样的话以后根本无需修改代码,多了运营商要测试只要实现接口实现方法,编译一个DLL扔到指定文件夹,配置文件一改就可以上线啦!


    ASP.NET Forum
    StackOverFlow
    FreeRice Donate
    Issues to report

    2017年7月19日 10:29
    版主
  • 不,不是质疑反射这一技术,而是质疑反射的滥用。不仅是我,您可以看到所有对反射的质疑都是对使用者的质疑,而不是对技术本身的质疑。反射有它的用途,但问题在于很多人把这个当万能钥匙,即使根本用不到反射的地方也非得要用,似乎不用就是技术落后,不用就算不上OO。结果就是糟糕的性能和难以预料的Bug。技术的应用应该恰当合理,而不是为了“cool”,我想这也就是为什么微软自己都开始审慎使用反射的原因。另外你举得那个例子应该属于dll动态加载,与反射无关,好像所有语言都能动态加载dll。
    我想和你交个朋友,我C++知识缺乏。所以你可以加我吗?你可以通过电邮联系哦,谢谢你。

    ASP.NET Forum
    StackOverFlow
    FreeRice Donate
    Issues to report



    2017年7月19日 11:18
    版主
  • Keywords.xml

    <?xml version="1.0" encoding="utf-8" ?>
    <Items>
        <Item>
            <ID>{829CD870-39E0-451E-A423-8527353B9731}</ID>
            <Keywords>ms,microsoft</Keywords>
        </Item>
        <Item>
            <ID>{882581E7-44BF-4FA8-AD08-043E425D12B2}</ID>
            <Keywords>vs,visualstudio</Keywords>
        </Item>
    </Items>

    Texts.xml

    <?xml version="1.0" encoding="utf-8" ?>
    <Items>
        <Item>
            <ID>Prefixes</ID>
            <Text>://www.</Text>
        </Item>
        <Item>
            <ID>MS_Domain</ID>
            <Text>microsoft.com</Text>
        </Item>
        <Item>
            <ID>VS_Domain</ID>
            <Text>visualstudio.com</Text>
        </Item>
    </Items>

    Program.cs

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using System.Xml;
    namespace ConsoleApp1
    {
        class Program
        {
            static void Main(string[] args)
            {
                var t = new Test();
                Debug.WriteLine(t.GetValue("MS", "https").AbsoluteUri);
                Debug.WriteLine(t.GetValue("VisualStudio", "https").AbsoluteUri);
                Debug.WriteLine(t.GetValue("Microsoft", "http").AbsoluteUri);
            }
        }
        class Test
        {
            public Test()
            {
                DefaultValue = null;
                Parallel.Invoke(() =>
                {
                    m_Funcs = new Dictionary<Guid, Func<string, Uri>>
                    {
                        { new Guid("{829CD870-39E0-451E-A423-8527353B9731}"), FunMS },
                        { new Guid("{882581E7-44BF-4FA8-AD08-043E425D12B2}"), FunVS }
                    };
                    m_Keywords = new Dictionary<string, Guid>();
                    using (var reader = XmlReader.Create("Keywords.xml"))
                    {
                        while (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Element && reader.Name == "Item")
                            {
                                reader.ReadToDescendant("ID");
                                var id = new Guid(reader.ReadElementContentAsString());
                                if (m_Funcs.ContainsKey(id))
                                {
                                    reader.ReadToNextSibling("Keywords");
                                    var keys = reader.ReadElementContentAsString().Split(new Char[] { ',' });
                                    foreach (var key in keys)
                                    {
                                        m_Keywords.Add(key, id);
                                    }
                                }
                            }
                        }
                    };
                }, () =>
                {
                    m_Texts = new Dictionary<string, string>();
                    using (var reader = XmlReader.Create("Texts.xml"))
                    {
                        while (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Element && reader.Name == "Item")
                            {
                                reader.ReadToDescendant("ID");
                                var id = reader.ReadElementContentAsString();
                                reader.ReadToNextSibling("Text");
                                m_Texts.Add(id, reader.ReadElementContentAsString());
                            }
                        }
                    };
                });
            }
            public Uri DefaultValue { get; set; }
            public Uri GetValue(string key, string arg)
            {
                Guid id;
                return m_Keywords.TryGetValue(key.ToLower(), out id) ? m_Funcs[id](arg) : DefaultValue;
            }
            private Dictionary<Guid, Func<string, Uri>> m_Funcs;
            private Dictionary<string, Guid> m_Keywords;
            private Dictionary<string, string> m_Texts;
            private Uri FunMS(string arg)
            {
                arg = arg + m_Texts["Prefixes"] + m_Texts["MS_Domain"];
                //其它逻辑处理
                return new Uri(arg);
            }
            private Uri FunVS(string arg)
            {
                arg = arg + m_Texts["Prefixes"] + m_Texts["VS_Domain"];
                //其它逻辑处理
                return new Uri(arg);
            }
        }
    }

    以上为我对楼主问题的解法,如果.Net专家们认为用继承、工厂或反射更好,请不吝给出对应的代码。

    2017年7月19日 13:42
  • 参考 http://www.cnblogs.com/hezhihao/p/4804924.html

    小白学习设计模式——简单工厂、工厂方法、抽象工厂


    专注于.NET ERP/CRM开发框架,C/S架构,SQL Server + ORM(LLBL Gen Pro) + Infragistics WinForms

    2017年7月20日 0:20