У меня есть эта функция 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;
}
Это позволяет нам избегать конструкторов со слишком большим количеством параметров и достигать неизменности. На самом деле это решает все проблемы (упорядочение параметров и т. Д.). Все же:
- Все (включая FXCop и Jon Skeet) согласны с тем, что показ открытых полей плох .
- Эрик Липперт и др. Говорят, что полагаться на поля только для чтения для неизменности - ложь .
Вот тогда я запутался и решил написать вопрос: какой самый простой способ в C # избежать проблемы «слишком много параметров» без введения изменчивости? Возможно ли использовать структуру readonly для этой цели и при этом не иметь плохой дизайн API?
ПОЯСНЕНИЯ:
- Пожалуйста, примите во внимание, что нет нарушения принципа единой ответственности. В моем исходном случае функция просто записывает данные параметры в одну запись БД.
- Я не ищу конкретного решения данной функции. Я ищу обобщенный подход к таким проблемам. Я особенно заинтересован в решении проблемы «слишком много параметров» без введения изменчивости или ужасного дизайна.
ОБНОВИТЬ
Ответы, представленные здесь, имеют различные преимущества / недостатки. Поэтому я хотел бы преобразовать это в вики сообщества. Я думаю, что каждый ответ с примером кода и преимуществами и недостатками мог бы стать хорошим руководством для решения подобных проблем в будущем. Я сейчас пытаюсь выяснить, как это сделать.
DoSomeActionParameters
это одноразовый объект, который будет отброшен после вызова метода.Ответы:
Один стиль, включенный в каркас, обычно похож на группирование связанных параметров в связанные классы (но опять же проблематично с изменчивостью):
источник
Используйте комбинацию компоновщика и API в стиле языка предметной области - Fluent Interface. API немного более многословен, но с intellisense его очень быстро напечатать и легко понять.
источник
DoSomeAction
был метод этого.То, что у вас есть, является довольно верным признаком того, что рассматриваемый класс нарушает Принцип Единой Ответственности, потому что у него слишком много зависимостей. Ищите способы реорганизовать эти зависимости в кластеры зависимостей фасада .
источник
Просто измените структуру данных параметров с a
class
на a,struct
и все готово.Теперь метод получит свою собственную копию структуры. Изменения, внесенные в переменную аргумента, не могут наблюдаться методом, а изменения, вносимые методом в переменную, не могут наблюдаться вызывающей стороной. Изоляция достигается без неизменности.
Плюсы:
Минусы:
источник
Как насчет создания класса построителя внутри вашего класса данных. Класс данных будет иметь все установщики как частные, и только строитель сможет установить их.
источник
DoSomeActionParameters.Builder
экземпляр может быть использован для создания и настройки ровно одногоDoSomeActionParameters
экземпляра. После вызоваBuild()
последующих измененийBuilder
свойств s будет продолжено изменение свойств исходногоDoSomeActionParameters
экземпляра, а последующие вызовыBuild()
продолжат возвращать тот жеDoSomeActionParameters
экземпляр. Так и должно бытьpublic DoSomeActionParameters Build() { var oldObj = obj; obj = new DoSomeActionParameters(); return oldObj; }
.Я не программист на C #, но я считаю, что C # поддерживает именованные аргументы: (F # делает, и C # в значительной степени совместим для такого рода вещей) Это делает: http://msdn.microsoft.com/en-us/library/dd264739 .aspx # Y342
Таким образом, вызов вашего исходного кода становится:
это не займет больше места / мысли о том, что ваш объект создан и обладает всеми преимуществами того факта, что вы вообще не изменили то, что происходит в непослушной системе. Вам даже не нужно ничего перекодировать, чтобы указать, что аргументы названы
Редактировать: вот статья, которую я нашел об этом. http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ Я должен упомянуть, что C # 4.0 поддерживает именованные аргументы, 3.0 не поддерживает
источник
Почему бы просто не создать интерфейс, обеспечивающий неизменность (то есть только получатели)?
По сути, это ваше первое решение, но вы заставляете функцию использовать интерфейс для доступа к параметру.
и объявление функции становится:
Плюсы:
struct
решениеМинусы:
DoSomeActionParameters
это класс, который может быть сопоставлен сIDoSomeActionParameters
источник
IDoSomeActionParameters
и хочет захватить в ней значения, не может знать, будет ли достаточно удерживать ссылку, или он должен скопировать значения в какой-то другой объект. Читаемые интерфейсы полезны в некоторых контекстах, но не как средство сделать вещи неизменными.Я знаю, что это старый вопрос, но я решил добавить свое предложение, поскольку мне просто нужно было решить ту же проблему. Теперь, конечно, моя проблема немного отличалась от вашей, так как у меня было дополнительное требование не желать, чтобы пользователи могли сами создавать этот объект (вся гидратация данных происходила из базы данных, поэтому я мог отбросить все конструкции изнутри). Это позволило мне использовать приватный конструктор и следующий шаблон;
источник
Вы можете использовать подход в стиле Builder, хотя, в зависимости от сложности вашего
DoSomeAction
метода, это может быть тяжеловес. Что-то вроде этого:источник
В дополнение к ответу Манджи - вы также можете разделить одну операцию на несколько меньших. Для сравнения:
и
Для тех, кто не знает POSIX, создание ребенка может быть таким простым:
Каждый выбор дизайна представляет собой компромисс по поводу того, какие операции он может выполнять.
PS. Да - это похоже на строителя - только наоборот (т.е. на стороне вызываемого абонента вместо вызывающего абонента). Это может или не может быть лучше, чем строитель в этом конкретном случае.
источник
Это немного отличается от Mikeys, но я пытаюсь сделать так, чтобы все было как можно меньше писать.
DoSomeActionParameters является неизменным, поскольку он может быть и не может быть создан напрямую, так как его конструктор по умолчанию является частным
Инициализатор не неизменный, а только транспорт
Использование использует инициализатор на Инициализаторе (если вы меня поняли) И у меня могут быть значения по умолчанию в конструкторе Инициализатора по умолчанию
Параметры здесь будут необязательными, если вы хотите, чтобы некоторые из них были обязательными, вы можете поместить их в конструктор по умолчанию для Initializer.
И валидация может идти в методе Create
Так что теперь это выглядит
Еще немного куки я знаю, но все равно попробую
Редактирование: перемещение метода create в static в объекте параметров и добавление делегата, который проходит инициализатор, снимает некоторую изворотливость вызова
Итак, звонок теперь выглядит так
источник
Используйте структуру, но вместо открытых полей имейте открытые свойства:
Jon и FXCop будут удовлетворены, потому что вы выставляете свойства, а не поля.
Эрик будет удовлетворен, потому что, используя свойства, вы можете убедиться, что значение установлено только один раз.
Один неизменный объект (значение может быть установлено, но не изменено). Работает для значений и ссылочных типов.
источник
this
являются гораздо более злыми, чем открытые поля. Я не уверен, почему вы думаете, что только установка значения однажды делает вещи "хорошо"; еслиthis
начать с экземпляра структуры по умолчанию, проблемы, связанные со свойствами -mutating, все равно будут существовать.Вариант ответа Самуила, который я использовал в своем проекте, когда у меня была такая же проблема:
И использовать:
В моем случае параметры были преднамеренно модифицируемыми, потому что методы установки не учитывали все возможные комбинации, а просто выставляли их общие комбинации. Это потому, что некоторые из моих параметров были довольно сложными, и методы записи для всех возможных случаев были бы сложными и ненужными (сумасшедшие комбинации используются редко).
источник
DoMagic
Имеет в своем контракте , хотя , что она не будет изменять объект. Но я думаю, это не защищает от случайных изменений.