Классы, которых следует избегать (код завершен)

StackOverflow https://stackoverflow.com/questions/2094609

  •  21-09-2019
  •  | 
  •  

Вопрос

Я несколько сбит с толку одним абзацем в книге code complete.

В разделе "Классы, которых следует избегать" говорится:

"Избегайте классов, названных в честь глаголов Класс, который имеет только поведение, но не имеет данных, как правило, на самом деле не является классом.Рассмотрите возможность превращения класса, такого как 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();
}

Редактировать Спасибо за все ответы.

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

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

Это было полезно?

Решение

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

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

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

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

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

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

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

Другие советы

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

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

Мое предложение было бы таким:Если вы обнаружите, что создаете класс "verb", который воздействует на один класс "noun", честно подумайте о том, может ли класс "noun" выполнить это действие сам по себе.Но не начинайте создавать Бог Возражает или придумывайте бессмысленные / вводящие в заблуждение имена с единственной целью избежать глагольного класса.

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

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

Пожалуйста, обратите внимание на использование слова "Избегать".Это не значит устранить, или искоренить, или гореть в аду, если вы когда-нибудь ими воспользуетесь.

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

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

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

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

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

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

Идея заключается в том, что

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

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

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

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

нет никакого преимущества в том, чтобы поместить его в объект.

Заявление Класс, который имеет только поведение, но не имеет данных, как правило, на самом деле не является классом. это просто неправильно.

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

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

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

Например:

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.Наличие их в отдельных классах позволит вам иметь разные реализации как для счетов-фактур, так и для статей.В целом, кажется, лучше, чтобы они были разделены.

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

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

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

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

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

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

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

    public IList<Article> GetArticles(); 
}

Вот классический взгляд на "глагол против существительного" в OO:

http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top