Классы, которых следует избегать (код завершен) - PullRequest
38 голосов
/ 19 января 2010

Я несколько озадачен абзацем в книге с полным кодом.

В разделе «Классы, которых следует избегать» написано:

«Избегайте классов, названных по глаголам. Класс, который имеет толькоповедение, но никакие данные обычно не являются классом. Рассмотрите возможность превращения такого класса, как DatabaseInitialization () или StringBuilder (), в подпрограмму другого класса "

Мой код в основном состоит из классов глаголов без данных.Есть читатели счетов, калькуляторы цен, построители сообщений и т. Д. Я делаю это, чтобы сконцентрировать классы на одной задаче.Затем я добавляю зависимости к другим классам для других функций.

Если я правильно понимаю параграф, я должен использовать код вроде

class Webservice : IInvoiceReader, IArticleReader {
    public IList<Invoice> GetInvoices();
    public IList<Article> GetArticles();
}

вместо

class InvoiceReader : IInvoiceReader {
    public InvoiceReader(IDataProvider dataProvider);
    public IList<Invoice> GetInvoices();
}

class ArticleReader : IArticleReader {
    public ArticleReader(IDataProvider dataProvider);
    public IList<Article> GetArticles();
}

Edit Спасибо за все ответы.

Мой вывод таков: мой текущий код больше SRP, чем OO, но он также страдает от "модели анемичной области".

I 'Я уверен, что эти идеи помогут мне в будущем.

Ответы [ 10 ]

20 голосов
/ 19 января 2010

Имена классов, такие как InvoiceReader, PriceCalculator, MessageBuilder, ArticleReader, InvoiceReader, на самом деле не являются именами глаголов. Это действительно имена классов "существительное агент-существительное". См. существительные агента .

Имя класса глагола было бы что-то вроде Validate, Operate, Manage и т. Д. Очевидно, что они лучше используются в качестве методов и будут довольно неудобными в качестве имен классов.

Самая большая проблема с именами классов «существительное агент-существительное» заключается в том, что они могут дать очень мало смысла относительно того, что класс фактически делает (например, UserManager, DataProcessor и т. Д.). В результате они, более вероятно, будут раздуты и потеряют внутреннее единство. (См. Принцип единой ответственности ).

Следовательно, класс WebService с интерфейсами IInvoiceReader и IArticleReader, вероятно, является более понятным и осмысленным дизайном ОО.

Это дает вам простое, очевидное имя существительного класса «WebService», наряду с именами интерфейса «существительное agent-noun», которые четко объявляют, что класс WebService может сделать для вызывающих.

Возможно, вы могли бы также придать больше смысла реальному классу, добавив префикс к другому существительному, например, PaymentWebService.

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

11 голосов
/ 19 января 2010

Лично я игнорирую это "правило". Сам .NET Framework полон классов «глаголов»: TextReader, BinaryWriter, XmlSerializer, CodeGenerator, StringEnumerator, HttpListener, TraceListener, ConfigurationManager, TypeConverter, RoleProvider ... если вы считаете, что Framework плохо спроектирован, то ни в коем случае не используйте имена, подобные этим.

Намерение Стива понятно. Если вы обнаружите, что создаете десятки и десятки классов просто для выполнения конкретной задачи, это, вероятно, признак модели анемичной области , где объекты, которые должен быть в состоянии выполнять эти вещи сами по себе нет. Но в какой-то момент вы должны сделать выбор между «чистым» ООП и SRP .

Мое предложение будет таким: если вы создаете класс «глагол», который действует на один класс «существительное», подумайте честно о том, может ли класс «существительное» выполнить действие самостоятельно. Но не начинайте создавать объекты Бога и не придумывайте бессмысленных / вводящих в заблуждение имен с единственной целью избежать класса глаголов.

5 голосов
/ 19 января 2010

Не следуйте никаким советам вслепую. Это всего лишь рекомендации.

Тем не менее, Существительные дают очень хорошие имена классов , если они моделируют логические объекты. Так как класс "Person" является планом для всех объектов "Person", называть его "Person" очень удобно, потому что он позволяет рассуждать так: «Я создам Person из ввода пользователя, но сначала чтобы подтвердить это ... "

3 голосов
/ 19 января 2010

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

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

Однако существует ситуация, когда создание классов для реализации действия - это хорошая вещь, например, когда у вас разные стратегии для одного и того же действия. Очень хороший пример - IComparer <>. Все, что он делает, это сравнивает две вещи, но есть несколько способов сравнения вещей.

Как предположил автор, в этих случаях хорошим способом сделать это является создание интерфейса и его реализация. IComparer <> снова приходит на ум.

Другая распространенная ситуация - тяжелое состояние действия, например загрузка файлов. Может оказаться оправданным инкапсулировать состояние в классе.

2 голосов
/ 19 января 2010

По сути, книга говорит о том, что ОО-проект состоит в извлечении объектов (существительных) и идентификации операций (глаголов), которые происходят на этих объектах и ​​между ними.

Существительные становятся объектами, глаголы становятся методами, воздействующими на эти объекты.

Идея состоит в том, что

ближе программа моделирует реальную мировая проблема, чем лучше программа будет.

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

В случае класса InvoiceReader

  • вы собираетесь создать только один экземпляр
  • единственное состояние, которое он представляет, это состояние, в котором содержится dataProvider
  • содержит только один метод

Нет смысла помещать его в объект.

1 голос
/ 19 января 2010

Утверждение Класс, который имеет только поведение, но не имеет данных, обычно не является классом. совершенно неверно.

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

Кроме того, классы без состояния отлично подходят для вычислений, которые вам нужны только в течение короткого периода времени.Вы создаете их экземпляры (или запрашиваете какую-то Фабрику, чтобы получить их), делаете необходимые вычисления, а затем выбрасываете их в мусор.У вас может быть соответствующая «версия» вашего поведения, доступная где угодно и когда угодно.

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

Например:

public interface IExporter
{
    /// <summary>
    /// Transforms the specified export data into a text stream.
    /// </summary>
    /// <param name="exportData">The export data.</param>
    /// <param name="outputFile">The output file.</param>
    void Transform(IExportData exportData, string outputFile);
}

может быть реализовано как

class TabDelimitedExporter : IExporter { ... }
class CsvExporter : IExporter { ... }
class ExcelExporter : IExporter { ... }

Для реализации экспорта из IExportData (что бы это ни было)к CSV-файлу, вам, вероятно, вообще не нужно никакого состояния.ExcelExporter, с другой стороны, может иметь различные свойства для параметров экспорта, но также может не иметь состояния.

[Редактировать]

Перемещение GetInvoices и GetArticles в классе WebService означает, что вы свяжете их реализацию с типом WebService.Наличие их в отдельных классах позволит вам иметь разные реализации как для счетов, так и для статей.В целом, кажется, лучше разделять их.

0 голосов
/ 21 января 2010

Вот классическая версия «глагол против существительного» в ОО:

http://steve -yegge.blogspot.com / 2006/03 / выполнение-в-царстве-оф-nouns.html

0 голосов
/ 19 января 2010

Я думаю, что книга предлагает такой дизайн:

class Article : IIArticleReader
{
    // Article data...

    public IList<Article> GetArticles(); 
}
0 голосов
/ 19 января 2010

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

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

0 голосов
/ 19 января 2010

Фокусируйтесь меньше на имени. Правило о названии - просто эмпирический индикатор плохой практики. Важный момент таков:

Класс, который имеет только поведение, но не содержит данных, обычно не является классом

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

...