Как меняется концепция класса при передаче данных в конструктор вместо параметров метода?

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();
}

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

ciscoheat
источник
Впервые я увидел несколько полезных вопросов и ответов о том, как семантика ОО на самом деле выражает намерение. До сих пор для меня все это было синтаксическим сахаром.

Ответы:

11

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

Параметры, которые вам нужно передать в конструкторе, это параметры, которые не имеют разумного значения по умолчанию, и если ваш класс является неизменным (или действительно struct), то должны передаваться все свойства, отличные от стандартных.

Относительно ваших двух примеров:

  • если вы переходите textк конструктору, он намекает на то, что Parser2класс будет специально создан для последующего анализа этого экземпляра текста. Это будет конкретный парсер. Это обычно имеет место, когда создание класса очень дорого или тонко, RegEx может быть скомпилирован в конструкторе, поэтому, как только вы держите экземпляр, вы можете использовать его повторно, не оплачивая стоимость компиляции; Другой пример - инициализация PRNG - лучше, если это делается редко.
  • если вы переходите textк методу, он сигнализирует, что Parser1его можно использовать повторно для анализа различных текстов по вызовам.
Sklivvz
источник
3
Я бы добавил, что есть слабый сигнал о том, что Parser1 поддерживает состояние - то есть, что конкретная строка текста может давать разные результаты в зависимости от того, что было ранее сделано на экземпляре tat. Это не обязательно так, но это может быть.
Jmoreno
8

Хорошо, давайте вспомним, что значит передавать переменную как параметр конструктора: Вы инициализируете объект, чтобы использовать его переменные экземпляра в методах объекта. Дело в том, что вы, вероятно, хотите использовать его более чем в одном методе, так как вы хотите иметь высокую степень сплоченности в своем классе.

Передача параметра непосредственно методу означает, что отправка сообщения объекту и, возможно, получение ответа. Таким образом, клиент хочет, чтобы объект предоставил ему услугу.

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

McMannus
источник
+1. Сплоченность - это то, как я решаю, принадлежит ли он методу или конструктору.
Jhewlett
4

Использование простое в обоих случаях, но что означает включение ввода параметров в Parser1 по сравнению с другим?

Это фундаментальное изменение дизайна. И дизайн должен передавать намерение и смысл. Вам нужно иметь отдельные объекты для каждой строки, которую вы хотите проанализировать? Другими словами, зачем нам нужен экземпляр синтаксического анализатора со строкой X и другой экземпляр со строкой Y? Что такое синтаксический анализ (ing) и заданная строка, что оба должны жить и умирать вместе? Предполагая, что «базовая [синтаксический анализ] реализация» (как говорит Роберт Харви) не меняется, похоже, в этом нет никакого смысла. И даже тогда его сомнительное ИМХО.

Как меняется концепция класса при передаче данных в конструктор вместо параметров метода?

Параметры конструктора говорят мне, что эти вещи необходимы для объекта. Правильное состояние не гарантируется без них. Также я знаю, как / почему один парсер принципиально отличается от другого.

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

Другой вопрос возникает, когда я понимаю, что интерфейс был бы совершенно бессмысленным во второй реализации:

Интерфейс, как и в API, - это методы и свойства, предоставляемые клиентскому коду. Не зацикливайтесь public interface { ... }исключительно. Таким образом, значение интерфейса заключается в дилемме параметра «либо», либо «конструктор против метода», а не public interface Iparserпротивpublic sealed class Parser

sealedКласс нечетный. Если я думаю о различных реализациях парсера - вы упомянули «Iparser» - тогда наследование - моя первая мысль. Это просто естественное концептуальное расширение моего мышления. То есть все ParserXs в основном Parsers. Как еще это сказать? ... Немецкая Шепард - это собака (наследство), но я могу научить своего попугая лаять (вести себя как собака - "интерфейс"); но Полли не собака, просто притворяется, выучив подмножество упрямства Классы, абстрактные или иные, прекрасно работают как интерфейсы .

radarbob
источник
Если он ходит как парсер и говорит как парсер, это ... утка!
2

Вторая версия класса может быть сделана неизменной.

Интерфейс все еще можно использовать для предоставления возможности замены базовой реализации.

Роберт Харви
источник
Разве нельзя сделать его неизменным в первой версии, передав функциональные данные в пределах класса?
ciscoheat
2
Абсолютно. Но общая закономерность неизменности состоит в том, чтобы устанавливать члены класса с помощью конструктора и иметь свойства только для чтения. Для функционального программирования вам даже не нужен класс.
Роберт Харви
Сцилла и Харибда: выбрать ОО или неизменные данные? Я никогда не слышал, чтобы это было так.
2

Parser1

Сборка с конструктором по умолчанию и передача входного текста в метод подразумевает, что Parser1 можно использовать повторно.

Parser2

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

Майк Партридж
источник
Конечно, поэтому, если мы возьмем его на следующий уровень, что сделать вывод о повторно используемом объекте по сравнению с повторно используемым?
ciscoheat
Я не предполагал бы больше, чем это, вместо того, чтобы откладывать к документации.
Майк Партридж
«Повторно используемый объект», в котором изменяется его идентичность, не является следствием. А с управляемыми фреймворками, означающими, что вам даже не нужно выбрасывать вещи, просто перезаписывать их или выводить их из области видимости, это, безусловно, не нужно. Построй прочь!