C #: возвращая 'this' для вложения метода? - PullRequest
13 голосов
/ 23 апреля 2009

У меня есть класс, который я должен вызывать один или два метода много раз друг за другом. Методы в настоящее время возвращают 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, и я думаю, что буду его использовать, но мне очень любопытно узнать, что другие думают об этом случае. И что считается лучшей практикой.

Ответы [ 8 ]

19 голосов
/ 23 апреля 2009

Стиль 'return this' иногда называют плавный интерфейс и является обычной практикой.

6 голосов
/ 23 апреля 2009

Мне нравится "свободный синтаксис" и я бы выбрал второй. В конце концов, вы все равно можете использовать его в качестве первого для людей, которые чувствуют себя неуютно из-за свободного синтаксиса.

еще одна идея сделать интерфейс, подобный сумматорам, более простым в использовании:

public Adder Add(params int[] i) { /* ... */ }
public Adder Remove(params int[] i) { /* ... */ }

Adder adder = new Adder()
  .Add(1, 2, 3)
  .Remove(3, 4);

Я всегда стараюсь создавать короткие и легкие для чтения интерфейсы, но многим нравится писать код как можно более сложным.

2 голосов
/ 23 апреля 2009

Цепочка - это хорошая вещь, которая есть в некоторых средах (например, расширения Linq и jQuery активно ее используют).

Создание нового объекта или return this зависит от того, как вы ожидаете, что ваш исходный объект будет вести себя:

var a = new Adder();

var b = a.Add(4)
         .Remove(1)
         .Add(7)
         .Remove(3);

//now - should a==b ?

Цепочка в jQuery изменит ваш исходный объект - он вернул это. Это ожидаемое поведение - в противном случае, в принципе, будут клонированы элементы пользовательского интерфейса.

Цепочка в Линке оставит вашу оригинальную коллекцию без изменений. Это тоже ожидаемое поведение - каждая связанная функция является фильтром или преобразованием, а исходная коллекция часто неизменна.

Какой шаблон лучше подходит для того, что вы делаете?

0 голосов
/ 23 апреля 2009

Основное различие между вторым и третьим решением состоит в том, что, возвращая новый экземпляр вместо этого, вы можете "поймать" объект в определенном состоянии и продолжить с этого.

var a = new Adder () .Add (4);

var b = a.Удалить (1);

var c = a.Add (7) .Remove (3);

В этом случае и b, и c имеют захваченное состояние в качестве отправной точки. Я столкнулся с этой идиомой, читая о шаблоне для создания объектов тестовой области в Растущем объектно-ориентированном программном обеспечении, руководствуясь тестами Стива Фримена; Нат Прайс .

На ваш вопрос, касающийся времени жизни ваших экземпляров: я ожидаю, что они пригодятся для сборки мусора, как только вызовы Remove или Add будут возвращены.

0 голосов
/ 23 апреля 2009

Я думаю, что для простых интерфейсов «плавный» интерфейс очень полезен, особенно потому, что он очень прост в реализации. Ценность свободного интерфейса в том, что он устраняет много постороннего пуха, который мешает пониманию. Разработка такого интерфейса может занять много времени, особенно когда интерфейс начинает участвовать. Вы должны беспокоиться о том, как использование интерфейса «читает»; На мой взгляд, наиболее убедительным использованием такого интерфейса является то, как он передает намерение программиста, а не количество сохраняемых символов.

Чтобы ответить на ваш конкретный вопрос, мне нравится стиль «верни это». Мое типичное использование беглого интерфейса - определить набор опций. То есть я создаю экземпляр класса и затем использую беглые методы в этом экземпляре, чтобы определить желаемое поведение объекта. Если у меня есть опция да / нет (скажем, для ведения журнала), я стараюсь не использовать метод setLogging (bool state), а два метода «WithLogging» и «WithoutLogging». Это немного больше работы, но ясность конечного результата очень полезна.

0 голосов
/ 23 апреля 2009

Если бы вы назвали это Adder, я бы пошел с возвращением этого. Однако для класса Adder довольно странно содержать ответ.

Возможно, вы захотите сделать что-то вроде MyNumber и создать метод Add ().

В идеале (ИМХО) это не изменит число, хранящееся в вашем экземпляре, но создаст новый экземпляр с новым значением, которое вы вернете:

class MyNumber
{
    ...

    MyNumber Add( int i )
    {
        return new MyNumber( this.Value + i );
    }
}
0 голосов
/ 23 апреля 2009
  1. В вашем конкретном случае перегрузка арифметических операторов, вероятно, будет лучшим решением.

  2. Возвращение this (интерфейс Fluent) является обычной практикой для создания выражений - модульное тестирование и фреймворки часто используют это. Свободный Hibernate является еще одним примером.

  3. Возвращение нового экземпляра также может быть хорошим выбором. Это позволяет сделать ваш класс неизменным - в общем, хорошая вещь и очень удобная в случае многопоточности. Но подумайте о накладных расходах на создание объекта, если неизменность для вас бесполезна.

0 голосов
/ 23 апреля 2009

Подумайте об этом: если вы вернетесь к этому коду через 5 лет, будет ли это иметь смысл для вас? Если так, то, я полагаю, вы можете идти вперед.

Однако для этого конкретного примера может показаться, что перегрузка операторов + и - прояснит ситуацию и сделает то же самое.

...