Коли метод класу повинен повернути той самий екземпляр після зміни себе?


9

У мене є клас, який має три методи A(), B()і C(). Ці методи змінюють власний екземпляр.

Хоча методи повинні повертати екземпляр, коли екземпляр є окремою копією (так само Clone()), я отримав вільний вибір повернути voidабо той самий екземпляр ( return this;), коли змінював той самий екземпляр у методі і не повертав жодного іншого значення.

Вирішуючи повернути той самий модифікований екземпляр, я можу робити акуратні ланцюги методів, як obj.A().B().C();.

Чи це буде єдиною причиною цього?

Чи навіть добре змінювати власний екземпляр і повертати його? Або він повинен лише повернути копію та залишити оригінальний об’єкт, як раніше? Тому що, повертаючи той самий модифікований екземпляр, користувач, можливо, вважатиме, що повернене значення є копією, інакше воно не буде повернене? Якщо це нормально, який найкращий спосіб з’ясувати такі речі методом?

Відповіді:


7

Коли використовувати ланцюжок

Функціонування ланцюга в основному популярне серед мов, де IDE з автоматичним завершенням є загальним місцем. Наприклад, майже всі розробники C # використовують Visual Studio. Тому, якщо ви розробляєте додавання ланцюга на C # до своїх методів, це може заощадити час для користувачів цього класу, оскільки Visual Studio допоможе вам у створенні ланцюга.

З іншого боку, такі мови, як PHP, які мають дуже динамічний характер і часто не мають автоматичної повної підтримки в IDE, побачать менше класів, які підтримують ланцюжок. Ланцюг буде доречним лише тоді, коли для розкриття можливих методів будуть використані правильні phpDocs.

Що таке ланцюжок?

З огляду на клас, названий, Fooдва наступні способи є можливими.

function what() { return this; }
function when() { return new Foo(this); }

Той факт, що один є посиланням на поточний екземпляр, а інший створює новий екземпляр, не змінюється, що це є можливими методами.

Не існує жодного золотого правила, згідно з яким метод, який можна отримати, повинен посилатися лише на поточний об'єкт. Взаємодія, доступні методи можуть бути в двох різних класах. Наприклад;

class B { function When() { return true; } };
class A { function What() { return new B(); } };

var a = new A();
var x = a.What().When();

Ні thisв якому з наведених прикладів немає посилань . Код a.What().When()є прикладом ланцюжка. Цікаво, що тип класу Bніколи не присвоюється змінній.

Метод є ланцюговим, коли його повернене значення використовується як наступний компонент виразу.

Ось ще кілька прикладів

 // return value never assigned.
 myFile.Open("something.txt").Write("stuff").Close();

// two chains used in expression
int x = a.X().Y() * b.X().Y();

// a chain that creates new strings
string name = str.Substring(1,10).Trim().ToUpperCase();

Коли використовувати thisтаnew(this)

Рядки більшості мов незмінні. Тож виклики методу ланцюга завжди призводять до створення нових рядків. Де як об'єкт на зразок StringBuilder може бути змінений.

Послідовність - найкраща практика.

Якщо у вас є методи, які змінюють стан об'єкта і повертаються this, тоді не змішуйте методи, що повертають нові екземпляри. Натомість створіть специфічний метод, який називається, Clone()який буде робити це явно.

 var x  = a.Foo().Boo().Clone().Foo();

Це набагато зрозуміліше, що відбувається всередині a.

Крок назовні та назад трюк

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

Тимчасовий клас існує лише для надання особливих ознак оригінальному класу, але лише за особливих умов.

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

Ось мій приклад, нехай відомий особливий стан B.

class A {
    function Foo() { return this; }
    function Boo() { return this; }
    function Change() return new B(this); }
}

class B {
    var _a;
    function (A) { _a = A; }
    function What() { return this; }
    function When() { return this; }
    function End() { return _a; }
}

var a = new A();
a.Foo().Change().What().When().End().Boo();

Зараз це дуже простий приклад. Якщо ви хочете мати більше контролю, тоді Bможете повернутися до нового супер-типу, Aякий має різні методи.


4
Я дійсно не розумію, чому ви рекомендуєте ланцюжок методів залежно виключно від підтримки автозаповнення типу Intellisense. Ланцюги методів або вільні інтерфейси - це модель дизайну API для будь-якої мови OOP; єдине, що робить автозаповнення - це запобігання помилок друку та введення тексту.
амон

@amon ти неправильно прочитав те, що я сказав. Я сказав, що це популярніше, коли інтелігенція є загальною для мови. Я ніколи не говорив, що це залежить від функції.
Реакційний

3
Хоча ця відповідь дуже допомагає мені зрозуміти можливості прикування, я все ще не вистачаю рішення, коли взагалі використовувати ланцюжок. Впевненість, консистенція найкраща . Однак я переживаю випадок, коли я змінюю свій об'єкт у функції та return this. Як я можу допомогти користувачам моїх бібліотек впоратися та зрозуміти цю ситуацію? (Дозволити їм ланцюгові методи, навіть коли це не потрібно, чи справді це буде добре? Або я повинен дотримуватися лише одного способу, вимагати змін взагалі?)
Мартін Браун

@modiX, якщо моя відповідь правильна, то прийміть її як відповідь. Інші речі, які ви шукаєте, - це думки щодо використання ланцюга, і на це немає правильної / неправильної відповіді. Це дійсно вирішувати вам. Можливо, ви занадто переживаєте за дрібні деталі?
Реакційний

3

Ці методи змінюють власний екземпляр.

Залежно від мови наявність методів, що повертають void/ unitзмінюють їх примірник або параметри, не є ідіоматичним. Навіть у мовах, які раніше робили це більше (C #, C ++), він виходить з моди з переходом на більш функціональне програмування стилів (незмінні об'єкти, чисті функції). Але припустимо, що зараз є вагома причина.

Чи це буде єдиною причиною цього?

Для певної поведінки (подумайте x++) очікується, що операція поверне результат, навіть не дивлячись на зміну змінної. Але це в значній мірі єдина причина зробити це самостійно.

Чи навіть добре змінювати власний екземпляр і повертати його? Або він повинен лише повернути копію та залишити оригінальний об’єкт, як раніше? Тому що при поверненні одного і того ж модифікованого екземпляра користувач, можливо, визнає, що повернене значення є копією, інакше воно не повернеться? Якщо це нормально, який найкращий спосіб з’ясувати такі речі методом?

Це залежить.

У мовах, де копіювання / нове повернення є загальним (C # LINQ та рядки), то повернення тієї ж посилання було б заплутаним. У мовах, де модифікація та повернення є загальними (деякі бібліотеки C ++), то копіювання було б заплутаним.

Зробити підпис однозначним (шляхом повернення voidабо використання мовних конструкцій на зразок властивостей) було б найкращим способом уточнення. Після цього ясно, щоб ім’я було зрозумілим, SetFooщоб показати, що ви змінюєте існуючий екземпляр, це добре. Але ключовим є збереження ідіом мови / бібліотеки, з якою ви працюєте.


Thx для вашої відповіді. Я в основному працюю з .NET рамкою, і за вашою відповіддю вона сповнена неідіоматичних речей. Хоча такі речі, як методи LINQ, повертають нову копію оригіналу після додавання операцій тощо. Такі речі, як Clear()або Add()будь-який тип колекції, модифікують той самий екземпляр і повертаються void. В обох випадках ви говорите, що це буде заплутано ...
Мартін Браун

@modix - LINQ не повертає копію під час додавання операцій.
Теластин

Чому я можу obj = myCollection.Where(x => x.MyProperty > 0).OrderBy(x => x.MyProperty);тоді робити ? Where()повертає новий об’єкт колекції. OrderBy()навіть повертає інший об’єкт колекції, тому що вміст упорядкований.
Мартін Браун

1
@modix - Вони повертають нові об'єкти , які реалізують IEnumerable, але ясно, що вони не є копіями, і вони не є колекціями (за винятком ToList, ToArrayі т.д.).
Теластин

Це правильно, вони не є копіями, крім того, це справді нові об'єкти. Крім того, кажучи, що колекція я посилався на об'єкти, через які ви можете порахувати (об'єкти, що містять більше одного об'єкта в будь-якому вигляді масиву), а не на класи, які успадковують ICollection. Моє ліжко.
Мартін Браун

0

(Я вважав, що С ++ є вашою мовою програмування)

Для мене це здебільшого аспект читабельності. Якщо A, B, C є модифікаторами, особливо якщо це тимчасовий об'єкт, переданий як параметр деякій функції, наприклад

do_something(
      CPerson('name').age(24).height(160).weight(70)
      );

у порівнянні з

{
   CPerson person('name');
   person.set_age(24);
   person.set_height(160);
   person.set_weight(70);
   do_something(person);
}

Оновлення, якщо це нормально, щоб повернути посилання на модифікований екземпляр, я б сказав так, і вкажу, наприклад, на оператори потоку '>>' та '<<' ( http://www.cprogramming.com/tutorial/ operator_overloading.html )


1
Ви вважали неправильним, це C #. Однак я хочу, щоб моє питання було більш поширеним.
Мартін Браун

Звичайно, могла бути і Java, але це було другорядним моментом лише для того, щоб розмістити мій приклад з операторами в контексті. Суть полягала в тому, що вона зводиться до читабельності, на яку впливають очікування читачів та передумови, на які самі впливають звичні практики у конкретних мовах / рамках, з якими один має справу, - як вказували й інші відповіді.
АдріанI

0

Ви також можете зробити метод, що зв'язує річ із поверненням копії, а не змінювати повернення.

Хорошим прикладом C # є string.Replace (a, b), який не змінює рядок, на який він викликався, але натомість повертає нову рядок, що дозволяє вам весело ланцюг.


Так, я знаю. Але я не хотів посилатися на цей випадок, оскільки мені доводиться використовувати цей випадок, коли я не хочу редагувати власний екземпляр у будь-якому випадку. Однак, дякую, що вказали на це.
Мартін Браун

Для інших відповідей важлива послідовність семантики. Оскільки ви можете узгоджуватись із поверненням копії, і ви не можете узгоджуватись із зміною повернення (тому що ви не можете це зробити із незмінними об'єктами), я б сказав, що відповідь на ваше запитання - не використовувати повернення копії.
Пітер Вун
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.