Ответ 1
Стиль "вернуть этот" иногда называют свободным интерфейсом и является распространенной практикой.
У меня есть класс, который я должен вызывать один или два метода много раз друг за другом. В настоящее время методы возвращают void
. Я думал, было бы лучше, если бы он вернулся this
, чтобы методы могли быть вложенными? или это очень важно? или если это плохо, было бы лучше, если бы он вернул новый объект того же типа? Или что вы думаете? В качестве примера я создал три версии класса сумматора:
// Regular
class Adder
{
public Adder() { Number = 0; }
public int Number { get; private set; }
public void Add(int i) { Number += i; }
public void Remove(int i) { Number -= i; }
}
// Returning this
class Adder
{
public Adder() { Number = 0; }
public int Number { get; private set; }
public Adder Add(int i) { Number += i; return this; }
public Adder Remove(int i) { Number -= i; return this; }
}
// Returning new
class Adder
{
public Adder() : this(0) { }
private Adder(int i) { Number = i; }
public int Number { get; private set; }
public Adder Add(int i) { return new Adder(Number + i); }
public Adder Remove(int i) { return new Adder(Number - i); }
}
Первый может быть использован следующим образом:
var a = new Adder();
a.Add(4);
a.Remove(1);
a.Add(7);
a.Remove(3);
Другие два могут быть использованы следующим образом:
var a = new Adder()
.Add(4)
.Remove(1)
.Add(7)
.Remove(3);
Если единственное отличие состоит в том, что a
в первом случае это new Adder()
, а во втором - результат последнего метода.
Первое, что я нахожу, быстро становится... раздражающим писать снова и снова. Поэтому я хотел бы использовать одну из других версий.
Третий работает подобно многим другим методам, таким как многие методы String и IEnumerable методы расширения. Я полагаю, что у нее есть положительная сторона в том, что вы можете делать такие вещи, как var a = new Adder(); var b = a.Add(5);
, а затем иметь один, который был 0, и тот, который был 5. Но в то же время не так ли дорого создавать новые объекты все время? И когда первый объект умрет? Когда первый метод возвращает вид? Или?
В любом случае мне нравится тот, который возвращает this
, и думаю, что я буду использовать это, но мне очень любопытно узнать, что другие думают об этом случае. И то, что считается лучшей практикой.
Стиль "вернуть этот" иногда называют свободным интерфейсом и является распространенной практикой.
Мне нравится "свободный синтаксис" и займет второе. В конце концов, вы все равно можете использовать его как первый, для людей, которые чувствуют себя некомфортно с плавным синтаксисом.
еще одна идея сделать интерфейс, такой как сумматоры, более простым в использовании:
public Adder Add(params int[] i) { /* ... */ }
public Adder Remove(params int[] i) { /* ... */ }
Adder adder = new Adder()
.Add(1, 2, 3)
.Remove(3, 4);
Я всегда стараюсь делать короткие и удобные для чтения интерфейсы, но многим нравится писать код как можно более сложным.
Цепочки - это хорошая вещь, чтобы иметь и ядро в некоторых фреймворках (например, расширения Linq и jQuery используют его сильно).
Создаете ли вы новый объект или return this
зависит от того, как вы ожидаете, что ваш исходный объект будет вести себя:
var a = new Adder();
var b = a.Add(4)
.Remove(1)
.Add(7)
.Remove(3);
//now - should a==b ?
Цепочка в jQuery изменит ваш исходный объект - он вернул это. Это ожидаемое поведение - в противном случае будет клонировать элементы пользовательского интерфейса.
Цепочки в Linq оставят неизмененную исходную коллекцию. Это тоже ожидаемое поведение - каждая связанная функция является фильтром или преобразованием, а исходная коллекция часто неизменно.
Какой шаблон лучше подходит для того, что вы делаете?
Учтите это: если вы вернетесь к этому коду через 5 лет, это будет иметь для вас смысл? Если да, тогда, я полагаю, вы можете идти вперед.
Для этого конкретного примера, однако, казалось бы, что перегрузка операторов +
и -
сделает вещи более ясными и выполнит одно и то же.
В вашем конкретном случае перегрузка арифметических операторов, вероятно, будет лучшим решением.
Возвращение this
(Свободный интерфейс) является обычной практикой для создания выражений - модульное тестирование и насмешливые фреймворки используют это много. Fluent Hibernate - еще один пример.
Возвращение нового экземпляра также может быть хорошим выбором. Это позволяет сделать ваш класс неизменным - в общем, хорошо и очень удобно в случае многопоточности. Но подумайте о накладных расходах объектов, если неизменяемость для вас бесполезна.
Если вы называете это Adder, я бы вернулся с этим. Тем не менее, это довольно странно, если класс Adder содержит ответ.
Возможно, вы захотите сделать что-то вроде MyNumber и создать метод Add().
В идеале (IMHO), который не изменит число, которое хранится внутри вашего экземпляра, но создайте новый экземпляр с новым значением, которое вы вернете:
class MyNumber
{
...
MyNumber Add( int i )
{
return new MyNumber( this.Value + i );
}
}
Я думаю, что для простых интерфейсов "свободный" интерфейс очень полезен, особенно потому, что его очень просто реализовать. Значение свободного интерфейса заключается в том, что он устраняет много постороннего пуха, который мешает пониманию. Разработка такого интерфейса может занять много времени, особенно когда интерфейс начинает участвовать. Вам следует беспокоиться о том, как использование интерфейса "читает"; На мой взгляд, наиболее привлекательным для такого интерфейса является то, как он передает намерение программиста, а не количество символов, которое он сохраняет.
Чтобы ответить на ваш конкретный вопрос, мне нравится стиль "вернуть этот". Мое типичное использование свободного интерфейса - это определение набора параметров. То есть я создаю экземпляр класса, а затем использую быстрые методы в экземпляре для определения желаемого поведения объекта. Если у меня есть опция yes/no (скажем, для ведения журнала), я стараюсь не иметь метод setLogging (bool state), а два метода "WithLogging" и "WithoutLogging". Это несколько больше, но ясность конечного результата очень полезна.
Основное различие между вторым и третьим решением заключается в том, что, возвращая вместо него новый экземпляр, вы можете "поймать" объект в определенном состоянии и продолжить с него.
var a = новый Adder() .Add(4);
var b = a.Remove(1);
var c = a.Add(7) .Remove(3);
В этом случае оба b и c имеют состояние, зафиксированное в как отправную точку. Я наткнулся на эту идиому, когда читал о шаблоне для создания объектов тестового домена в Растущее объектно-ориентированное программное обеспечение, руководствуясь испытаниями Стива Фримена; Nat Pryce.
По вопросу о времени жизни ваших экземпляров: я бы подумал, что они будут понятными для сбора мусора, как только вызывается обращение к Remove или Add.