Интерфейс или оператор переключения, нахождение правильного шаблона - PullRequest
5 голосов
/ 21 декабря 2011

Этот вопрос, возможно, был опубликован ранее, но я не смог его найти.

Я так долго пишу такие вещи, я сажусь, чтобы написать что-то новое, и просто начинаю печатать это, как будто это был мой собственный шаблон.Недавно появился проект, и я обнаружил, что смотрю на свой собственный код, и начал думать, как он вонючий.

 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 ]

7 голосов
/ 21 декабря 2011

Один из вариантов - иметь карту 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 вместо констант магической строки.

3 голосов
/ 21 декабря 2011

На самом деле, это не кажется мне слишком вонючим.Если вам не нравится оператор switch, вы можете перейти к IEmailQuery-Path и просто подключить его в Dictionary<string,IEmailQuery>.Это, вероятно, экономит некоторые строки кода, так как вы можете получить к нему следующий доступ:

QueryDictionary["MyKey"].ExecuteQuery(); 

Cheers, Oliver

1 голос
/ 21 декабря 2011

Я бы выбрал шаблон Фабрики, что-то вроде

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

, а затем

//.. first get String TemplateKey

IEmailQuery qry=EmailQueryFactory.Create(TemplateKey);
qry.Execute();
0 голосов
/ 21 декабря 2011

Шаблон команды - идеальный шаблон для использования в этом сценарии. Смотрите http://www.codeproject.com/KB/books/DesignPatterns.aspx для практики c # описание этого паттерна. Лямбда, как описал Джон Скит, - это полезные новые программные конструкции, которые вы можете увидеть. См. Шаблон команды: Как передать параметры в команду? для более подробного обсуждения использования шаблона.

0 голосов
/ 21 декабря 2011

Почему бы не использовать рефлексию, как вы предложили в своем вопросе?Я думаю, что это правильный способ делать подобные вещи.

Еще один подход - использование инверсии схемы внедрения управления / зависимости.Вы определяете интерфейс так же, как вы это делаете, и регистрируете все известные конкретные реализации в своем контейнере DI (это можно сделать с помощью конфигурации или кода).

При регистрации вам необходимо сообщить контейнеру DI имя службы для различения реализаций, поскольку они реализуют один и тот же интерфейс.

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

При создании экземпляра вы можете получить соответствующую реализацию, снова указав имя службы:

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