Метод рефакторинга для уменьшения количества операторов switch — C#

В настоящее время я пытаюсь найти лучший способ рефакторинга класса, который выглядит примерно так:

public static SearchModel GetSearchResults(SearchModel model)
    {
        List<ResultModel> results = new List<ResultModel>();

        try
        {
            string sqlCommand = string.Empty;

            switch (model.Attribute)
            {
                case "Users":
                    sqlCommand = "GeneralUserSearch";
                    break;
                case "Favorites":
                    sqlCommand = "UserFavorites";
                    break;
                case "Email":
                    sqlCommand = "EmailSearch";
                    break;                                 
            }

            using (SqlConnection conn = new SqlConnection("connection string"))
            {
                conn.Open();

                using (SqlCommand cmd = AdoBase.GetSqlCommand(sqlCommand, conn))
                {                        
                    switch (model.Attribute)
                    {
                        case "Users":
                            if (!string.IsNullOrWhiteSpace(model.Name)) {
                                cmd.Parameters.AddWithValue("Name", model.Name);
                            }
                            if (!string.IsNullOrWhiteSpace(model.Username)) {
                                cmd.Parameters.AddWithValue("Username", model.Username);
                            }                                                           
                            break;
                        case "Favorites":
                            cmd.Parameters.AddWithValue("Favorites", model.Favorites);
                            break;
                        case "Email":
                            cmd.Parameters.AddWithValue("Email", model.Email);
                            break;                                             
                    }

                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        if (reader.HasRows)
                        {
                            while (reader.Read())
                            {
                                ResultModel result = new ResultModel();

                                switch (model.Attribute)
                                {
                                    case "Users":
                                        result.Users.Add(reader["User"]);                                       
                                        break;
                                    case "Favorites":
                                        result.User = reader["User"];
                                        result.Favorites = reader["Favorites"];
                                        break;
                                    case "Email":
                                        result.User = reader["User"];
                                        result.Email = reader["Email"];
                                        break;                                             
                                }

                                results.Add(result);
                            }
                        }
                    }

                }
            }
        }
        catch (Exception ex)
        {
            return ex;
        }

        return model;
    }

Поскольку поиск изменяется в зависимости от значения в model.Attribute, используется оператор switch. Однако большая часть кода не зависит от атрибута. Есть ли способ реорганизовать это, чтобы исключить операторы switch или сократить их до одного?

Я голосую за то, чтобы закрыть этот вопрос как не по теме, потому что 1. Рефакторинг принадлежит CodeReview.StackExchange 2. Это проблема X, Y, вы просите людей реорганизовать свой код, когда вместо этого вы должны изменить свою архитектуру.

johnny 5 19.01.2019 01:29
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
118
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Переключатели — это запах кода, который говорит, что ваш код не объектно-ориентированный. В этом случае у вас могут быть разные типы SearchModel с методом search, реализованным по-разному для каждого типа.

Это похоже на комментарий, поскольку на самом деле он не отвечает на вопрос. Я бы попытался расширить это примерами

Austin T French 19.01.2019 00:43

@AustinTFrench «Не делайте этого, потому что…» — это ответ. Как делать то, что вы должны делать вместо этого, обычно другой вопрос.

StackOverthrow 14.02.2019 17:04
Ответ принят как подходящий

Я согласен с ТКК

Тем не менее, чтобы ответить на ваш вопрос на основе предоставленного кода, я вижу только способ исключить один из операторов switch в этом методе.

Первый оператор switch установит sqlcommand и параметры, а второй останется неизменным в цикле while.

Объявите их в верхней части вашего метода:

`SqlConnection conn = new SqlConnection("connection string");
 SqlCommand cmd = AdoBase.GetSqlCommand("", conn);`

Измените первый оператор switch, чтобы добавить параметры в команду sql:

switch (model.Attribute)
{
    case "Users":
        sqlCommand = "GeneralUserSearch";
        if (!string.IsNullOrWhiteSpace(model.Name))
        {
            cmd.Parameters.AddWithValue("Name", model.Name);
        }

        if (!string.IsNullOrWhiteSpace(model.Name))
        {
            cmd.Parameters.AddWithValue("Username", model.Username);
        }

        break;
    case "Favorites":
        sqlCommand = "UserFavorites";
        cmd.Parameters.AddWithValue("Favorites", model.Favorites);
        break;
    case "Email":
        sqlCommand = "EmailSearch";
        cmd.Parameters.AddWithValue("Email", model.Email);
        break;
}

Удалите этот блок кода:

switch (model.Attribute)
{
    case "Users":
        if (!string.IsNullOrWhiteSpace(model.Name)) {
            cmd.Parameters.AddWithValue("Name", model.Name);
        }
        if (!string.IsNullOrWhiteSpace(model.Name)) {
            cmd.Parameters.AddWithValue("Username", model.Username);
        }                                                           
        break;
    case "Favorites":
        cmd.Parameters.AddWithValue("Favorites", model.Favorites);
        break;
    case "Email":
        cmd.Parameters.AddWithValue("Email", model.Email);
        break;                                             
}

И добавьте, наконец, после вашего улова, чтобы очистить объекты соединения и команды:

 finally
 {
    conn.Dispose();
    cmd.Dispose();
 }

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

public static SearchModel GetSearchResults(SearchModel model)
{
    List<ResultModel> results = new List<ResultModel>();
    SqlCommand cmd = new SqlCommand(); // only for compile for finnaly dispose
    try
    {
        using (SqlConnection conn = new SqlConnection("connection string"))
        {
            conn.Open();
            string sqlCommand = string.Empty;

            switch (model.Attribute)
            {
                case "Users":
                    cmd = AdoBase.GetSqlCommand("GeneralUserSearch", conn);
                    if (!string.IsNullOrWhiteSpace(model.Name)) {
                        cmd.Parameters.AddWithValue("Name", model.Name);
                    }
                    if (!string.IsNullOrWhiteSpace(model.Name)) { // redundant if or mistake, and there should be model.Username
                        cmd.Parameters.AddWithValue("Username", model.Username);
                    }
                    break;
                case "Favorites":
                    cmd = AdoBase.GetSqlCommand("UserFavorites", conn);
                    cmd.Parameters.AddWithValue("Favorites", model.Favorites);
                    break;
                case "Email":
                    cmd = AdoBase.GetSqlCommand("EmailSearch", conn);
                    cmd.Parameters.AddWithValue("Email", model.Email);
                    break;
            }
            SimpleMethod(results, cmd, model);
        }
    }
    catch (Exception ex)
    {
        return ex;
    }
    finally
    {
        cmd.Dispose();
    }
    return model;
}

Простой метод:

public static void SimpleMethod(List<ResultModel> results, SqlCommand cmd, SearchModel model)
{
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        if (reader.HasRows)
        {
            while (reader.Read())
            {
                ResultModel result = new ResultModel();

                switch (model.Attribute)
                {
                    case "Users":
                        result.Users.Add(reader["User"]);
                        break;
                    case "Favorites":
                        result.User = reader["User"];
                        result.Favorites = reader["Favorites"];
                        break;
                    case "Email":
                        result.User = reader["User"];
                        result.Email = reader["Email"];
                        break;
                }
                results.Add(result);
            }
        }
    }
}

Другие ответы полны анти-шаблонов. Необходимость в операторах switch (особенно повторяющихся так часто) кажется возможностью двигаться в направлении большего ООП.

Я не рефакторил все это, а просто чтобы дать вам представление.

public class SearchHelper
{
    //why does this need to return the model at all? the model isn't altered 
    //and would already be in scope for whatever is calling this method
    public static void GetSearchResults(SearchModel model)
    {
        List<ResultModel> results = new List<ResultModel>();

        try
        {
            using (SqlConnection conn = new SqlConnection("connection string"))
            {
                conn.Open();

                using (SqlCommand cmd = AdoBase.GetSqlCommand(model.SqlCommandName, conn))
                {
                    //this will mutate the object, so you don't need a return type. I'd suggest refactoring this further.
                    model.BuildSqlCommand(cmd);

                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        //your code sample wasn't returning this, but maybe you intended to?
                        BuildResultSet(reader);
                    }

                }
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    private static IEnumerable<ResultModel> BuildResultSet(SqlDataReader reader)
    {
        var results = new List<ResultModel>();

        if (!reader.HasRows) { return results; }

        while (reader.Read())
        {
            ResultModel result = new ResultModel();

            //  ...the result composition would need to be refactored in a similar way as well
            results.Add(result);
        }
        return results;
    }
}

public abstract class SearchModel
{
    public string SqlCommandName { get; private set; }

    private SearchModel() { }

    protected SearchModel(string sqlCommandName)
    {
        SqlCommandName = sqlCommandName;
    }

    public abstract void BuildSqlCommand(SqlCommand command);
}

public class UserSearchModel : SearchModel
{
    public string Name { get; set; }

    public string Username { get; set; }

    public UserSearchModel() : base("GeneralUserSearch")
    {
    }

    //warning...this mutates the input parameter
    public override void BuildSqlCommand(SqlCommand command)
    {
        if (!string.IsNullOrWhiteSpace(Name))
        {
            command.Parameters.AddWithValue(nameof(Name), Name);
        }
        if (!string.IsNullOrWhiteSpace(Username))
        {
            command.Parameters.AddWithValue(nameof(Username), Username);
        }
    }
}

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

Тем не менее, вы можете видеть, насколько это субъективно и может быть неуместным для SO. Кажется, здесь проблема с фундаментальным дизайном.

Другие вопросы по теме