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

本文关键字:模式 开关 语句 接口 | 更新日期: 2023-09-27 18:18:41

这个问题可能以前已经发布过了,但是我找不到它。

我写这类东西已经很长时间了,我坐下来写一些新的东西,就开始打字,好像这是我自己的模式。最近出现了一个项目,我发现自己看着自己的代码,开始思考它看起来有多臭。

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

如果不使用反射,就没有更清晰的连接方式了吗?

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

一个选择是拥有一个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();

你可能还想考虑使用enum而不是魔法字符串常量。

实际上,我觉得这看起来不太臭。如果你不喜欢开关语句,你可以去IEmailQuery-Path,然后在Dictionary<string,IEmailQuery>中连接它。这可能会节省一些代码行,因为您可以像这样访问它:

QueryDictionary["MyKey"].ExecuteQuery(); 

欢呼,奥利弗

我会选择Factory模式,就像

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

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

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

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

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

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

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

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

命令模式是用于此场景的完美模式。请参阅http://www.codeproject.com/KB/books/DesignPatterns.aspx获取该模式的c#实践描述。正如Jon Skeet所描述的,lambda是您可以看到的有用的较新的编程结构。参见命令模式:如何向命令传递参数?有关模式使用的更多讨论。