Когда метод класса должен возвращать тот же экземпляр после изменения самого себя?

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

Тот факт, что каждый является ссылкой на текущий экземпляр, а другой создает новый экземпляр, не меняет того, что это цепные методы.

Не существует золотого правила о том, что цепочечный метод должен ссылаться только на текущий объект. Infact, цепные методы могут быть в двух разных классах. Например;

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с другими методами.

Reactgular
источник
4
Я действительно не понимаю, почему вы рекомендуете цепочку методов, зависящую исключительно от поддержки автозаполнения, подобной Intellisense. Цепочки методов или плавные интерфейсы являются шаблоном разработки API для любого языка ООП; единственное, что делает автозаполнение, это предотвращает опечатки и ошибки типа.
Амон
@ Возможно, ты неправильно понял, что я сказал. Я сказал, что это более популярно, когда intellisense является общим для языка. Я никогда не говорил, что это зависит от функции.
Reactgular
3
Хотя этот ответ мне очень помогает понять возможности цепочки, мне все еще не хватает решения, когда вообще использовать цепочку. Конечно, последовательность является лучшей . Однако я ссылаюсь на случай, когда я изменяю свой объект в функции и return this. Как я могу помочь пользователям моих библиотек разобраться и понять эту ситуацию? (Позволить им связывать методы, даже если это не требуется, будет ли это действительно хорошо? Или я должен придерживаться только одного способа, требующего изменения вообще?)
Мартин Браун
@modiX, если мой ответ правильный, тогда, пожалуйста, примите его как ответ. Другая вещь, которую вы ищете, это мнения о том, как использовать цепочку, и нет правильного / неправильного ответа на это. Это действительно решать вам. Может быть, вы слишком беспокоитесь о мелких деталях?
Reactgular
3

Эти методы изменяют собственный экземпляр.

В зависимости от языка наличие методов, которые возвращают void/ unitи изменяют их экземпляр или параметры, не является идиоматическим. Даже в языках, которые раньше делали это больше (C #, C ++), это выходит из моды с переходом к программированию более функциональных стилей (неизменяемые объекты, чистые функции). Но давайте предположим, что сейчас есть веская причина.

Будет ли это единственной причиной для этого?

Для определенного поведения (думаю x++) ожидается, что операция возвращает результат, даже если она изменяет переменную. Но это во многом единственная причина сделать это самостоятельно.

Можно ли изменить собственный экземпляр и вернуть его? Или он должен только вернуть копию и оставить исходный объект, как раньше? Потому что при возврате того же измененного экземпляра пользователь может признать, что возвращаемое значение является копией, иначе оно не будет возвращено? Если все в порядке, каков наилучший способ прояснить такие вещи в методе?

Это зависит.

В тех языках, где copy / new и return распространены (C # LINQ и строки), возврат одной и той же ссылки может привести к путанице. В тех языках, где модификация и возврат распространены (некоторые библиотеки C ++), копирование может привести к путанице.

Делать подпись однозначной (возвращая voidили используя языковые конструкции, такие как свойства) было бы лучшим способом прояснить. После этого сделать имя понятным, SetFooчтобы показать, что вы модифицируете существующий экземпляр - это хорошо. Но ключ в том, чтобы поддерживать идиомы языка / библиотеки, с которой вы работаете.

Telastyn
источник
Спасибо за ваш ответ. В основном я работаю с платформой .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

(Я принял C ++ в качестве вашего языка программирования)

Для меня это в основном удобочитаемость. Если 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 )

Adriani
источник
1
Вы ошиблись, это C #. Однако я хочу, чтобы мой вопрос был более распространенным.
Мартин Браун
Конечно, мог бы быть и Java, но это был незначительный момент, чтобы просто поместить мой пример с операторами в контекст. Суть заключалась в том, что все сводится к удобочитаемости, на которую влияют ожидания и фон читателя, на которые сами влияют обычные практики в конкретном языке / системах, с которыми приходится иметь дело - как также указывали другие ответы.
AdrianI
0

Вы также можете сделать цепочку методов с копией return вместо модификации return.

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

Питер Воне
источник
Да, я знаю. Но я не хотел ссылаться на этот случай, так как я должен использовать этот случай, когда я в любом случае не хочу редактировать собственный экземпляр. Тем не менее, спасибо за указание на это.
Мартин Браун
По другим ответам важна последовательность семантики. Так как вы можете быть согласны с возвратом копии, и вы не можете быть согласованным с возвратом модификации (потому что вы не можете сделать это с неизменяемыми объектами), тогда я бы сказал, что ответ на ваш вопрос - не использовать возврат копии.
Питер Воне