Як уникнути проблеми "занадто багато параметрів" в дизайні API?


160

У мене є така функція 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?

ПОЯСНЕННЯ:

  • Будь ласка, припустіть, що немає порушення принципу єдиної відповідальності. У моєму початковому випадку функція просто записує задані параметри в один запис БД.
  • Я не шукаю конкретного рішення даної функції. Я шукаю узагальненого підходу до таких проблем. Мені спеціально цікаво вирішити проблему "занадто багато параметрів" без введення змінності або жахливого дизайну.

ОНОВЛЕННЯ

Надані тут відповіді мають різні переваги / недоліки. Тому я хотів би перетворити це на вікі спільноти. Я думаю, що кожна відповідь із зразком коду та плюсами / мінусами стане хорошим посібником для подібних проблем у майбутньому. Зараз я намагаюся з’ясувати, як це зробити.


Чистий код: Підручник з гнучкої майстерності програмного забезпечення Роберта К. Мартіна та книги "Рефакторинг" Мартіна Фаулера висвітлює це трохи
Іен Рінгроуз

1
Хіба це рішення для Builder не є зайвим, якщо ви використовуєте C # 4, де у вас є необов’язкові параметри ?
khellang

1
Я можу бути дурним, але я не розумію, як це проблема, враховуючи, що це DoSomeActionParameters це об'єкт, що викидається, який буде відкинутий після виклику методу.
Vilx-

6
Зауважте, що я не кажу, що вам слід уникати полів, які читаються тільки в структурі; незмінні структури є найкращою практикою, і поля, які читають лише, допоможуть вам створити самодокументовані незмінні структури. Моя думка полягає в тому, що ви не повинні покладатися на спостереження , що вони читаються тільки для того, щоб вони ніколи не змінювалися , оскільки це не є гарантією того, що поле, яке читається, дає вам структуру. Це конкретний випадок із більш загальних порад, що не слід ставитися до типів цінності як би довідкових типів; вони дуже різні тварини.
Ерік Ліпперт

7
@ssg: Я теж цього хотів би. До C # ми додали функції, які сприяють незмінності (наприклад, LINQ) одночасно, оскільки ми додали функції, що сприяють мутаційності (як ініціалізатори об'єктів.) Було б непогано мати кращий синтаксис для просування незмінних типів. Ми дуже задумуємось над цим і маємо кілька цікавих ідей, але я не очікував би подібного на наступну версію.
Ерік Ліпперт

Відповіді:


22

Один стиль, який охоплює рамки, зазвичай нагадує групування пов'язаних параметрів у споріднені класи (але знову ж таки проблематичні щодо змінності):

var request = new HttpWebRequest(a, b);
var service = new RestService(request, c, d, e);
var client = new RestClient(service, f, g);
var resource = client.RequestRestResource(); // O params after 3 objects

Конденсація всіх рядків у масив рядків має сенс лише у тому випадку, якщо вони всі пов'язані. З порядку аргументів, схоже, вони не всі повністю пов'язані.
icktoofay

Крім того, ви погодилися, що це буде працювати лише в тому випадку, якщо у вас є багато тих же типів, що і параметр.
Тімо Віллемсен

Чим це відрізняється від мого першого прикладу в питанні?
Седат Капаноглу

Ну, це звичайний сприйнятий стиль навіть у .NET Framework, що вказує на те, що ваш перший приклад цілком правильний у тому, що він робить, навіть він вводить незначні проблеми щодо змінності (хоча, очевидно, цей приклад з іншої бібліотеки, очевидно). Приємне питання до речі.
Теоман Сойгул

2
Я з часом більше сходився до цього стилю. Мабуть, значну кількість проблем із "занадто великою кількістю параметрів" можна вирішити за допомогою хороших логічних груп та абстракцій. Врешті-решт, це робить код більш читабельним та більш модульованим.
Седат Капаноглу

87

Використовуйте комбінацію API стилю для розробника та конкретного домену - Fluent Interface. API дещо докладніший, але з intellisense його дуже швидко набрати та легко зрозуміти.

public class Param
{
        public string A { get; private set; }
        public string B { get; private set; }
        public string C { get; private set; }


  public class Builder
  {
        private string a;
        private string b;
        private string c;

        public Builder WithA(string value)
        {
              a = value;
              return this;
        }

        public Builder WithB(string value)
        {
              b = value;
              return this;
        }

        public Builder WithC(string value)
        {
              c = value;
              return this;
        }

        public Param Build()
        {
              return new Param { A = a, B = b, C = c };
        }
  }


  DoSomeAction(new Param.Builder()
        .WithA("a")
        .WithB("b")
        .WithC("c")
        .Build());

+1 - такий підхід (який я часто бачив як "вільний інтерфейс") - це саме те, що я мав на увазі також.
Даніель Приден

+1 - це те, що я опинився в тій же ситуації. За винятком того, що був лише один клас, і DoSomeActionбув його метод.
Vilx-

+1 Я теж думав про це, але оскільки я новачок у вільних інтерфейсах, я не знав, чи це тут ідеально підходить. Дякую, що підтвердили власну інтуїцію.
Метт

Я не чув про модель "Будівельника", перш ніж я задав це питання, тому це було глибоким досвідом для мене. Цікаво, як це часто? Тому що будь-який розробник, який не чув про шаблон, може мати проблеми з розумінням використання без документації.
Седат Капаноглу

1
@ssg, це часто в цих сценаріях. У BCL є, наприклад, класи побудови з'єднувальних рядків.
Семюель Нефф

10

У вас є досить впевнене свідчення того, що даний клас порушує Принцип єдиної відповідальності, оскільки має занадто багато залежностей. Шукайте способи переформулювання цих залежностей у кластери фасадних залежностей .


1
Я б все-таки рефактор ввів один або кілька об'єктів параметрів, але очевидно, якщо ви перемістите всі аргументи до одного непорушного типу, ви нічого не досягли. Трюк полягає в пошуку кластерів аргументів, які є більш тісно пов'язаними, а потім перефактуруйте кожен з цих кластерів на окремий об'єкт параметра.
Марк Семанн

2
Ви можете перетворити кожну групу в незмінний об’єкт сам по собі. Кожному об’єкту потрібно було б прийняти лише кілька параметрів, тому, хоча фактична кількість аргументів залишається однаковою, кількість аргументів, що використовуються в будь-якому одному конструкторі, буде зменшена.
Марк Семанн

2
+1 @ssg: Кожен раз, коли мені вдавалося переконати себе у чомусь подібному, я з часом доводив себе неправильно, виводячи корисну абстракцію з великих методів, що вимагають такого рівня параметрів. Книга DDD Еванса може дати вам кілька ідей щодо того, як думати про це (хоча ваша система звучить так, що це потенційно далеко не відповідне місце для застосування таких малюнків) - (і в будь-якому випадку це просто чудова книга).
Рубен Бартелінк

3
@Ruben: Жодна нормальна книга не скаже, що "в хорошому проекті OO клас не повинен мати більше 5 властивостей". Класи логічно згруповані, і такий вид контекстуалізації не може базуватися на кількості. Однак підтримка незмінності C # починає створювати проблеми з певним числом властивостей, перш ніж ми почнемо порушувати хороші принципи проектування OO.
Седат Капаноглу

3
@ Рубен: Я не судив твоїх знань, ставлення чи вдачі. Я очікую, що ти зробиш те саме. Я не кажу, що ваші рекомендації не є гарними. Я кажу, що моя проблема може виявитися навіть на найдосконалішому дизайні, і це, здається, є занедбаним моментом. Хоча я розумію, чому досвідчені люди задають фундаментальні запитання щодо найпоширеніших помилок, стає менш приємним з'ясувати це через пару раундів. Знову мушу сказати, що дуже складно мати цю проблему з ідеальним дизайном. І дякую за підсумки!
Седат Капаноглу

10

Просто змініть структуру даних параметрів на class на" structта "ви" готові йти.

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

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code) 

Тепер метод отримає власну копію структури. Зміни, внесені до змінної аргументу, не можуть спостерігатися методом, а зміни, які метод робить в змінній, не можуть спостерігати абонент. Ізоляція досягається без незмінності.

Плюси:

  • Найпростіший у виконанні
  • Найменша зміна поведінки в базовій механіці

Мінуси:

  • Незмінність не очевидна, вимагає уваги розробника.
  • Непотрібне копіювання для підтримки незмінності
  • Заняття укладають місце

+1 Це правда, але це не вирішує частину про те, що "безпосередньо викривати поля - це зло". Я не впевнений, наскільки погані речі можуть погіршитися, якщо я вибираю використовувати поля замість властивостей цього API.
Седат Капаноглу

@ssg - Отже, зробіть їх загальнодоступними властивостями замість полів. Якщо ви ставитесь до цього як до структури, яка ніколи не матиме код, то це не має великої різниці в тому, чи використовуєте ви властивості чи поля. Якщо ви вирішите коли-небудь надати йому код (наприклад, перевірка чи щось таке), то ви обов'язково захочете зробити їх властивостями. Принаймні, на публічних полях ніхто ніколи не матиме ілюзій щодо інваріантів, що існують у цій структурі. Її доведеться перевірити методом точно так, як і параметри.
Джефрі Л Уітлідж

О, це правда. Дивовижно! Дякую.
Седат Капаноглу

8
Я думаю, що правило експонування-полів-це-зло стосується об'єктів в об'єктно-орієнтованому дизайні. Структура, яку я пропоную, - це просто металевий контейнер параметрів. Оскільки ваші інстинкти мали бути з чимось незмінним, я думаю, що такий базовий контейнер може бути доречним для цього випадку.
Джефрі Л Уітлідж

1
@JeffreyLWhitledge: Мені дуже не подобається думка про те, що споруди під відкритим полем - це зло. Таке твердження вважає мене рівнозначним тому, що викрутки є злими, і люди повинні використовувати молотки, оскільки точки викруток вм'яті головками цвяхів. Якщо вам потрібно забити цвях, слід використовувати молоток, але якщо потрібно загнати гвинт, слід використовувати викрутку. Існує багато ситуацій, коли структура відкритого поля є саме правильним інструментом для роботи. Між іншим, набагато менше, де структура з властивостями get / set - це справді правильний інструмент (у більшості випадків, коли ...
supercat

6

Як щодо створення класу builder всередині вашого класу даних. Клас даних матиме всі налаштування як приватні, і лише їх може встановити тільки розробник.

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 class Builder
        {
            DoSomeActionParameters obj = new DoSomeActionParameters();

            public string A
            {
                set { obj.A = value; }
            }
            public string B
            {
                set { obj.B = value; }
            }
            public DateTime C
            {
                set { obj.C = value; }
            }
            public OtherEnum D
            {
                set { obj.D = value; }
            }
            public string E
            {
                set { obj.E = value; }
            }
            public string F
            {
                set { obj.F = value; }
            }

            public DoSomeActionParameters Build()
            {
                return obj;
            }
        }
    }

    public class Example
    {

        private void DoSth()
        {
            var data = new DoSomeActionParameters.Builder()
            {
                A = "",
                B = "",
                C = DateTime.Now,
                D = testc,
                E = "",
                F = ""
            }.Build();
        }
    }

1
+1 Це цілком правильне рішення, але я думаю, що це занадто багато лісів для підтримки такого простого дизайнерського рішення. Особливо, коли рішення "структура лише для читання" є "дуже" близькою до ідеальної.
Седат Капаноглу

2
Як це вирішує проблему "занадто багато параметрів"? Синтаксис може бути різним, але проблема виглядає однаково. Це не критика, мені просто цікаво, бо я не знайомий з цією схемою.
alexD

1
@alexD це вирішує проблему з надто великою кількістю параметрів функції та збереженням об'єкта незмінним. Лише клас будівельника може встановлювати приватні властивості, і як тільки ви отримаєте об'єкт параметрів, ви не можете його змінити. Проблема полягає в тому, що вона потребує великого коду лісу.
березня

5
Об'єкт параметра у вашому рішенні не є незмінним. Хто зберігає будівельника, може редагувати параметри навіть після побудови
astef

1
Для точки зору @ astef, оскільки в даний час написаний DoSomeActionParameters.Builderекземпляр, можна використовувати для створення та налаштування саме одного DoSomeActionParametersекземпляра. Після виклику Build()наступних змін Builderвластивостей 's продовжуватимуть змінювати властивості оригінального DoSomeActionParameters екземпляра, а наступні виклики до Build()продовжуватимуть повертати той самий DoSomeActionParametersекземпляр. Це справді повинно бути public DoSomeActionParameters Build() { var oldObj = obj; obj = new DoSomeActionParameters(); return oldObj; }.
BACON

6

Я не програміст на C #, але я вважаю, що C # підтримує названі аргументи: (F # так і C # є значною мірою функцією, порівнянною для подібних речей) Це: http://msdn.microsoft.com/en-us/library/dd264739 .aspx # Y342

Отже, виклик вашого оригінального коду стає:

public ResultEnum DoSomeAction( 
 e:"bar", 
 a: "foo", 
 c: today(), 
 b:"sad", 
 d: Red,
 f:"penguins")

це не займає більше місця / думки, що ваш об’єкт створює і має всі переваги, того, що ви взагалі не змінили те, що відбувається в неповноцінній системі. Вам навіть не потрібно нічого перекодувати, щоб вказати, що аргументи названі

Редагувати: ось про це я знайшов артистичний. http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ Я повинен зазначити, що C # 4.0 підтримує названі аргументи, 3.0 не


Справді, поліпшення. Але це вирішує лише читабельність коду, і це робиться лише тоді, коли розробник відмовився від використання названих параметрів. Можна легко забути вказувати імена та надавати переваги. Це не допомагає при написанні самого коду. наприклад, при рефакторингу функції на менші і передайте дані навколо в одному пакеті, що міститься.
Седат Капаноглу

6

Чому б просто не зробити інтерфейс, який застосовує незмінність (тобто лише гетерів)?

По суті це ваше перше рішення, але ви змушуєте функцію використовувати інтерфейс для доступу до параметра.

public interface IDoSomeActionParameters
{
    string A { get; }
    string B { get; }
    DateTime C { get; }
    OtherEnum D { get; }
    string E { get; }
    string F { get; }              
}

public class DoSomeActionParameters: IDoSomeActionParameters
{
    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 ResultEnum DoSomeAction(IDoSomeActionParameters parameters, out Guid code)

Плюси:

  • Не має проблеми з простором стеку, як struct рішення
  • Природне рішення з використанням мовної семантики
  • Незмінність очевидна
  • Гнучка (споживач може використовувати інший клас, якщо хоче)

Мінуси:

  • Деякі повторювані роботи (однакові декларації у двох різних об'єктах)
  • Розробник повинен здогадатися, що DoSomeActionParametersце клас, до якого можна було б відобразити картуIDoSomeActionParameters

+1 Я не знаю, чому я про це не думав? :) Я думаю, що я думав, що об'єкт все ще постраждає від конструктора з занадто великою кількістю параметрів, але це не так. Так, це теж дуже вірне рішення. Єдина проблема, про яку я можу подумати - це те, що користувачеві API не дуже просто знайти правильне ім'я класу, яке підтримує даний інтерфейс. Рішення, яке потребує найменшої документації, краще.
Седат Капаноглу

Мені це подобається, дублювання та знання карти обробляються переробкою, і я можу надати значення за замовчуванням, використовуючи конструктор за замовчуванням конкретного класу
Ентоні Джонстон,

2
Мені не подобається такий підхід. Хтось, хто має посилання на довільну реалізацію IDoSomeActionParametersта хоче зафіксувати в ній значення, не може знати, чи достатньо буде мати посилання, чи він повинен скопіювати значення на якийсь інший об'єкт. Прочитані інтерфейси корисні в деяких контекстах, але не є засобом зробити речі непорушними.
supercat

"Параметри IDoSomeActionParameters" можна передати на DoSomeActionParameters та змінити. Розробник може навіть не усвідомлювати, що вони обходять спробу зробити параметри непорушними
Джозеф Сімпсон

3

Я знаю, що це давнє запитання, але я думав, що проникнуся зі своєю пропозицією, оскільки мені просто довелося вирішити ту саму проблему. Тепер, звичайно, моя проблема трохи відрізняється від вашої, оскільки у мене з'явилася додаткова вимога не бажати, щоб користувачі могли самостійно конструювати цей об’єкт (вся гідратація даних надходила з бази даних, тому я міг позбутися всієї конструкції всередині). Це дозволило мені використовувати приватний конструктор та наступну схему;

    public class ExampleClass
    {
        //create properties like this...
        private readonly int _exampleProperty;
        public int ExampleProperty { get { return _exampleProperty; } }

        //Private constructor, prohibiting construction outside of this class
        private ExampleClass(ExampleClassParams parameters)
        {                
            _exampleProperty = parameters.ExampleProperty;
            //and so on... 
        }

        //The object returned from here will be immutable
        public ExampleClass GetFromDatabase(DBConnection conn, int id)
        {
            //do database stuff here (ommitted from example)
            ExampleClassParams parameters = new ExampleClassParams()
            {
                ExampleProperty = 1,
                ExampleProperty2 = 2
            };

            //Danger here as parameters object is mutable

            return new ExampleClass(parameters);    

            //Danger is now over ;)
        }

        //Private struct representing the parameters, nested within class that uses it.
        //This is mutable, but the fact that it is private means that all potential 
        //"damage" is limited to this class only.
        private struct ExampleClassParams
        {
            public int ExampleProperty { get; set; }
            public int AnotherExampleProperty { get; set; }
            public int ExampleProperty2 { get; set; }
            public int AnotherExampleProperty2 { get; set; }
            public int ExampleProperty3 { get; set; }
            public int AnotherExampleProperty3 { get; set; }
            public int ExampleProperty4 { get; set; }
            public int AnotherExampleProperty4 { get; set; } 
        }
    }

2

Ви можете використовувати підхід у стилі будівельника, хоча залежно від складності вашого 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);

Ах, Марто побив мене на пропозицію будівельника!
Кріс Уайт

2

Окрім меншого відгуку - ви також можете розділити одну операцію на кілька менших. Порівняйте:

 BOOL WINAPI CreateProcess(
   __in_opt     LPCTSTR lpApplicationName,
   __inout_opt  LPTSTR lpCommandLine,
   __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
   __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
   __in         BOOL bInheritHandles,
   __in         DWORD dwCreationFlags,
   __in_opt     LPVOID lpEnvironment,
   __in_opt     LPCTSTR lpCurrentDirectory,
   __in         LPSTARTUPINFO lpStartupInfo,
   __out        LPPROCESS_INFORMATION lpProcessInformation
 );

і

 pid_t fork()
 int execvpe(const char *file, char *const argv[], char *const envp[])
 ...

Для тих, хто не знає POSIX, створення дитини може бути таким же простим, як:

pid_t child = fork();
if (child == 0) {
    execl("/bin/echo", "Hello world from child", NULL);
} else if (child != 0) {
    handle_error();
}

Кожен вибір дизайну представляє компроміс щодо того, які операції він може робити.

PS. Так - це схоже на програму будівельника - лише в зворотному порядку (тобто на стороні виклику замість виклику). У цьому конкретному випадку це може бути, а може і не бути кращим, ніж будівельник.


Це невелика операція, але це хороша рекомендація для тих, хто порушує принцип єдиної відповідальності. Моє запитання виникає відразу після того, як були зроблені всі інші вдосконалення дизайну.
Седат Капаноглу

Підйоми. Вибачте - я підготував запитання перед вашою редагуванням і опублікував його пізніше - отже, я його не помітив.
Maciej Piechotka

2

ось дещо інший від Майкі, але те, що я намагаюся зробити, це зробити так, щоб вся справа була написана якомога менше

public class DoSomeActionParameters
{
    readonly string _a;
    readonly int _b;

    public string A { get { return _a; } }

    public int B{ get { return _b; } }

    DoSomeActionParameters(Initializer data)
    {
        _a = data.A;
        _b = data.B;
    }

    public class Initializer
    {
        public Initializer()
        {
            A = "(unknown)";
            B = 88;
        }

        public string A { get; set; }
        public int B { get; set; }

        public DoSomeActionParameters Create()
        {
            return new DoSomeActionParameters(this);
        }
    }
}

DoSomeActionParameters є незмінним, яким він може бути, і не може бути створений безпосередньо, оскільки його конструктор за замовчуванням є приватним

Ініціалізатор не є незмінним, а лише транспортом

Використання використовує переваги ініціалізатора на Initializer (якщо ви отримаєте мій дрейф). І я можу мати за замовчуванням конструктор за замовчуванням Initializer

DoSomeAction(new DoSomeActionParameters.Initializer
            {
                A = "Hello",
                B = 42
            }
            .Create());

Тут параметри будуть необов’язковими, якщо ви хочете, щоб вони були потрібні, ви можете помістити їх у конструктор за замовчуванням Initializer

І перевірка може пройти методом Create

public class Initializer
{
    public Initializer(int b)
    {
        A = "(unknown)";
        B = b;
    }

    public string A { get; set; }
    public int B { get; private set; }

    public DoSomeActionParameters Create()
    {
        if (B < 50) throw new ArgumentOutOfRangeException("B");

        return new DoSomeActionParameters(this);
    }
}

Тож тепер це схоже

DoSomeAction(new DoSomeActionParameters.Initializer
            (b: 42)
            {
                A = "Hello"
            }
            .Create());

Ще трохи коокі я знаю, але все одно спробую

Редагувати: переміщення методу створення до статичного в об'єкті параметрів і додавання делегата, який передає ініціалізатор, виводить частину коокієнності з виклику

public class DoSomeActionParameters
{
    readonly string _a;
    readonly int _b;

    public string A { get { return _a; } }
    public int B{ get { return _b; } }

    DoSomeActionParameters(Initializer data)
    {
        _a = data.A;
        _b = data.B;
    }

    public class Initializer
    {
        public Initializer()
        {
            A = "(unknown)";
            B = 88;
        }

        public string A { get; set; }
        public int B { get; set; }
    }

    public static DoSomeActionParameters Create(Action<Initializer> assign)
    {
        var i = new Initializer();
        assign(i)

        return new DoSomeActionParameters(i);
    }
}

Тож дзвінок зараз виглядає приблизно так

DoSomeAction(
        DoSomeActionParameters.Create(
            i => {
                i.A = "Hello";
            })
        );

1

Використовуйте структуру, але замість публічних полів використовуйте загальнодоступні властивості:

• Усі (включаючи FXCop та Jon Skeet) погоджуються, що оголення публічних полів є поганим.

Jon і FXCop будуть задоволені, оскільки ви виставляєте власне не поля.

• Ерік Ліпперт та ін стверджують, що покладатися на полі, які читаються лише для незмінності, є брехнею.

Еріку буде задоволено, оскільки, використовуючи властивості, ви можете переконатися, що значення встановлено лише один раз.

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

Один напівнезмінний об'єкт (значення можна встановити, але не змінити). Працює для значень та типів посилань.


+1 Можливо, приватні поля лише для читання та загальнодоступні властивості лише для збирання можуть бути об'єднані в структуру, щоб отримати менш детальне рішення.
Седат Капаноглу

Публічні властивості для читання і запису на структурах, що мутують this, набагато більше зла, ніж публічні поля. Я не впевнений, чому ви вважаєте, що лише встановлення значення робить один раз «нормальним»; якщо починати з екземпляра структури за замовчуванням, проблеми, пов’язані з thisвластивостями, що змінюються, все ще будуть існувати.
supercat

0

Варіант відповіді Самуїла, який я використовував у своєму проекті, коли в мене була така ж проблема:

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();

У моєму випадку параметри були навмисно модифіковані, оскільки методи встановлення не допускали всіх можливих комбінацій, а просто викривали їх загальні комбінації. Це тому, що деякі мої параметри були досить складними, а методи написання для всіх можливих випадків були б важкими та непотрібними (шалені комбінації рідко використовуються).


Це клас, що змінюється незалежно.
Седат Капаноглу

@ssg - Ну так, так. У DoMagicдоговорі є, хоча він не змінює об'єкт. Але я думаю, що це не захищає від випадкових модифікацій.
Vilx-
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.