Как спроектировать класс, который имеет только один сверхмощный метод работы и данные, возвращающие другие методы? - PullRequest
3 голосов
/ 21 ноября 2008

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

Как мне его оформить?

  1. Укажите ctor, который принимает строку, предоставьте метод Parse и предоставьте методы (назовем их «второстепенными»), которые возвращают отдельные токены, количество токенов и т. Д. ИЛИ
  2. Предоставьте ctor, который ничего не принимает, предоставьте метод Parse, который принимает строку и второстепенные методы, как указано выше. OR
  3. Предоставляет ctor, который принимает строку и предоставляет только второстепенные методы, но не метод parse. Разбор выполняется ctor.

1 и 2 имеют тот недостаток, что пользователь может вызывать второстепенные методы без вызова метода Parse. Мне придется проверять каждый второстепенный метод, который вызывался для метода Parse.

Проблема, которую я вижу в 3, заключается в том, что метод синтаксического анализа может потенциально многое сделать. Кажется неуместным помещать его в ctor.

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

Какой хороший подход? Каковы некоторые из соображений?

(язык c #, если кого-то волнует).

Спасибо

Ответы [ 4 ]

4 голосов
/ 21 ноября 2008

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

ValueObject values = parsingClass.Parse(theString);
1 голос
/ 21 ноября 2008

Я думаю, что это действительно хороший вопрос ...

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

Одно из соображений, на которое вы указываете, заключается в том, что синтаксический анализ токенов может быть относительно сложным вычислительным процессом; это может занять некоторое время. Я согласен с вами, что вы, возможно, не захотите принять удар за это в конструкторе; в этом случае может иметь смысл использовать метод Parse (). Однако возникает вопрос, существуют ли какие-либо разумные операции, которые можно выполнить в вашем классе до завершения метода parse (). Если нет, то вы вернулись к исходной точке; перед завершением метода parse () вы фактически находитесь в состоянии «неполного экземпляра» вашего класса; это фактически бесполезно. Конечно, все меняется, если вы хотите и можете использовать многопоточность в своем приложении; если вы готовы перенести сложные вычислительные операции в другой поток и поддерживать некоторую синхронизацию в методах / средствах доступа вашего класса до тех пор, пока вы не закончите, тогда вся функция parse () имеет больше смысла, так как вы можете выбрать порождение что в новой ветке целиком. Тем не менее, вы по-прежнему сталкиваетесь с проблемами при попытке использовать ваш класс до того, как он полностью все проанализирует.

Я думаю, что в этот дизайн входит еще более широкий вопрос: какова большая область, в которой этот код будет использоваться? Для чего будет использоваться этот код, и этим, я имею в виду, не только сейчас, при предполагаемом использовании, но есть ли вероятность того, что этот код может расти или изменяться по мере того, как это делает ваше приложение? С точки зрения стабильности реализации , можете ли вы ожидать, что это будет полностью стабильно, или есть вероятность, что что-то из набора данных вы захотите проанализировать или размер данных для анализа или токены, в которые вы будете разбирать, изменится в будущем? Если реализация может измениться, рассмотрите все способы, которыми она может измениться; по моему опыту, эти соображения могут сильно привести к той или иной реализации. И рассмотрение этих вещей не тривиально; не длинным выстрелом.

Чтобы вы не думали, что это просто придирки, я бы сказал, по скромным подсчетам, примерно 10-15% написанных мною классов нуждались в некотором уровне рефакторинга еще до завершения проекта; редко есть дизайн, над которым я работал в выжившей реализации, чтобы выйти на другую сторону, выглядя так же, как и раньше. Таким образом, рассмотрение возможных изменений реализации становится очень полезным для определения того, какой должна быть ваша реализация. Если, скажем, ваша реализация никогда не захочет изменять размер строки для токенизации, вы можете сделать предположение о вычислительной сложности, которая может так или иначе привести вас к общему дизайну.

0 голосов
/ 25 ноября 2008

Два важных соображения:

1) Может ли синтаксический анализ завершиться неудачей?

Если это так, и если вы поместите его в конструктор, он должен вызвать исключение. Метод Parse может возвращать значение, указывающее успех. Поэтому проверьте, как ваши коллеги относятся к выбрасыванию исключений в ситуациях, которые не останавливают показ: по умолчанию предполагается, что им это не понравится.

2) Конструктор должен привести ваш объект в правильное состояние.

Если вы не возражаете, что "еще ничего не проанализировано" является допустимым состоянием ваших объектов, тогда, вероятно, стоит использовать метод parse и вызвать класс SomethingParser.

Если вы этого не хотите, то выполните синтаксический анализ в конструкторе (или фабрике, как предлагает Гарри) и вызовите класс ParsedSomething.

Разница, вероятно, заключается в том, планируете ли вы передавать эти вещи в качестве параметров другим методам. Если это так, то состояние «еще не готово» - это боль, потому что вы должны либо проверять его в каждом вызываемом объекте и корректно обрабатывать его, либо вам нужно написать документацию типа «параметр должен уже проанализировать строку» , А потом, скорее всего, все равно проверяйте каждого вызываемого с помощью assert.

Возможно, вам удастся обработать его так, чтобы начальное состояние совпадало с состоянием после анализа пустой строки (или некоторого другого базового значения), что позволяет избежать проблемы "еще не готово".

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

Вы могли бы подумать о том, чтобы дать вещи два конструктора и метод Parse: строковый конструктор эквивалентен вызову конструктора no-arg, а затем вызову Parse.

0 голосов
/ 21 ноября 2008

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

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

// Using option 3
ParsingClass myClass = new ParsingClass(inputString);

// Parse a new string.
myClass = new ParsingClass(anotherInputString);

// Using option 2
ParsingClass myClass = new ParsingClass();
myClass.Parse(inputString);

// Parse a new string.
myClass.Parse(anotherInputString);

Нет большой разницы в использовании, но с Вариантом 2 вам нужно проверить все ваши второстепенные методы и свойства, чтобы увидеть, был ли выполнен анализ, прежде чем они смогут продолжить. (Вариант 1 требует, чтобы вы делали все, что делает вариант 2 внутри, но также позволяет вам писать код в стиле Варианта 3 при его использовании.)

Кроме того, вы можете сделать конструктор приватным, а метод Parse - статическим, если метод Parse вернет экземпляр объекта.

// Option 4
ParsingClass myClass = ParsingClass.Parse(inputString);

// Parse a new string.
myClass = ParsingClass.Parse(anotherInputString);

Опции 1 и 2 обеспечивают большую гибкость, но требуют больше кода для реализации. Варианты 3 и 4 менее гибкие, но кода для написания меньше. В принципе, нет единственно правильного ответа на этот вопрос. Это действительно вопрос того, что лучше всего соответствует вашему существующему коду.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...