Служебный класс (или вспомогательный класс) — это «структура», которая имеет только статические методы и не содержит никакого состояния. StringUtils , IOUtils , FileUtils от Apache Commons ; Iterables и Iterators из Guava , и Files из JDK7 являются прекрасными примерами служебных классов.
Эта идея дизайна очень популярна в мире Java (а также в C #, Ruby и т. Д.), Поскольку служебные классы предоставляют общую функциональность, используемую повсеместно.
Здесь мы хотим следовать принципу СУХОЙ и избежать дублирования. Поэтому мы помещаем общие блоки кода в служебные классы и повторно используем их при необходимости:
|
1
2
3
4
5
6
|
// This is a terrible design, don't reusepublic class NumberUtils { public static int max(int a, int b) { return a > b ? a : b; }} |
Действительно, это очень удобная техника!
Утилиты Зла Злые
Однако в объектно-ориентированном мире служебные классы считаются очень плохой (некоторые даже могут сказать «ужасной») практикой.
Было много дискуссий на эту тему; назвать несколько: Злые ли вспомогательные классы? Ник Малик, Почему помощники, синглтоны и служебные классы в основном плохи от Саймона Харта, Избегание служебных классов от Маршала Уорда, Kill That Util Class! Дхавал Далал, вспомогательные классы — это запах кода от Роба Бэгби.
Кроме того, на StackExchange есть несколько вопросов о служебных классах: Если класс «Утилиты» является злом, куда мне поместить мой общий код? Утилиты Зла .
Краткое изложение всех их аргументов состоит в том, что служебные классы не являются собственными объектами; следовательно, они не вписываются в объектно-ориентированный мир. Они были унаследованы от процедурного программирования, главным образом потому, что тогда большинство из них использовалось для парадигмы функциональной декомпозиции.
Предполагая, что вы согласны с аргументами и хотите прекратить использование служебных классов, я покажу на примере, как эти существа могут быть заменены соответствующими объектами.
Процедурный пример
Например, вы хотите прочитать текстовый файл, разбить его на строки, обрезать каждую строку и затем сохранить результаты в другом файле. Это можно сделать с помощью FileUtils от Apache Commons:
|
1
2
3
4
5
6
7
8
|
void transform(File in, File out) { Collection<String> src = FileUtils.readLines(in, "UTF-8"); Collection<String> dest = new ArrayList<>(src.size()); for (String line : src) { dest.add(line.trim()); } FileUtils.writeLines(out, dest, "UTF-8");} |
Приведенный выше код может выглядеть чисто; однако это процедурное программирование, а не объектно-ориентированное. Мы манипулируем данными (байтами и битами) и явно указываем компьютеру, откуда их извлекать, а затем куда их помещать в каждую строку кода. Мы определяем процедуру исполнения .
Объектно-ориентированная альтернатива
В объектно-ориентированной парадигме мы должны создавать и создавать объекты, позволяя им управлять данными, когда и как они пожелают. Вместо того, чтобы вызывать дополнительные статические функции, мы должны создавать объекты, которые способны демонстрировать поведение, которое мы ищем:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
public class Max implements Number { private final int a; private final int b; public Max(int x, int y) { this.a = x; this.b = y; } @Override public int intValue() { return this.a > this.b ? this.a : this.b; }} |
Это процедурный звонок:
|
1
|
int max = NumberUtils.max(10, 5); |
Станет объектно-ориентированным:
|
1
|
int max = new Max(10, 5).intValue(); |
Картошка, картошка? На самом деле, нет; просто читайте дальше …
Объекты вместо структур данных
Вот как я бы разработал ту же функцию преобразования файлов, что и выше, но в объектно-ориентированном виде:
|
1
2
3
4
5
6
7
8
9
|
void transform(File in, File out) { Collection<String> src = new Trimmed( new FileLines(new UnicodeFile(in)) ); Collection<String> dest = new FileLines( new UnicodeFile(out) ); dest.addAll(src);} |
FileLines реализует Collection<String> и инкапсулирует все операции чтения и записи файлов. Экземпляр FileLines ведет себя как коллекция строк и скрывает все операции ввода-вывода. Когда мы повторяем это — файл читается. Когда мы addAll() в него addAll() — файл пишется.
Trimmed также реализует Collection<String> и инкапсулирует коллекцию строк ( шаблон Decorator ). Каждый раз, когда извлекается следующая строка, она обрезается.
Все классы, принимающие участие во фрагменте, довольно маленькие: FileLines , UnicodeFile и UnicodeFile . Каждый из них отвечает за свою собственную особенность, таким образом, полностью следуя принципу единой ответственности .
С нашей стороны, как пользователи библиотеки, это может быть не так важно, но для их разработчиков это обязательно. Гораздо проще разрабатывать, поддерживать и тестировать FileLines класс FileLines , чем использовать метод readLines() в более 80 методах и в 3000 строк служебного класса FileUtils . Серьезно, посмотрите на его исходный код .
Объектно-ориентированный подход обеспечивает ленивое выполнение. Файл in не читается, пока не потребуются его данные. Если мы не сможем открыть out за какой-то ошибки ввода / вывода, первый файл даже не будет затронут. Все шоу начинается только после того, как мы вызываем addAll() .
Все строки во втором фрагменте, кроме последнего, создают экземпляры и объединяют более мелкие объекты в более крупные. Такая композиция объектов довольно дешевая для процессора, поскольку она не вызывает каких-либо преобразований данных.
Кроме того, очевидно, что второй скрипт выполняется в пространстве O (1), а первый — в O (n). Это является следствием нашего процедурного подхода к данным в первом скрипте.
В объектно-ориентированном мире нет данных; Есть только объекты и их поведение!
Похожие сообщения
Вы также можете найти эти сообщения интересными:
- Почему NULL это плохо?
- Избегайте конкатенации строк
- Объекты должны быть неизменными
- Типичные ошибки в коде Java
| Ссылка: | Альтернатива ООП классам утилит от нашего партнера по JCG Егора Бугаенко в блоге About Programming . |