Каковы лучшие альтернативы геттерам и сеттерам в C #?

Я читал кое-что об использовании геттеров / сеттеров в объектно-ориентированном программировании и многих ресурсах, таких как это говорит о том, что их следует использовать очень редко. Цитата из этой статьи гласит:

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

Хотя в принципе это звучит хорошо, я не совсем уверен, как это лучше всего реализовать. Скажите, что у меня есть класс, описывающий машину. Внутри этого класса допустим, что марка этого автомобиля, хранящегося в классе, - «Тойота». Теперь у меня есть графический интерфейс, который хочет показать, что это «Тойота». Как это сделать без геттера?

Даже если я абстрагируюсь от того, что «Toyota» хранится как String, и верну TextBox с уже записанным в нем «Toyota», это в лучшем случае звучит как средство получения обернутой формы исходного свойства. Я просто подумал, что такие вещи, как TextBoxes, должны быть только в классе GUI, а не во вспомогательных классах, таких как мой класс автомобиля.


person Xantham    schedule 29.08.2012    source источник
comment
Это философия, вы можете зайти слишком далеко. Я никогда не видел статьи, призывающей к такому пуризму объектно-ориентированного дизайна, которая также включает пример с кодом, показывающий, как вы на самом деле сделаете что-то практичное, например, изобразите машину. Тем не менее, философия заслуживает понимания, но если вы замените свойство двумя методами, которые читают и записывают внутреннее поле, чего вы достигли?   -  person Jamie Treworgy    schedule 30.08.2012
comment
Да, я всю эту статью размышлял, когда у них будут примеры кода, но, увы, их не было. В других статьях аналогичного характера были примеры замены сеттеров, но не геттеров.   -  person Xantham    schedule 30.08.2012


Ответы (4)


Вы игнорируете «делать работу». Если работа, которую вы хотите выполнить, - это марка автомобиля, то работу сделает геттер, возвращающий марку.

В статье говорится, что если вы хотите, чтобы работа выполнялась с данными класса, поместите ее в класс. Не вытаскивайте данные из класса, работайте с ними, а затем возвращайте в класс:

var c = new Car();
c.Odometer += 10;  // bad
c.DriveMiles(10);  // good

Я также сомневаюсь, насколько эта статья применима к C #; он утверждает:

Getter and setter methods (also known as accessors) are dangerous for the same reason that public fields are dangerous: They provide external access to implementation details.

В C # это неверно, средство доступа C # может принимать произвольные классы, они не «предоставляют внешний доступ к деталям реализации».

person Dour High Arch    schedule 29.08.2012
comment
Так что, если я правильно вас понимаю, можно использовать геттер, если это то, что я хочу, но не для того, чтобы я мог его получить и работать с ним, кроме отображения или чего-то в этом роде? Итак, если бы у меня было другое поле о модели, у меня не было бы String brandmodel = car.getBrand () + car.getModel (), у меня было бы что-то в моем автомобильном классе, которое делает это для меня, например car.getBrandModel () ? - person Xantham; 30.08.2012
comment
В оригинальной статье Голуба, вероятно, сказано, что у вас car.describeMyself. Проблема в том, что вы заменяете одну проблему другой. Теперь ваша машина должна знать контекст своего описания: для чего это описание нужно? Реклама? Платите за проезд? Налоги? Что-то, о чем вы не подумали? Теперь вам нужна совершенно новая абстракция, которая (в конце концов) будет просто дублировать раскрытие свойств автомобиля. Я думаю, что методы должны делать что-то присущее объекту (вот почему мне нравится этот ответ), но объект не должен иметь или нуждаться в знаниях о том, как он используется. - person Jamie Treworgy; 30.08.2012
comment
Подавляющее большинство свойств - это просто оболочки для полей, которые технически просто предоставляют внешний доступ к деталям реализации ... - person Peter Ritchie; 30.08.2012
comment
Не совсем так, говорит он, как только вы определите int SomeProperty {get; set;} раскрытая вами деталь реализации заключается в том, что SomeProperty (вероятно) хранится как int. Кажется, это более веский аргумент против добавления ненужных аксессоров в статически типизированных языках. - person Tony Hopkinson; 30.08.2012
comment
Также обратите внимание, что C # может иметь средство доступа BrandModel, которое возвращает car.Brand + car.Model, не раскрывая ни Brand, ни Model, может запретить вызывающей стороне изменять их, и которое можно изменить без изменения интерфейса. - person Dour High Arch; 30.08.2012
comment
@DourHighArch Я никогда не понимал значения этого по сравнению с простым выставлением Brand и Model (только для доступа для чтения). Предоставление BrandModel предполагает определенную потребность, которая не является общедоступной. Что, если бы вам пришлось разделить их запятой? Что, если вы хотите изменить порядок? Это проблема пользователей автомобилей, а не автомобилей. Определенно предоставлять методы, специфичные для действий, для изменения свойств как можно чаще, но скрытие свойств от доступа для чтения в пользу широкого набора контекстно-зависимых методов для меня не имеет смысла. BrandModel должен быть опорой объекта CarReport (или ничего). - person Jamie Treworgy; 30.08.2012

В этой статье описывается, что вы должны использовать методы вместо средств получения / установки свойств. По сути, это предполагает, что метод должен выполнять действие для изменения состояния вместо того, чтобы разрешать установщику изменять состояние. например У меня может быть класс со свойством Месяц и День. Тогда у меня мог бы быть код, который делает это:

obj.Day = 28;
obj.Month = Months.February;
obj.Day = 30;
obj.Month = Months.March;

Как только я установил день на 30, а месяц - февраль, у меня недействительное состояние.

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

obj.ChangeDate(Months.March, 30);

Если аналогия даты и времени сбивает с толку из-за DateTime, у вас может быть аналогичный пример с координатами широты и долготы:

obj.Latitude = 45.4112;
obj.Longitude = -75.6981;
//... 
obj.Latitude = 39.73;
obj.Longitude = -86.27;

Когда для широты установлено значение 39,73, местоположение становится местом рядом с Ньюарком, а не с Оттавой (первая широта / долгота) или Индианаполисом (вторая широта / долгота). Если оставить настройки для lat / long, интерфейс останется открытым, и он не сможет проверить его инварианты, а некоторые могут сказать, что он не совсем объектно-ориентированный. Итак, вместо этого у вас может быть метод:

obj.MoveTo(39.73, -86.27);

ОО подразумевает как инкапсулированное состояние, так и поведение. Хотя некоторым может казаться, что свойства «инкапсулируют» состояние, обычно это не так. Они отделяют реализацию от интерфейса, так что то, что используется в качестве резервного хранилища свойств, может измениться, но это редко когда-либо; поэтому свойства просто предоставляют доступ к деталям реализации иначе, чем предоставляют общедоступные поля.

Существуют такие концепции, как разделение командных запросов, основанные на этой идее, которые предполагают, что интерфейс должен либо обновлять состояние, либо состояние запроса, но не то и другое вместе. Свойства с геттерами и сеттерами предоставляют интерфейс, который обновляет состояние и состояние запроса. Приведенные выше примеры MoveTo и ChangeDate будут «командами». По идее, вы должны предоставить другие способы запроса состояния; если это необходимо.

Еще одна проблема со свойствами заключается в том, что люди обычно добавляют как геттеры, так и сеттеры просто потому, что это свойство. Возможность получать или устанавливать свойство может не быть необходимой, и простое добавление как получателя, так и сеттера путем механического создания создает интерфейс, требующий тестирования и обслуживания, которые могут никогда не понадобиться.

Тонкий? Конечно; но он говорит об объектно-ориентированном дизайне, а не о коде, который «просто работает».

person Peter Ritchie    schedule 29.08.2012
comment
Вы также можете просто иметь свойство DateTime, которое является объектом, который инкапсулирует ограничения действительной даты. - person Jamie Treworgy; 30.08.2012
comment
Это имеет смысл для сеттера, что вы могли бы абстрагироваться от него, а также превратить ваши 2 строки в 2 аргумента в 1 строке. Однако как насчет использования этой философии для геттеров? - person Xantham; 30.08.2012
comment
Да, это пример ... Я бы никогда не использовал datetime, но это простой концептуальный пример ... - person Peter Ritchie; 30.08.2012
comment
Если он утверждает это, а я не думаю, что это так, то это будет полная чушь, аксессоры - всего лишь синтаксический сахар. - person Tony Hopkinson; 30.08.2012
comment
@TonyHopkinson O-O имеет поведение и инкапсулированное состояние. Считаете ли вы, что класс, имеющий только средства получения / установки свойств, обеспечивает поведение? - person Peter Ritchie; 30.08.2012
comment
@PeterRitchie. Зависит от того, открывают ли геттер и сеттер только внутреннее состояние. Нет никакой разницы в вопросе, поставленном OP между set {if value! = _Member {_member = value; DoMemberChanged ();}} и SetMember (int value) {if value! = _Member {_member = value; DoMemberChanged ();}}. Синтаксический сахар ... - person Tony Hopkinson; 31.08.2012
comment
@TonyHopkinson Вот почему я упомянул, что они отделяют реализацию от интерфейса, так что то, что используется в качестве резервного хранилища свойств, может измениться - но это происходит редко; поэтому свойства просто предоставляют доступ к деталям реализации иначе, чем предоставляют общедоступные поля. Да, они могут инкапсулировать личные данные, но обычно этого не делают. - person Peter Ritchie; 31.08.2012
comment
@PeterRitchie Это один из тех дизайнерских компромиссов, на которые мы идем каждый день. Большинство моих форм имеют дело с методами доступа к классу данных, но когда было очевидное преимущество, я передал интерфейс, реализованный формой, классу данных. В среде с динамической типизацией это соотношение, вероятно, будет обратным. - person Tony Hopkinson; 31.08.2012
comment
@TonyHopkinson Конечно, и статьи, посвященные идеям / предложениям / дизайнам и т. Д. обычно имеют дело только с концепцией; они не касаются того, как и где его следует применять. Есть определенные аспекты приложения, которые лучше обслуживаются более или полностью объектно-ориентированными, а некоторые нет. Классы данных или DTO обычно не имеют поведения для начала, поэтому навязывание его там, где оно не принадлежит, вероятно, так же плохо, как и то, что другой подразумевал в своем сценарии. Я, конечно же, не понял из статьи все вещи против собственности ... - person Peter Ritchie; 31.08.2012
comment
Подразумевается, что там, где у вас есть поведение, предоставление свойств для всего обходит поведение, которое вы пытаетесь обеспечить. И хотя предоставление свойств для всех ваших полей звучит просто или безобидно, на самом деле это не так - если вы также пытаетесь обеспечить поведение. - person Peter Ritchie; 31.08.2012
comment
@PeterRitchie. Я бы тоже не назвал это безобидным. Публикация чего-либо, когда вам это не нужно, - это огромный красный флаг в моей книге. Не думайте, что мы здесь противоположны друг другу, просто смотрим на проблему с разных сторон. Оказалось, что это скорее дискуссия, чем вопрос этот, но ни в коем случае не бесполезный. - person Tony Hopkinson; 31.08.2012

Геттеры и сеттеры не являются злом, просто они не должны генерироваться автоматически для каждого атрибута. Что касается вашего примера, класс Car, вероятно, должен иметь метод getBrand, возвращающий String, но установщик для этого атрибута, вероятно, не нужен. Метод в Car, возвращающий TextBox с названием бренда, скорее всего, худшая идея, чем простой метод получения, возвращающий String, потому что он не очень общий.

Теперь я вижу ответ Питера Ричи: хороший пример с changeDate. Класс Date должен предоставлять подобный метод вместо того, чтобы предоставлять геттер для дня и месяца и требовать, чтобы пользовательский код самостоятельно вычислял следующий день в календаре.

person piokuc    schedule 29.08.2012

По сути, он говорит, что у вас есть класс с десятью свойствами, и вы хотите сбросить его в строку csv. Добавьте в класс метод Tocsv, чтобы вернуть строку с кучей csvs в ней. Не пишите другой класс, который обращается к нему через свойства и строит строку csv, поскольку по необходимости они становятся очень тесно связанными.

Не уверен, почему он выделил аксессоры, проплем был бы точно таким же, как с дискретными методами для получения и установки, или, что еще хуже, с переменными-членами.

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

SomeLabel.Text = SomeInstance.StringValue;

с участием

SomeInstance.MakeStringValueALabel(SomeLabel);
person Tony Hopkinson    schedule 29.08.2012