Полезные уроки.. Хорошо или плохо?
Я читал, что создание зависимостей с использованием статических классов / синглетонов в коде, является плохой формой, и создает проблемы, т.е. крепкое сцепление и модульное тестирование.
У меня есть ситуация, когда у меня есть группа методов анализа URL, с которыми не связано ни одно состояние, и выполняю операции, используя только входные аргументы метода. Я уверен, что вы знакомы с этим методом.
В прошлом я бы продолжал создавать класс и добавлять эти методы и вызывать их прямо из моего кода, например.
UrlParser.ParseUrl(url);
Но подождите, это введение зависимости в другой класс. Я не уверен, являются ли эти "полезные" классы плохими, так как они не имеют состояния, и это минимизирует некоторые проблемы с указанными статическими классами и синглетонами. Может ли кто-нибудь уточнить это?
Должен ли я переместить методы в вызывающий класс, то есть, если только вызывающий класс будет использовать метод. Это может нарушать "Принцип единой ответственности".
9 ответов
С теоретической точки зрения дизайна, я чувствую, что полезных классов следует избегать, когда это возможно. Они в основном не отличаются от статических классов (хотя и немного лучше, так как у них нет состояния).
Однако с практической точки зрения я создаю их и поощряю их использование при необходимости. Попытка избежать использования служебных классов часто обременительна и приводит к уменьшению объема поддерживаемого кода. Тем не менее, я стараюсь поощрять моих разработчиков избегать их в публичных API, когда это возможно.
Например, в вашем случае я чувствую, что UrlParser.ParseUrl(...), вероятно, лучше обрабатывать как класс. Посмотрите на System.Uri в BCL - он обрабатывает чистый, простой в использовании интерфейс для Uniform Resource Indentifiers, который работает хорошо и поддерживает фактическое состояние. Я предпочитаю этот подход вспомогательному методу, который работает со строками и заставляет пользователя передавать строку, не забывать проверять ее и т. Д.
Полезные классы в порядке..... до тех пор, пока они не нарушают принципы проектирования. Используйте их так же счастливо, как и базовые классы фреймворка.
Классы должны быть хорошо названы и логичны. На самом деле они не столько "полезны", сколько являются частью новой фреймворк, которую не предоставляют нативные классы.
Использование таких вещей, как методы Extension, также может быть полезно для выравнивания функциональности по "правильному" классу. НО, они могут быть причиной некоторой путаницы, поскольку расширения не упакованы с классом, который они обычно расширяют, что не является идеальным, но, тем не менее, может быть очень полезным и производить более чистый код.
Это действительно зависит от контекста и от того, как мы его используем.
Сервисные классы, само по себе, неплохо. Тем не менее, это станет плохо, если мы будем использовать это плохим способом. Любой шаблон проектирования (особенно шаблон Singleton) можно легко превратить в шаблон, то же самое касается классов Utility.
При разработке программного обеспечения нам необходим баланс между гибкостью и простотой. Если мы собираемся создать StringUtils, который отвечает только за манипуляции со строками:
- Это нарушает SRP (принцип единой ответственности)? -> Нет, это разработчики, которые возлагают слишком много ответственности на служебные классы, которые нарушают SRP.
- "Он не может быть введен с использованием DI-фреймворков" -> Будет ли изменяться реализация StringUtils? Мы собираемся переключить его реализации во время выполнения? Мы будем издеваться над этим? Конечно, нет.
=> Полезные классы, сами по себе, не плохие. Это ошибка разработчиков, которые делают это плохо.
Все действительно зависит от контекста. Если вы просто собираетесь создать служебный класс, который содержит только одну ответственность и используется только конфиденциально внутри модуля или слоя. Тогда ты все еще хорош в этом.
Вы всегда можете создать интерфейс и использовать его с внедрением зависимости с экземплярами классов, которые реализуют этот интерфейс вместо статических классов.
Возникает вопрос, действительно ли это стоит усилий? В некоторых системах ответ да, но в других, особенно небольших, ответ, вероятно, нет.
Служебные классы, используемые таким образом, в основном являются пространствами имен для того, что в противном случае было бы (чистыми) функциями верхнего уровня.
С архитектурной точки зрения нет никакой разницы, используете ли вы чистые «глобальные» функции верхнего уровня или базовые (*) чисто статические методы. Любые плюсы или минусы одного в равной степени применимы и к другому.
Статические методы против глобальных функций
Основным аргументом в пользу использования служебных классов вместо глобальных («плавающих») функций является организация кода, структура файлов и каталогов, а также имена:
- Возможно, у вас уже есть соглашение о структурировании файлов классов в каталогах по пространству имен, но у вас может не быть хорошего соглашения для функций верхнего уровня.
- Для контроля версий (например, git) может быть предпочтительнее иметь отдельный файл для каждой функции, но по другим причинам может быть предпочтительнее иметь их в одном файле.
- Ваш язык может иметь механизм автозагрузки для классов, но не для функций. (Я думаю, что это в основном относится к PHP)
- Вы можете предпочесть написать
import Acme:::Url; Url::parse(url)
надimport function Acme:::parse_url; parse_url();
. Или вы можете предпочесть последнее. - Вы должны проверить, позволяет ли ваш язык передавать статические методы и/или функции верхнего уровня в качестве значений. Возможно, некоторые языки позволяют только одно, но не другое.
Так что это во многом зависит от языка, который вы используете, и соглашений в вашем проекте, фреймворке или программной экосистеме.
(*) Вы можете иметь закрытые или защищенные методы в служебном классе или даже использовать наследование, чего нельзя делать с функциями верхнего уровня. Но чаще всего это не то, что вы хотите.
Статические методы/функции против методов объекта
Основное преимущество объектных методов заключается в том, что вы можете внедрить объект, а затем заменить его другой реализацией с другим поведением. Вызов статического метода напрямую работает хорошо, если вам никогда не нужно его заменять. Обычно это происходит, если:
- функция чистая (без побочных эффектов, не зависит от внутреннего или внешнего состояния)
- любое альтернативное поведение будет считаться неправильным или крайне странным. Например, 1 + 1 всегда должно быть 2. Нет причин для альтернативной реализации, где 1 + 1 = 3.
Вы также можете решить, что статический вызов "на данный момент достаточно хорош".
И даже если вы начнете со статических методов, позже вы сможете сделать их внедряемыми/подключаемыми. Либо с помощью функций/вызываемых значений, либо с помощью небольших классов-оболочек с объектными методами, которые внутренне вызывают статический метод.
Я согласен с некоторыми другими ответами здесь, что это классический синглтон, который поддерживает единственный экземпляр объекта с состоянием, которого следует избегать, и не обязательно вспомогательные классы без состояния, которое является злым. Я также согласен с Ридом в том, что, если это вообще возможно, поместите эти служебные методы в класс, где это имеет смысл, и где можно логически подозревать, что такие методы будут находиться. Я бы добавил, что часто эти статические служебные методы могут быть хорошими кандидатами на методы расширения.
Я действительно, очень стараюсь избегать их, но кого мы обманываем... они проникают в каждую систему. Тем не менее, в данном примере я использовал объект URL, который затем выставлял бы различные атрибуты URL (параметры протокола, домена, пути и строки запроса). Практически каждый раз, когда я хочу создать служебный класс статики, я могу получить большую ценность, создав объект, который выполняет эту работу.
Подобным образом я создал много пользовательских элементов управления, которые встроили проверку для таких вещей, как проценты, валюта, номера телефонов и тому подобное. До этого у меня был служебный класс Parser, в котором были все эти правила, но гораздо проще было просто отбросить элемент управления на странице, которая уже знает основные правила (и, следовательно, требует добавления только проверки бизнес-логики).,
Я по-прежнему сохраняю класс утилиты синтаксического анализа, и эти элементы управления скрывают этот статический класс, но широко его используют (сохраняя весь анализ в одном легко доступном месте). В связи с этим я считаю приемлемым иметь служебный класс, потому что он позволяет мне применять "Не повторяй себя", в то время как я получаю преимущество от экземпляров классов с элементами управления или другими объектами, которые используют утилиты.
Они в порядке, если вы хорошо их оформляете (то есть вам не нужно время от времени менять их подпись).
Эти служебные методы меняются не так часто, потому что они делают только одну вещь. Проблема возникает, когда вы хотите привязать более сложный объект к другому. Если один из них необходимо изменить или заменить, это будет сложнее, если у вас есть сильная связь.
Поскольку эти служебные методы не будут меняться так часто, я бы сказал, что это не большая проблема.
Я думаю, что было бы хуже, если бы вы копировали / вставляли один и тот же служебный метод снова и снова.
В этом видео " Как спроектировать хороший API и почему это важно " Джошуа Блоха объясняется несколько концепций, которые следует учитывать при разработке API (это будет ваша служебная библиотека). Хотя он является признанным Java-архитектором, его содержание применимо ко всем языкам программирования.
Используйте их экономно, вы хотите использовать в своих классах как можно больше логики, чтобы они не стали просто контейнерами данных.
Но в то же время вы не можете избежать утилит, иногда они требуются.
В этом случае я думаю, что все в порядке.
К вашему сведению, существует класс system.web.httputility, который содержит множество общих утилит http, которые вы можете найти полезными.