Как избежать проблемы «слишком много параметров» в дизайне API? - PullRequest
154 голосов
/ 05 июня 2011

У меня есть эта функция API:

public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d, 
     string e, string f, out Guid code)

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

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

public class DoSomeActionParameters
{
    public string A;
    public string B;
    public DateTime C;
    public OtherEnum D;
    public string E;
    public string F;        
}

Это уменьшило мою декларацию API до:

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)

Хорошо.Выглядит очень невинно, но мы действительно внесли огромное изменение: мы ввели изменчивость.Потому что раньше мы фактически передавали анонимный неизменяемый объект: параметры функции в стеке.Теперь мы создали новый класс, который очень изменчив.Мы создали возможность манипулировать состоянием вызывающей стороны .Это отстой.Теперь я хочу, чтобы мой объект был неизменным, что мне делать?

public class DoSomeActionParameters
{
    public string A { get; private set; }
    public string B { get; private set; }
    public DateTime C { get; private set; }
    public OtherEnum D { get; private set; }
    public string E { get; private set; }
    public string F { get; private set; }        

    public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d, 
     string e, string f)
    {
        this.A = a;
        this.B = b;
        // ... tears erased the text here
    }
}

Как видите, я фактически заново создал свою первоначальную проблему: слишком много параметров.Очевидно, что это не тот путь.Что я собираюсь делать?Последний вариант для достижения такой неизменности - это использование структуры «только для чтения», подобной этой:

public struct DoSomeActionParameters
{
    public readonly string A;
    public readonly string B;
    public readonly DateTime C;
    public readonly OtherEnum D;
    public readonly string E;
    public readonly string F;        
}

, которая позволяет нам избегать конструкторов со слишком большим количеством параметров и достигать неизменности.На самом деле это решает все проблемы (упорядочение параметров и т. Д.).Тем не менее:

Вот тогда я запутался и решил написать этот вопрос: Какой самый простой способ в C # избежать проблемы «слишком большого количества параметров» без введения изменчивости?Можно ли использовать для этой цели только для чтения структуру и при этом не иметь плохой дизайн API?

УТОЧНЕНИЯ:

  • Пожалуйста, предположите, что нарушения не существуетпринцип единой ответственности.В моем исходном случае функция просто записывает данные параметры в одну запись БД.
  • Я не ищу конкретного решения для данной функции.Я ищу обобщенный подход к таким проблемам.Я особенно заинтересован в решении проблемы «слишком много параметров» без введения изменчивости или ужасного дизайна.

ОБНОВЛЕНИЕ

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

Ответы [ 13 ]

2 голосов
/ 05 июня 2011

Вы можете использовать подход в стиле Builder, хотя, в зависимости от сложности вашего DoSomeAction метода, это может быть тяжеловес.Что-то вроде этого:

public class DoSomeActionParametersBuilder
{
    public string A { get; set; }
    public string B { get; set; }
    public DateTime C { get; set; }
    public OtherEnum D { get; set; }
    public string E { get; set; }
    public string F { get; set; }

    public DoSomeActionParameters Build()
    {
        return new DoSomeActionParameters(A, B, C, D, E, F);
    }
}

public class DoSomeActionParameters
{
    public string A { get; private set; }
    public string B { get; private set; }
    public DateTime C { get; private set; }
    public OtherEnum D { get; private set; }
    public string E { get; private set; }
    public string F { get; private set; }

    public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d, string e, string f)
    {
        A = a;
        // etc.
    }
}

// usage
var actionParams = new DoSomeActionParametersBuilder
{
    A = "value for A",
    C = DateTime.Now,
    F = "I don't care for B, D and E"
}.Build();

result = foo.DoSomeAction(actionParams, out code);
1 голос
/ 05 июня 2011

Используйте структуру, но вместо общедоступных полей имейте общедоступные свойства:

• Все (включая FXCop и Jon Skeet) согласны с тем, что обнародование открытых полей является плохим.

Джон и FXCop будут удовлетворены, потому что вы выставляете свойства, а не поля.

• Эрик Липперт и др. Говорят, что полагаться на неизменяемость полей для неизменяемости - ложь.

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

    private bool propC_set=false;
    private date pC;
    public date C {
        get{
            return pC;
        }
        set{
            if (!propC_set) {
               pC = value;
            }
            propC_set = true;
        }
    }

Один полуизменяемый объект (значение может быть установлено, но не изменено).Работает для значений и справочных типов.

0 голосов
/ 05 июня 2011

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

class MagicPerformer
{
    public int Param1 { get; set; }
    public string Param2 { get; set; }
    public DateTime Param3 { get; set; }

    public MagicPerformer SetParam1(int value) { this.Param1 = value; return this; }
    public MagicPerformer SetParam2(string value) { this.Param2 = value; return this; }
    public MagicPerformer SetParam4(DateTime value) { this.Param3 = value; return this; }

    public void DoMagic() // Uses all the parameters and does the magic
    {
    }
}

И использовать:

new MagicPerformer().SeParam1(10).SetParam2("Yo!").DoMagic();

ВВ моем случае параметры были преднамеренно модифицируемыми, потому что методы установки не учитывали все возможные комбинации, а просто выставляли их общие комбинации.Это потому, что некоторые из моих параметров были довольно сложными, и методы записи для всех возможных случаев были бы сложными и ненужными (сумасшедшие комбинации используются редко).

...