【问题标题】:Interface or switch statement, finding the right pattern接口或 switch 语句,找到正确的模式
【发布时间】:2012-01-25 07:48:58
【问题描述】:

这个问题之前可能已经发布过,但我找不到。

我写这种东西已经很久了,我坐下来写一些新的东西,然后开始打字,就好像它是我自己的模式一样。最近出现了一个项目,我发现自己正在查看自己的代码并开始思考它看起来有多臭。

 BackgroundInfoIfYouCare

在这个特定的库中,我需要向用户发送电子邮件。到目前为止,有 13 封罐头电子邮件。

每封电子邮件都有自己的模板(我使用的是 Razor 解析器,因此模板是用 cshtml 编写的)。 每个电子邮件模板都有一个字符串名称键。 每封电子邮件都有自己的 EF4 查询,用于返回基于“会员”实体和所有相关数据的模型。

我有一个类接受一个字符串,它是一个电子邮件模板名称键。

该方法将运行适当的查询并返回一个列表,获取电子邮件模板。

列表和模板被传递给解析器以将每个成员资格合并到模板并返回列表电子邮件。

 EndOfBackgroundInfoIfYouCare

所以真正的问题是……最好的方法是什么?

一种方法是只使用一个开关

public List<Membership> Execute(string TemplateKey) {
switch (TemplateKey) 
        {
            case "SomethingExpired":
                QueryResult = new SomethingExpiredEmailQuery().ExecuteQuery();
                break;
            case "SomethingExpireIn30":
                QueryResult = new SomethingExpireIn30EmailQuery().ExecuteQuery();
                break;
            case "FirstTimeLoginThanks":
                QueryResult = new FirstTimeLoginThanksEmailQuery().ExecuteQuery();
                break;
            case "SecurityTraining":
                QueryResult = new SecurityTrainingEmailQuery().ExecuteQuery();
                break;
            case ETC ETC ETC...

}

另一种方法是使用接口

IEmailQuery
void ExecuteQuery()

但如果我使用接口,我仍然需要实例化 Query 类。它不保存代码,也不会使代码更易于维护。

通过反射,我可以使用以下模式为所有电子邮件查询命名: SecurityTraining 的电子邮件模板键的查询名称为 SecurityTrainingEmailQuery,我可以使用反射来实例化并调用 ExecuteQuery 方法。

如果不使用反射,有没有更清洁的方法来连接它?

【问题讨论】:

    标签: c# design-patterns switch-statement


    【解决方案1】:

    一种选择是拥有Dictionary&lt;string, Func&lt;IEmailQuery&gt;&gt; 地图。你可以这样构建它:

    private static readonly Dictionary<string, Func<IEmailQuery>> MailQueryMap = 
        new Dictionary<string, Func<IEmailQuery>> {
        { "SomethingExpired", () => new SomethingExpiredMailQuery() },
        { "SomethingExpireIn30", () => new SomethingExpireIn30EmailQuery() },
        // etc
    };
    

    然后:

    public List<Membership> Execute(string templateKey) {
        IEmailQuery query = MailQueryMap[templateKey].Invoke();
        var queryResult = query.ExecuteQuery();
        // ...
    }
    

    如果您可以保证您只需要无参数的构造函数,您始终可以存储 Dictionary&lt;string, Type&gt; 并通过反射实例化它 - 但会有一些丑陋的演员表等。

    编辑:当然,如果模板的名称总是类型的名称,你可以使用

    Type queryType = Type.GetType(namespacePrefix + "." + templateKey);
    IEmailQuery query = (IEmailQuery) Activator.CreateInstance(queryType);
    var queryResult = query.ExecuteQuery();
    

    您可能还想考虑使用枚举而不是魔术字符串常量。

    【讨论】:

    • 这是如何遵循开闭原则的?如果我正确阅读了 Paul 的帖子,他希望避免更改现有类(用新案例扩展 switch 语句)。
    • @Wivani:我没有看到任何暗示 - 我只看到他希望代码更简单,更易于维护。它在问题的什么地方谈到了避免改变现有的课程?
    • 猜猜我正在把你和我搞得一团糟;-) 让我们看看 Paul 能否证实我认为他在寻找什么。
    • 我愿意对代码进行任何更改以使其变得更好。到目前为止,我喜欢字典的想法。我的问题是,在过去的 20 年中,我可能经历了数百次这种情况。我现在自动写了一个 switch 语句,我看着它挠头,想知道是否有更好的方法。
    • 另外,我选择在数据库中为每个电子邮件模板存储一个密钥,以便于跟踪对象和调试。您只需查看密钥即可知道正在运行哪个模板。我知道枚举可能是一个更聪明的解决方案,但这也有效。
    【解决方案2】:

    实际上,这在我看来并不太臭。如果您不喜欢 switch 语句,您可以使用 IEmailQuery-Path 并将其连接到 Dictionary&lt;string,IEmailQuery&gt; 中。 这可能会节省一些代码行,因为您可以这样访问它:

    QueryDictionary["MyKey"].ExecuteQuery(); 
    

    干杯, 奥利弗

    【讨论】:

    • 嗯,乔恩的回答是相似的,只是比我的更先进,我担心;-)
    【解决方案3】:

    我会选择工厂模式,比如

    class EmailQueryFactory
    {
      public IEmailQuery Create(String TemplateKey)
      {
        ....
      }
    }
    

    然后

    //.. first get String TemplateKey
    
    IEmailQuery qry=EmailQueryFactory.Create(TemplateKey);
    qry.Execute();
    

    【讨论】:

    • 我正在考虑使用工厂,但是在工厂中你最终会遇到同样的问题。您需要将请求的模板连接到正确的查询类。
    【解决方案4】:

    为什么不使用您在问题中提出的反射?我认为这是做这种事情的一种有效方式。

    另一种方法是使用控制反转/依赖注入模式。您像以前一样定义一个接口,并将所有已知的具体实现注册到您的 DI 容器(这可以通过配置或代码来完成)。

    注册的时候需要告诉DI容器一些服务名来区分实现,因为它们实现的接口是一样的。

    YourIocContainer.Register<IEmailQuery>(typeof(SomethingExpiredMailQuery),
                                           "SomethingExpiredMailQuery");
    

    实例化时,再次提供服务名即可得到对应的实现:

    public List<Membership> Execute(string TemplateKey) {
       YourIocContainer.Resolve<IEmailQuery>(TemplateKey);
    

    【讨论】:

    • 当我想在stackoverflow上问这个问题时,我想到的第一件事就是反思。速度不是问题,因此它是一种有效的使用方法。我只是好奇我没有想到的其他解决方案。我也喜欢你的依赖注入解决方案。
    【解决方案5】:

    命令模式是用于此场景的完美模式。请参阅 http://www.codeproject.com/KB/books/DesignPatterns.aspx 以了解此模式的实践 c# 描述。正如 Jon Skeet 所描述的,Lambda 是您可以看到的有用的较新的编程结构。有关该模式使用的更多讨论,请参阅Command Pattern : How to pass parameters to a command?

    【讨论】:

    • 感谢您提及命令模式。迫使我重新阅读 4gang 书中的定义。我很好奇我会从它的使用中获得什么。我是否还需要创建接收器的实例来调用命令?让我回到类列表并将请求的命令与正确的命令相关联。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-13
    • 2021-04-26
    • 1970-01-01
    • 2015-01-10
    • 1970-01-01
    • 2014-05-20
    相关资源
    最近更新 更多