接口或switch语句,找到正确的模式

时间:2011-12-21 13:51:02

标签: c# design-patterns switch-statement

这个问题可能以前发过,但我找不到了。

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

 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方法。

不使用反射,是否没有更清洁的接线方式?

5 个答案:

答案 0 :(得分:7)

一种选择是拥有Dictionary<string, Func<IEmailQuery>>地图。你可以像这样构建它:

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<string, Type>并通过反射实例化它 - 但会有一些丑陋的演员等。

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

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

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

答案 1 :(得分:3)

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

QueryDictionary["MyKey"].ExecuteQuery(); 

干杯, 奥利弗

答案 2 :(得分:1)

我会选择工厂模式,比如

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

然后

//.. first get String TemplateKey

IEmailQuery qry=EmailQueryFactory.Create(TemplateKey);
qry.Execute();

答案 3 :(得分:0)

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

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

注册时,您需要告诉DI容器一些服务名称以区分实现,因为它们实现了相同的接口。

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

实例化时,您可以通过再次提供服务名称来获得相应的实现:

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

答案 4 :(得分:0)

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