Чи добре для класу використовувати власний публічний метод?


23

Фон

Наразі у мене ситуація, коли у мене є об'єкт, який передається і приймається пристроєм. Це повідомлення має кілька конструкцій:

public void ReverseData()
public void ScheduleTransmission()

ScheduleTransmissionМетод необхідно викликати ReverseDataметод щоразу , коли він викликається. Однак бувають випадки, коли мені потрібно буде зателефонувати ReverseDataзовнішньо (і мені слід додати повністю поза простором імен ), звідки об’єкт інстанціюється у програмі.

Що стосується "прийому", я маю на увазі, що ReverseDataвін буде викликаний зовні в object_receivedобробці подій, щоб скасувати назад дані.

Питання

Чи загально прийнятним об'єкт називати власні публічні методи?


5
Немає нічого поганого в тому, щоб викликати публічний метод самостійно. Але, виходячи з назв вашого методу, ReverseData () здається мені трохи небезпечним для публічного методу, якщо він перетворює внутрішні дані. Що робити, якщо ReverseData () викликається за межами об'єкта, а потім знову викликається за допомогою ScheduleTransmission ().
Каан

@Kaan Це не справжні назви моїх методів, але тісно пов’язані між собою. Насправді "зворотні дані" повертають лише 8 біт загального слова і робиться, коли ми отримуємо та передаємо.
Snoop

Питання досі стоїть. Що робити, якщо ці 8 біт повернуті двічі до запланованої передачі? Це відчувається як величезна дірка в загальнодоступному інтерфейсі. Думка про публічний інтерфейс, як я згадую у своїй відповіді, може допомогти вияснити цю проблему.
Даніель Т.

2
@StevieV Я вважаю, що це лише посилює стурбованість, яку викликає Каан. Це звучить як думка, що ви відкриваєте метод, який змінює стан об'єкта, і стан залежить насамперед від того, скільки разів викликається метод. Це створює кошмар, намагаючись відстежувати стан об'єкта у вашому коді. Мені здається, більше, ніж ви отримаєте користь від окремих типів даних із цих концептуальних станів, тому ви можете сказати, що це в будь-якому даному фрагменті коду, не турбуючись про це.
jpmc26

2
@StevieV щось на зразок даних і SerializedData. Дані - це те, що бачить код, а SerializedData - це те, що надсилається через мережу. Потім зробіть зворотні дані (а точніше серіалізувати / десериалізувати дані) перетворення з одного типу в інший.
csiz

Відповіді:


33

Я б сказав, що це не тільки прийнятно, але рекомендується, особливо якщо ви плануєте дозволити розширення. Щоб підтримувати розширення до класу в C #, вам потрібно позначити метод як віртуальний відповідно до наведених нижче коментарів. Однак ви можете задокументувати це, щоб хтось не здивувався, коли переосмислення ReverseData () змінило спосіб ScheduleTransmission ().

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


Нічого собі, здавалося, дуже дивно, але це допомагає. Хотіли б також побачити, що кажуть деякі інші. Дякую.
Snoop

2
"щоб хтось не здивувався, коли переосмислення ReverseData () змінює спосіб ScheduleTransmission () працює" : ні, не дуже. Принаймні, не в C # (зауважте, що питання має тег C #). Якщо ReverseData()це не абстрактно, незалежно від того, чим ви займаєтесь у дитячому класі, це завжди буде називатися оригінальний метод.
Арсеній Мурценко

2
@MainMa Або virtual... І якщо ви перекриєте метод, то, за визначенням, він повинен бути virtualабо abstract.
Pokechu22

1
@ Pokechu22: дійсно, virtualтеж працює. У всіх випадках підпис, на думку ОП, є public void ReverseData(), тому частина відповіді про переважні речі є дещо оманливою.
Арсеній Муренко

@MainMa Ви говорите, що якщо метод базового класу викликає віртуальний метод, він не веде себе звичайним віртуальним способом, якщо це не було abstract? Я переглянув відповіді на abstractvs virtualі не побачив згаданого: досить абстрактний означає, що в цій базі немає версії.
JDługosz

20

Абсолютно.

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

Немає нічого поганого в тому, щоб викликати публічні методи. Ілюстрація у вашому запитанні є прекрасним прикладом ситуації, коли два загальнодоступні методи - саме те, що вам слід зробити.

Однак ви можете уважно стежити за такими моделями:

  1. Метод Hello()викликає World(string, int, int)так:

    Hello()
    {
        this.World("Some magic value here", 0, 100);
    }

    Уникайте цієї закономірності. Замість цього використовуйте необов'язкові аргументи . Необов'язкові аргументи полегшують відкриття: абонент, який вводить Hello(, не обов’язково знатиме, що існує метод, який дозволяє викликати метод із значеннями за замовчуванням. Необов’язкові аргументи також є самодокументуванням. World()не показує абоненту, які є фактичні значення за замовчуванням.

  2. Метод Hello(ComplexEntity)викликає World(string, int, int)так:

    Hello(ComplexEntity entity)
    {
        this.World(entity.Name, entity.Start, entity.Finish);
    }

    Замість цього використовуйте перевантаження . Ця ж причина: краща виявність. Абонент може побачити відразу всі перевантаження через IntelliSense та вибрати потрібне.

  3. Метод просто викликає інші публічні методи, не додаючи суттєвого значення.

    Подумай двічі. Вам справді потрібен цей метод? Або ви повинні його видалити та дозволити абоненту викликати різні методи? Підказка: якщо назва методу не здається правильним або його важко знайти, ймовірно, його слід видалити.

  4. Метод підтверджує введення, а потім викликає інший метод:

    Hello(string name, int start, int end)
    {
        if (name == null) throw new ArgumentNullException(...);
        if (start < 0) throw new OutOfRangeException(...);
        ...
        if (end < start) throw new ArgumentException(...);
    
        this.World(name, start, end);
    }

    Натомість Worldслід перевірити його параметри або бути приватними.


Чи застосовуватимуться ті ж думки, якби ReverseData насправді був внутрішнім, і використовувався у всьому просторі імен, що містить екземпляри "object"?
Снуп

1
@StevieV: так, звичайно.
Арсеній Муренко

@MainMa Я не розумію, в чому причина пунктів 1 і 2
Каполь

@Kapol: дякую за відгук. Я відредагував свою відповідь, додавши пояснення про перші два пункти.
Арсеній Муренко

Навіть у випадку 1 я зазвичай віддаю перевагу перевантаженням, а не необов'язковим аргументам. Якщо параметри такі, що використання перевантажень не є варіантом або створює особливо велику кількість перевантажень, я створив би власний тип і використовував би його як аргумент (як у пропозиції 2) або рефакторинг коду.
Брайан

8

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

У високооптимізованих бібліотеках потрібно скопіювати ідіому, викладену в java.util.ArrayList#ensureCapacity(int).

забезпечитиздатність

  • ensureCapacity є публічним
  • ensureCapacity має всі необхідні межі перевірки та значення за замовчуванням тощо.
  • ensureCapacity дзвінки ensureExplicitCapacity

забезпечитиздатність внутрішньої

  • ensureCapacityInternal є приватним
  • ensureCapacityInternal має мінімальну перевірку помилок, оскільки всі вхідні дані надходять зсередини класу
  • ensureCapacityInternal ТАКОЖ дзвонить ensureExplicitCapacity

secureExplicitCapacity

  • ensureExplicitCapacity ТАКОЖ приватний
  • ensureExplicitCapacityне має перевірки помилок
  • ensureExplicitCapacity робить фактичну роботу
  • ensureExplicitCapacityне дзвониться з будь-якого місця, окрім як ensureCapacityіensureCapacityInternal

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

Однак це використовується в ArrayList, одному з найбільш використовуваних класів в JDK. Цілком можливо, що ваш випадок не вимагає такого рівня складності та суворості. Довга коротка історія, якби всі ensureCapacityInternalдзвінки були замінені ensureCapacityдзвінками, ефективність все-таки була б дійсно, дуже хорошою. Ця мікрооптимізація, ймовірно, лише після детального розгляду.


3

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

Публічні методи зазвичай мають договір «взяти об’єкт у послідовному стані, зробити щось розумне, залишити об’єкт у (можливо, іншому) послідовному стані». Тут "послідовний" може означати, наприклад, що Lengtha з a List<T>не більший за його Capacityі те, що посилання на елементи в індексах від 0to Length-1не буде кинутим.

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


1

Інший приклад, коли виклик публічного методу всередині іншого публічного методу є цілком чудовим, це підхід CanExecute / Execute. Я використовую його, коли мені потрібно як перевірка, так і інваріантне збереження .

Але взагалі я завжди з цим обережний. Якщо метод a()називається всередині методу b(), це означає, що метод a()є деталізацією реалізації b(). Дуже часто це вказує на те, що вони належать до різних рівнів абстракції. І той факт, що вони обидва є публічними, змушує мене замислитись, чи це порушення принципу єдиної відповідальності чи ні.


-5

Вибачте, але мені доведеться не погодитися з більшістю інших відповідей "так можна" і сказати:

Я б заважав класу викликати один публічний метод від іншого

З цією практикою є кілька потенційних проблем.

1: Нескінченний цикл у спадковому класі

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

2: Події, ведення журналів тощо

наприклад, у мене є метод Add1, який запускає подію "1 додано!" Напевно, я не хочу, щоб метод Add10 піднімав цю подію, писав у журнал чи що завгодно, десять разів.

3: нарізка різьби та інші тупики

Наприклад, InsertComplexData відкриває db-з'єднання, починає транзакцію, блокує таблицю, потім викликає InsertSimpleData, при відкритті з'єднання починає транзакцію, чекає розблокування таблиці ....

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

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

Редагувати ----

Давайте розширюватимемо конкретний випадок в ОП.

у нас немає багато деталей, але ми знаємо, що ReverseData викликає якийсь обробник подій, а також метод ScheduleTransmission.

Я припускаю, що зворотні дані також змінюють внутрішній стан об'єкта

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

Щоб зробити нитку ReverseData безпечною, можна додати замок. Але якщо ScheduleTransmission також має бути безпечним для потоків, вам потрібно поділитися тим самим блокуванням.

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

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

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

Ось ще один: Ви також потенційно порушуєте DDD. Якщо ваш об’єкт є об’єктом домену, його публічними методами повинні бути терміни домену, які щось означають для бізнесу. У цьому випадку малоймовірним є те, що "купити десяток яєць" - це те саме, що "купити 1 яйце 12 разів", навіть якщо воно починається саме так.


3
Ці питання звучать більше як погано продумана архітектура, ніж загальне засудження принципу дизайну. (Можливо, виклик "1 додано" добре щоразу. Якщо це не так, ми повинні програмувати по-іншому. .) Замість того, щоб представити погані реалізації, ви можете зосередитись на більш твердих аргументах, які стосуються доступу до публічних методів як універсального принципу. Наприклад, з урахуванням хорошого акуратного коду, чому це погано?
doppelgreener

Точно, якщо це не так, у вас немає місця розмістити подію. Так само і з №3, все добре, доки один із ваших методів по ланцюжку не потребує транзакції, тоді вас накрутили
Еван

Усі ці проблеми відображають більш низьку якість самого коду, ніж загальний принцип. Усі три випадки - це лише поганий код. Вони можуть бути вирішені якоюсь версією "не роби цього, а замість цього" (можливо, запитавши "що саме ти хочеш?") І не ставити під сумнів загальний принцип класу, який називає власну публіку методи. Туманний потенціал для нескінченного циклу є єдиним тут, який досягає питання в принципі, і навіть тоді він не вирішується ґрунтовно.
doppelgreener

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

Те, що ви можете використовувати молоток, щоб зламати власну руку, не означає, що молоток поганий. Це означає, що вам не потрібно робити з цим речі, які зламають вам руку.
doppelgreener
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.