Це анти-візерунок, якщо властивість класу створює та повертає новий екземпляр класу?


24

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

У мене може бути проста властивість, викликана reciprocalдля повернення протилежного заголовка поточного значення, а потім вручну створити новий екземпляр класу Heading, або я можу створити метод, як createReciprocalHeading()автоматично створити новий екземпляр класу Heading і повернути його в режим користувач.

Однак один з моїх колег рекомендував мені просто створити властивість класу під назвою, reciprocalяка повертає новий екземпляр самого класу через його метод getter.

Моє запитання: чи не анти-шаблон для властивості класу поводитись так?

Я особливо вважаю це менш інтуїтивним, оскільки:

  1. На мій погляд, властивість класу не повинна повертати новий екземпляр класу, і
  2. Назва властивості, яка є reciprocal, не допомагає розробнику повністю зрозуміти свою поведінку, не отримуючи допомоги від IDE або перевірки підпису getter.

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


6
Як говорить @Ewan в останньому абзаці своєї відповіді, це далеко не те, що було антизразком, мати Headingнепорушний тип і reciprocalповертати нове Heading- це «хороша практика». (із застереженням у двох закликах reciprocalповернути "те саме", тобто вони повинні пройти тест на рівність.)
Девід Арно

20
"мій клас непорушний". Там ваш справжній анти-візерунок. ;)
Давид Арно

3
@DavidArno Ну, іноді існує величезна кара за ефективність певних речей, непорушних, особливо якщо мова не підтримує її споконвічно, але в іншому випадку я погоджуюся, що незмінність є хорошою практикою у багатьох випадках.
53777A

2
@dcorking клас реалізований як у C #, так і в TypeScript, але я краще зберігаю цю мову-агностик.
53777A

2
@Kaiserludi звучить як відповідь (чудова відповідь) - коментарі вимагають ясності
dcorking

Відповіді:


22

Невідомо, що є такі речі, як Copy () або Clone (), але так, я думаю, ви праві перейматися з цього приводу.

візьмемо для прикладу:

h2 = h1.reciprocal
h2.Name = "hello world"
h1.reciprocal.Name = ?

Було б непогано мати попередження про те, що майно кожного разу було новим об’єктом.

Ви також можете припустити, що:

h2.reciprocal == h1

Однак. Якщо ваш клас заголовка був незмінним типом значення, то ви могли б реалізувати ці взаємозв'язки, і зворотна може бути хорошим ім'ям для операції


1
Просто зауважте, що Copy()і Clone()є методами, а не властивостями. Я вважаю, що ОП посилається на власників нерухомості, які повертають нові інстанції.
Лінн

6
я знаю. але властивості (начебто) також методи у c #
Ewan

4
В цьому випадку, C # має набагато більше пропозиції: String.Substring(), Array.Reverse(), Int32.GetHashCode()і т.д.
Лінн

If your heading class was an immutable value type- Я думаю, вам слід додати, що встановники властивостей на незмінних об'єктах повинні повертати нові екземпляри завжди (або принаймні, якщо нове значення не те саме, що старе значення), оскільки це визначення незмінних об'єктів. :)
Іван Колмичек

17

Інтерфейс класу змушує користувача класу робити припущення про те, як він працює.

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

Якщо багато з цих припущень є неправильними, а мало хто правильним, інтерфейс є сміттям.

Поширене припущення щодо властивостей полягає в тому, що виклик функції отримання є дешевим. Ще одне поширене припущення щодо властивостей полягає в тому, що виклик функції get двічі поспіль поверне те саме.


Ви можете обійти це за допомогою послідовності, щоб змінити очікування. Наприклад, для невеликої 3D - бібліотеки , де вам потрібно Vector, Ray Matrixі т.д. Ви можете зробити це таким чином, що добувачі , такі як Vector.normalі Matrix.inverseв значній мірі завжди дорого. На жаль , навіть якщо ваші інтерфейси послідовно використовувати дорогі властивості, інтерфейси будуть в кращому випадку , як інтуїтивні , як один , який використовує Vector.create_normal()і Matrix.create_inverse()- але я НЕ знаю ні одного сильного аргументу , який може бути зроблений , що використання властивостей створює більш інтуїтивний інтерфейс, навіть після зміни очікувань.


10

Властивості повинні повертатися дуже швидко, повертати те саме значення при повторних дзвінках, а отримання їх значення не повинно мати побічних ефектів. Те, що ви описуєте тут, не повинно реалізовуватися як властивість.

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

Властивості, як правило, повинні бути дуже простими операціями, які отримують / задають частину поточного стану класу. Заводські операції не відповідають цим критеріям.


1
DateTime.NowВластивість зазвичай використовується і відноситься до нового об'єкту. Я думаю, що назва ресурсу може допомогти усунути неоднозначність щодо того, повертається чи ні одного і того ж об'єкта щоразу. Це все в назві і як воно використовується.
Грег Бургхардт

3
DateTime.Now розцінюється як помилка. Дивіться stackoverflow.com/questions/5437972/…
Бред Томас

@GregBurghardt Побічний ефект нового об'єкта сам по собі не може бути проблемою, тому що DateTimeце тип значення і не має поняття ідентичності об'єкта. Якщо я правильно розумію, проблема полягає в тому, що це недетерміновано і щоразу повертає нове значення.
Зев Шпіц

1
@GregBurghardt DateTime.Newє статичним, і тому ви не можете очікувати, що він відображає стан об'єкта.
Цахі Ашер

5

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

У C # властивості відрізняються тим, що вони (умовно) з великої літери (як методи), але відсутні у дужках (як змінні загальнодоступних екземплярів). Якщо ви бачите наступний код, відсутні документи, що ви очікуєте?

var reciprocalHeading = myHeading.Reciprocal;

Як родич-початківець C #, але той, хто читав Правила користування майном Microsoft , я б сподівався Reciprocal, серед іншого:

  1. бути логічним членом даних Headingкласу
  2. недорого зателефонувати, таким чином, щоб мені не потрібно кешувати значення
  3. відсутність помітних побічних ефектів
  4. дають той же результат, якщо дзвонити два рази поспіль
  5. (можливо) запропонувати ReciprocalChangedподію

З цих припущень (3) і (4), ймовірно, є правильними (якщо припустити Heading, що це непорушний тип значення, як у відповіді Евана ), (1) є дискусійним, (2) невідомим, але також дискусійним, і (5) навряд чи мають смисловий сенс (хоча все, що має заголовок, може мати HeadingChangedподію). Це підказує мені, що в API C # "отримати або обчислити зворотній зв'язок" не слід реалізовувати як властивість, але особливо, якщо обчислення дешеве і Headingнезмінне, це прикордонний випадок.

(Однак зауважте, що жодна з цих проблем не має нічого спільного з тим, чи викликає властивість навіть новий екземпляр (2). Створення об'єктів у CLR, саме по собі, є досить дешевим.)

У Java властивості - це метод, який називає умову. Якщо я бачу

Heading reciprocalHeading = myHeading.getReciprocal();

мої очікування схожі з вищезазначеними (якщо менш чітко викладено): я очікую, що дзвінок буде дешевим, безсильним і не має побічних ефектів. Однак поза рамками JavaBeans поняття "властивість" не настільки важливе для Яви, і, особливо, якщо розглядати незмінне властивість без відповідних setReciprocal(), getXXX()конвенція зараз дещо старомодна. Від другого видання « Ефективна Java» (якому вже більше восьми років):

Методи, що повертають нефункцію booleanчи атрибут об’єкта, на який вони викликаються, зазвичай називають іменником, фразою іменника або словосполученням, що починається з дієслова get…. Існує вокальний контингент, який стверджує, що лише третя форма (починаючи з get) є прийнятною, але для цього твердження є мало підстав. Перші дві форми зазвичай призводять до більш читаного коду ... (стор. 239)

Тоді я б сподівався, що в сучасній більш плавній API

Heading reciprocalHeading = myHeading.reciprocal();

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

У Рубі немає такого поняття як власність. Є "атрибути", але якщо я бачу

reciprocalHeading = my_heading.reciprocal

У мене немає безпосереднього способу дізнатися, чи я отримую доступ до змінної примірника @reciprocalза допомогою attr_readerпростого або методу accessor або я викликаю метод, який виконує дорогий обчислення. Той факт, що назва методу є простим іменником, хоча, замість того, щоб сказати calcReciprocal, знову ж таки говорить про те, що виклик є принаймні дешевим і, ймовірно, не має побічних ефектів.

У Scala умовою називання є те, що методи з побічними ефектами мають круглі дужки, а методи без них не мають, але

val reciprocal = heading.reciprocal

може бути будь-який із:

// immutable public value initialized at creation time
val reciprocal: Heading = … 

// immutable public value initialized on first use
lazy val reciprocal: Heading = … 

// public method, probably recalculating on each invocation
def reciprocal: Heading = …

// as above, with parentheses that, by convention, the caller
// should only omit if they know the method has no side effects
def reciprocal(): Heading = …

(Зверніть увагу, що Scala допускає різні речі, які, тим не менше, не відволікає керівництво по стилю . Це одне з моїх головних роздратовань щодо Scala.)

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

Якщо коротко: знайте мову, якою ви користуєтесь, і знайте, які очікування принесуть іншим програмістам до вашого API. Все інше - деталь реалізації.


1
Один з моїх улюблених відповідей +1, дякую, що ви витратили свій час на написання цього запису.
53777A

2

Як говорили інші, повернення екземплярів одного класу є досить поширеною схемою.

Іменування має йти поряд з умовами мови про іменування.

Наприклад, на Java я, мабуть, очікував, що він буде викликаний getReciprocal();

Зважаючи на це, я вважав би об'єкти, що змінюються проти непорушних .

З непорушними речами все дуже легко, і це не зашкодить, повертаючи той же предмет чи ні.

Що стосується змінних , це може стати дуже страшним.

b = a.reciprocal
a += 1
b = what?

Тепер на що йдеться? Зворотна вихідна величина а? Або зворотна змінена? Це може бути занадто коротким прикладом, але ви розумієте.

У таких випадках шукайте кращу назву, яка повідомляє, що відбувається, наприклад, createReciprocal()може бути кращим вибором.

Але це дійсно залежить і від контексту.


Ви мали на увазі змінні ?
Carsten S

@CarstenS Так, звичайно, дякую. Поняття не маю, де моя голова. :)
Ейко

Дещо не погоджуйтеся getReciprocal(), оскільки це "звучить" як "звичайний" стикер JavaBeans. Віддайте перевагу "createXXX ()" або, можливо, "
CalcuXXX

@ user949300 Я трохи погоджуюся з вашими занепокоєннями - і згадую про створення ... ім'я далі. Однак є і мінуси. create ... має на увазі нові екземпляри, які не завжди можуть бути такими (придумайте окремі виділені об'єкти для деяких спеціальних випадків), обчисліть ... подібні деталі впровадження витоків - що може сприяти неправильним припущенням щодо цінника.
Ейко

1

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


1
+1 для ясності імені, але що не так з SRP в інших рішеннях?
53777A

3
Чому ви рекомендуєте заводський клас над фабричним методом? (На мою думку, корисна відповідальність об'єкта - це часто випромінювати такі об'єкти, як він, наприклад iterator.next. Чи це легке порушення SRP викликає інші проблеми в інженерній програмі?)
dcorking

2
@dcorking Заводський клас проти методу (на мою думку), як правило, того ж типу питань, що і "Чи об'єкт порівняний, або я повинен зробити компаратор?" Завжди добре робити компаратор, але тільки добре зробити їх порівняльними, якщо це досить універсальний спосіб їх порівняння. (Наприклад, більші числа є більшими, ніж менші. Це добре для порівняння, але можна сказати, що всі рівні більше, ніж усі шанси, я б перетворив це на порівняння) - Отже, для цього я думаю, що існує лише одна спосіб повернути заголовок, тому я схиляюся до створення методу, щоб уникнути зайвого класу.
Людина капітана

0

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

Але, наприклад, рядок рядків може мати властивості "firstWord", "lastWord", або якщо він обробляє Unicode "firstLetter", "lastLetter", які будуть повноцінними рядковими об'єктами - просто зазвичай меншими.


0

Оскільки інші відповіді стосуються частини власності, я просто звернусь до вашого №2 про reciprocal:

Не використовуйте нічого, крім цього reciprocal. Це єдиний правильний термін для того, що ви описуєте (і це формальний термін). Не пишіть некоректне програмне забезпечення , щоб зберегти розробник від навчання домену вони працюють. У навігації, терміни мають досить специфічні значення і використовувати що - то , як , здавалося б , нешкідливі , як reverseі oppositeможуть призвести до плутанини в певних контекстах.


0

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

Зворотна - це чиста функція заголовка, так само як і від’ємник числа є чистою функцією цього числа.

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

Тепер, у деяких мовах, може бути доцільним мати його як властивість, як це визначено в контексті цієї мови, якщо це приносить певні переваги завдяки механіці цієї мови. Наприклад, якщо ви хочете зберегти його в базі даних, можливо, доцільно зробити його властивістю, якщо це дозволяє автоматичне управління за допомогою ORM. А може це мова, де немає різниці між властивістю та функцією, що не має параметри (я не знаю такої мови, але я впевнений, що є). Тоді розробити функції та властивості слід від дизайнера та документаліста API.


Редагувати: конкретно для C # я би розглядав існуючі класи, особливо класичні бібліотеки.

  • Є BigIntegerі Complexструктури. Але вони є структурами і мають лише статичні функції, і всі властивості мають різний тип. Тож не дуже допомагає дизайн для вас у Headingкласі.
  • Math.NET Numerics має Vectorклас , який має дуже мало властивостей і, здається, досить близький до вашого Heading. Якщо ви йдете цим, то у вас буде функція для зворотної, а не властивості.

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


-1

Дійсно дуже цікаве питання. Я не бачу порушення ТОЛОГО + СУХОГО + КІССУ в тому, що ви пропонуєте, але все-таки воно погано пахне.

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

Плюс: що станеться, якщо ви посилаєтесь getReciprocal()на повернутий об’єкт? Ви отримуєте ще один екземпляр класу, який може бути точною копією першого! А якщо ви посилаєтесь getReciprocal()на ТОМУ? Об'єкти, що розповсюджуються кому?

Знову ж таки: що, якщо користувачеві потрібна зворотна величина (я маю на увазі скалярну)? getReciprocal()->getValue()? ми порушуємо Закон Деметра за невеликі вигоди.


Я б із задоволенням прийняв контр-аргументи з низової точки, дякую!
Доктор Джанлуїджі Зене Занеттіні

1
Я не заявив, але підозрюю, що причиною може бути той факт, що це не дуже додає решти відповідей, але я можу помилятися.
53777A

2
СОЛІД і КІСС часто протилежні один одному.
whatsisname
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.