Як змінюється концепція класу при передачі даних конструктору замість параметрів методу?


12

Скажімо, ми робимо аналізатор. Однією з реалізацій може бути:

public sealed class Parser1
{
    public string Parse(string text)
    {
       ...
    }
}

Або ми могли замість цього передати текст конструктору:

public sealed class Parser2
{
    public Parser2(string text)
    {
       this.text = text;
    }

    public string Parse()
    {
       ...
    }
}

Використання в обох випадках просте, але що означає включити параметр в Parser1порівнянні з іншими? Яке повідомлення я надіслав колезі-програмісту, коли вони переглядають API? Також, чи є якісь технічні переваги / недоліки в певних випадках?

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

public interface IParser
{
    string Parse();
}

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


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

Відповіді:


11

Семантично кажучи, в OOP ви повинні передавати конструктору лише набір параметрів, необхідних для складання класу - аналогічно, коли ви викликаєте метод, ви повинні передавати йому лише ті параметри, які йому потрібні для виконання своєї бізнес-логіки.

Параметри, які вам потрібно передати конструктору, - це параметри, які не мають значущого значення за замовчуванням, і якщо ваш клас незмінний (або справді а struct), то слід передавати всі, які не є типовими властивостями.

Щодо двох ваших прикладів:

  • якщо перейти textдо конструктора, він натякає, що Parser2клас буде спеціально побудований для розбору цього екземпляра тексту пізніше. Це буде конкретний аналізатор. Зазвичай це відбувається, коли складання класу є дуже дорогим або тонким, RegEx може бути скомпільований у конструкторі, тому щойно ви тримаєте екземпляр, ви можете його повторно використовувати, не платячи витрати на складання; Ще один приклад - ініціалізація PRNG - краще, якщо це робиться рідко.
  • якщо перейти textдо методу, він подає сигнали, які Parser1можна повторно використовувати для розбору різних текстів дзвінками.

3
Я додам, що є слабкий сигнал, що Parser1 підтримує стан - тобто певний рядок тексту може давати різні результати залежно від того, що раніше було зроблено на прикладі tat. Це не обов'язково так, але може бути.
jmoreno

8

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

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

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


+1. Згуртованість - це те, як я вирішую, належить вона методу чи конструктору.
jhewlett

4

Використання в обох випадках просте, але що означає включити параметр до Parser1 порівняно з іншими?

Його фундаментальний зрушення в дизайні. І дизайн повинен передавати наміри та сенс. Чи потрібно мати окремі об'єкти для кожного рядка, який ви хочете проаналізувати? Іншими словами, навіщо нам потрібен екземпляр парсера з stringX та інший екземпляр з stringY? Що стосується розбору (англ.) Та даного рядка, що вони повинні жити і вмирати разом? Якщо припустити, що "основна [розбір] реалізації" (як каже Роберт Харві) не змінюється, то, здається, немає сенсу. І навіть тоді його сумнівний ІМХО.

Як змінюється концепція класу при передачі даних конструктору замість параметрів методу?

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

Параметри конструктора не дозволяють мені занадто багато знати про використання класу. Якщо замість цього я повинен встановити певні властивості - як я можу це знати? Відкривається ціла банка глистів. Які властивості? У якому порядку? Перш ніж використовувати які методи? і так далі.

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

Інтерфейс, як і в API, - це методи та властивості, що піддаються дії клієнтського коду. Не загортайтеся public interface { ... }виключно. Отже, сенс інтерфейсу полягає в дилемі параметрів або конструктор або метод, НЕ public interface Iparserпротиpublic sealed class Parser

sealedКлас непарний. Якщо я замислююся над різними реалізаціями парсера - ви згадали про "Iparser" - то спадщина - це моя перша думка. Це просто природне концептуальне продовження в моєму мисленні. IE всі ParserXs є принципово Parsers. Як інакше сказати? ... Німецький Шепард - собака (спадщина), але я можу навчити свого папугу гавкати (діяти як собака - "інтерфейс"); але Поллі - не собака, а просто прикидається, навчившись підмножини догматичності. Класи, абстрактні чи іншим чином, чудово служать інтерфейсом .


Якщо він ходить, як парсер, і розмовляє, як парсер, це ... качка!

2

Другу версію класу можна зробити незмінною.

Інтерфейс все ще може бути використаний для надання можливості замінювати базову реалізацію.


Чи не можна це зробити непорушним і в першій версії, передаючи дані навколо класу у функціональному порядку?
ciscoheat

2
Абсолютно. Але загальна закономірність незмінності полягає у встановленні членів класу за допомогою конструктора та мають властивості лише для читання. Для функціонального програмування вам навіть не потрібен клас.
Роберт Харві

Сцилла та Харибді: чи я вибираю ОО або незмінні дані? Я ніколи не чув, щоб це ставилося так раніше.

2

Парсер1

Побудова з конструктором за замовчуванням та передача тексту вхідного тексту в метод означає, що Parser1 можна використовувати повторно.

Парсер2

Передача вхідного тексту в конструктор означає, що для кожного вхідного рядка повинен бути створений новий Parser2.


Звичайно, так що якщо ми перенесемо його на наступний рівень, що робити висновок про об'єкт, який може бути повторно використаний, порівняно з неодноразовим використання?
ciscoheat

Я б не припускав більше цього, замість того, щоб переходити до документації.
Майк Партрідж

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