Утилитарные классы - зло? [закрыто]


97

Я видел эту ветку

Если класс «Утилиты» злой, где мне разместить свой общий код?

и подумал, почему классы утилит злые?

Допустим, у меня есть модель предметной области, состоящая из десятков классов. Мне нужно иметь возможность использовать экземпляры xml-ify. Могу ли я создать метод toXml для родительского элемента? Могу ли я создать вспомогательный класс MyDomainXmlUtility.toXml? Это тот случай, когда бизнес-потребность охватывает всю модель предметной области - действительно ли она относится к методу экземпляра? А что, если есть куча вспомогательных методов по xml-функциональности приложения?


27
Обесценивание термина «зло» - зло!
Мэтью Лок

1
@matthew я сохранил условия поста, на котором основан мой вопрос ...;)
hvgotcodes

Полезные классы - плохая идея по тем же причинам, что и одиночные.
CurtainDog

1
Споры о том, использовать ли метод toXML, сосредоточены на моделях богатой или анемичной предметной области. codeflow.blogspot.com/2007/05/anemic-vs-rich-domain-models.html
Джеймс П.

@james, toXML - это просто пример ... как насчет некоторых функций регулярных выражений, которые используются повсеместно? Например, вам нужно проделать кое-что со строками в вашей модели предметной области, но вы не можете использовать подклассы из-за другой основной проблемы, которая использовала ваш единственный суперкласс (в java)
hvgotcodes

Ответы:


129

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

Однако бывают случаи, когда вы можете использовать служебные классы для группировки нескольких методов вместе - примером является java.util.Collectionsкласс, который предоставляет ряд служебных программ, которые можно использовать в любой коллекции Java. Они не относятся к одному конкретному типу Коллекции, а вместо этого реализуют алгоритмы, которые можно использовать в любой Коллекции.

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


хорошо
согласуется

8
Если язык не предлагает никаких механизмов пространства имен, кроме классов, у вас нет другого выбора, кроме как злоупотреблять классом там, где было бы пространство имен. В C ++ вы можете поместить автономную функцию, не связанную с другими функциями, в пространство имен. В Java вы должны поместить его в класс как статический член (класс).
Unslander Monica

1
Группировка как методы может быть канонической с точки зрения ООП. Однако ООП редко является решением (среди исключений - высокоуровневая архитектура и наборы инструментов виджетов, как один из примеров, когда действительно подходит невероятно нарушенная семантика повторного использования кода путем наследования) и, как правило, методы увеличивают взаимосвязь и зависимости. Тривиальный пример: если вы предоставляете методы принтера / сканера своему классу как методы, вы связываете класс с библиотеками принтера / сканера. Кроме того, вы фиксируете количество возможных реализаций до одной. Если только вы не добавите интерфейс и не увеличите зависимости.
Джо Со

С другой стороны, я согласен с вашим мнением «группировать по назначению и функциональности». Давайте еще раз вернемся к примеру с принтером / сканером и начнем с набора согласованных классов, разработанных для общей цели. Вы можете написать код отладки и разработать текстовое представление для ваших классов. Вы можете реализовать свои отладочные принтеры в одном файле, который зависит от всех этих классов и библиотеки принтеров. Неотладочный код не будет обременен зависимостями этой реализации.
Джо Со

1
Классы Util и Helper - неудачный артефакт таких ограничений, и те, кто не понимает ООП или не понимает проблемную область, продолжают писать процедурный код.
bilelovitch

57

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

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

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

  • Не используйте служебные классы, где статические методы или методы экземпляра в доменном классе были бы лучшим решением. Например, подумайте, будут ли методы в абстрактном базовом классе или создаваемом экземпляре вспомогательного класса лучшим решением.

  • Для Java 8 и новее "методы по умолчанию" в интерфейсе могут быть лучшим вариантом, чем служебные классы. (См., Например, интерфейс с методами по умолчанию и абстрактный класс в Java 8. )


Другой способ взглянуть на этот Вопрос - это заметить, что в процитированном Вопросе «Если классы полезности являются« злыми »» - это соломенный аргумент. Это как я спрашиваю:

«Если свиньи умеют летать, стоит ли носить зонтик?».

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

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


16

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

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

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


2
Я бы назвал это практической проблемой, поскольку, например, в Java вы не можете создавать какие-либо функции, не являющиеся методами в классе. На таком языке «класс без переменных и только со статическими методами» - это идиома, обозначающая пространство имен служебных функций ... Когда сталкиваешься с таким классом в Java, ИМХО неверно и бессмысленно говорить о классе , даже если используется classключевое слово. Он крякает, как пространство имен, он ходит как пространство имен - это одно ...
Unslander Monica

2
Прошу прощения за резкость, но «чудаковатые [...] статические методы без сохранения состояния в философской проблеме [...] системы ООП» крайне ошибочны. Замечательно, если вы можете избежать состояния, потому что это упрощает написание правильного кода. Когда мне нужно писать, это СЛОМАНО, x.equals(y)потому что 1) тот факт, что это действительно не должно изменять какое-либо состояние, не передается (и я не могу полагаться на это, не зная реализации) 2) Я никогда не собирался вставлять x" предпочтительная позиция или позиция, в которой «действовали» по сравнению с y3) Я не хочу рассматривать «идентичности» xили yпросто интересуюсь их ценностями.
Джо Со,

4
На самом деле большая часть функциональных возможностей в реальном мире лучше всего выражается как статические математические функции без сохранения состояния. Math.PI - это объект, который следует изменить? Мне действительно нужно создавать экземпляр AbstractSineCalculator, который реализует IAbstractRealOperation только для получения синуса числа?
Джо Со,

1
@JoSo Я полностью согласен с ценностью безгражданства и прямого вычисления. Наивный объектно-ориентированный объект философски противостоит этому, и я думаю, что это глубоко ошибочно. Он поощряет абстракцию вещей, которые в этом не нуждаются (как вы продемонстрировали), и не может предоставить значимые абстракции для реальных вычислений. Однако сбалансированный подход к использованию объектно-ориентированного языка имеет тенденцию к неизменности и безгражданству, поскольку они способствуют модульности и ремонтопригодности.
Ален О'Ди,

2
@ AlainO'Dea: Я понимаю, что неправильно истолковал ваш комментарий как противовес функциональности без гражданства. Прошу прощения за свой тон - в настоящее время я участвую в своем первом Java-проекте (после того, как я избегал ООП большую часть своих 10 лет программирования) и погребен под слоями абстрактных вещей с неясным состоянием и смыслом. А мне просто нужно подышать свежим воздухом :-).
Jo So

9

Я полагаю, это становится злым, когда

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

Но пока эти условия не выполняются, я считаю, что они очень полезны.


«Присутствуют методы, которые не должны быть статическими». Как это возможно?
Корай Тугай

@KorayTugay Рассмотрим нестатичность в Widget.compareTo(Widget widget)сравнении со статикой WidgetUtils.compare(Widget widgetOne, Widget widgetTwo). Сравнение - это пример того, что нельзя делать статически.
ndm13

5

Практическое правило

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

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

Пример 1. Правильное использование utilклассов / модулей. Пример внешней библиотеки

Предположим, вы пишете приложение, которое управляет ссудами и кредитными картами. Данные из этих модулей предоставляются через веб-службы в jsonформате. Теоретически вы можете вручную преобразовать объекты в строки, которые будут внутри json, но для этого придется изобретать колесо. Правильным решением было бы включить в оба модуля внешнюю библиотеку, используемую для преобразования java-объектов в желаемый формат. (в примере изображения я показал gson )

введите описание изображения здесь


Пример 2. Правильное использование utilклассов / модулей. Написание своего собственного utilбез извинений перед другими членами команды

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

Вот небольшая, но важная деталь. Написанный вами класс / модуль не называется HolidayUtil, но PolishHolidayCalculator. Функционально это utilкласс, но нам удалось избежать общего слова.

введите описание изображения здесь


1
YEd фанат, я вижу;) Замечательное приложение.
Шридхар Сарнобат

3

Служебные классы плохи, потому что означают, что вам было лень придумать лучшее название для класса :)

При этом я ленив. Иногда вам просто нужно выполнить работу, и ваш ум пуст ... вот тогда-то и начинают появляться "служебные" классы.


3

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

У вас также есть JavaScript, где вы можете просто добавить новую функцию прямо к существующему объекту.

Но я не уверен, что действительно есть элегантный способ решить эту проблему на более старом языке, таком как C ++ ...

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

А когда у вас ограниченный бюджет, ваш босс не всегда рад видеть, что вы весь день пишете кучу уроков ...


3

Я не совсем согласен с тем, что служебные классы - это зло.

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

Например, представьте, что вам нужна функция, которая очистит строку от всех подстрок, соответствующих значению x.

stl c ++ (на данный момент) не поддерживает это напрямую.

Вы можете создать полиморфное расширение std::string.

Но проблема в том, действительно ли вы хотите, чтобы КАЖДАЯ строка, которую вы используете в своем проекте, была вашим расширенным строковым классом?

Бывают случаи, когда объектно-ориентированный подход не имеет смысла, и это один из них. Мы хотим, чтобы наша программа была совместима с другими программами, поэтому мы будем придерживаться std::stringи создать класс StringUtil_(или что-то еще).

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


2

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

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


2

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



1

Большинство классов Util плохи, потому что:

  1. Они расширяют набор методов. Они делают общедоступным код, который в противном случае был бы частным. Если метод util требуется нескольким вызывающим объектам в отдельных классах и является стабильным (т.е. не требует обновления), на мой взгляд, лучше скопировать и вставить частные вспомогательные методы в вызывающий класс. После того, как вы представите его как API, вам станет труднее понять, что такое общедоступная точка входа в компонент jar (вы поддерживаете древовидную структуру, называемую иерархией, с одним родителем на метод. Это легче мысленно разделить на компоненты, что сложнее, когда у вас есть методы, вызываемые из нескольких родительских методов).
  2. Они приводят к мертвому коду. Методы использования со временем перестают использоваться по мере развития вашего приложения, и вы получаете неиспользуемый код, загрязняющий вашу базу кода. Если бы он оставался закрытым, ваш компилятор сказал бы вам, что метод не используется, и вы могли бы просто удалить его (лучший код - это вообще не код). Как только вы сделаете такой метод закрытым, ваш компьютер будет бессилен помочь вам удалить неиспользуемый код. Насколько известно компьютеру, он может быть вызван из другого jar-файла.

Есть некоторые аналогии статических и динамических библиотек.


0

Когда я не могу добавить метод к классу (скажем, Accountзаблокирован от изменений младшими разработчиками), я просто добавляю несколько статических методов в свой класс Utilities, например:

public static int method01_Account(Object o, String... args) {
    Account acc = (Account)o;
    ...
    return acc.getInt();
}  

1
Похоже, ты для меня младший ..: P Неплохой способ сделать это - вежливо попросить своего "младшего разработчика" разблокировать Accountфайл. Или лучше использовать неблокирующие системы управления версиями.
Sudeep

1
Я предполагаю, что он имел в виду, что Accountфайл был заблокирован так, что разработчики-младшие не могут вносить в него изменения. Как младший разработчик, чтобы обойти это, он сделал то, что описано выше. Тем не менее, все же лучше просто получить доступ на запись к файлу и сделать это правильно.
Док

Добавьте +1 к этому, потому что отрицательные голоса кажутся резкими, когда никто не знает полного контекста того, почему вы предоставили этот ответ
Joelc

0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.