Готовясь к собеседованию, я просмотрел не мало mock интервью на позицию Junior/Middle Java Developer, и понял что многие программисты валятся на таком простом вопросе как - опишите те или иные паттерны программирования. Да, конечно, я не имею ввиду то, что мы в этот момент сидим с потерянными глазами и вытянув руки молчим, словно это вопрос на миллион, совсем нет, на этот вопрос у нас уже еле сдерживая зубы так и вылетает: фабрикааа.. абстрактн... сииингелтон... прототааайп... Но у меня, и я думаю, я такой не один, сложилось мнение, что называть данные паттерны, ну.. моветон что ли какой-то уже. Советую придержать их на самое вкусненькое, на заключительный момент, на контрольный выстрел, когда ты уже рассказал о других не очевидных паттернах, и тут как скороговорку уже по разогретому: ну да, еще конечно часто приходится работать с фабрикой, сингелтоном, протайпом и так далее (важно закончить "так далее", тем самым оставить впечатление - ну, мол, это и так очевидно, что я знаю)
В данной статье я не собираюсь открыть что-то грандиозное, нет, я всего лишь хочу на примере кода, с которыми мы как Java программисты уже знакомы в Java Core, показать 5 обыкновенных паттернов, о которых вы, наверняка, уже слышали. Моя цель простая - указать на тот код с которым мы работаем и открыть простую истину - паттерны везде, стоит лишь заметить и, надеюсь, увидев раз, как снежный ком, эти простые паттерны будут вас преследовать везде, день ото дня. Проще говоря - я собираюсь создать триггер (об этом в конце статьи, лирическое отступление).
Те самые 5 паттернов:
Стратегия
Декоратор
Фасад
Итератор
Шаблонный метод
Стратегия
Стратегия (Strategy) - поведенческий патерн.
Его задача выделить схожие алгоритмы решающие конкретную задачу. Реализация алгоритмов выносится в отдельные классы и представляется возможность выбирать алгоритм во время выполнения программы.
Теперь ответьте себе - какой пример паттерна стратегия вы знаете в Java Core ?
Все просто - Comparator
. Да тот самый компаратор который вряд ли кого-то обошел стороной.
Для примера возьмем интерфейс List
и его метод sort
:
default void sort(Comparator<? super E> c) {
...
}
Как мы видим сам алгоритм сортировки в данном методе вынесли в отдельный интерфейс, и нам не составит труда создать свой алгоритм реализовав данный интерфейс удобным нам способом.
Не грешно ли теперь не вспомнить о данном паттерне на собеседовании ? Я молчу еще о том, что его стоит назвать одним из первых...
Декоратор
Декоратор (Decorator) - структурный паттерн.
Предназначен для динамического подключения дополнительного поведения объекту.
Теперь ответьте мне - какой пример паттерна декоратор вы знаете в Java Core ? Все просто - BufferedInputStream
. На самом деле вернее было бы сказать FilterInputStream
, так как именно в нем инкапсулирована вся логика для реализации паттерна декоратор, а BufferedInputStream
уже частный случай.
Обратимся к документации, и в ней сказано:
FilterInputStream
содержит некоторый другой входной поток, который он использует в качестве основного источника данных, возможно, преобразуя данные по пути или предоставляя дополнительную функциональность.
Ну как это если не то, что нужно для создания декоратора ?
Приведу пример только используя FilterReader
и BufferedReader
, которые выполняют те же задачи, только больше подходят для чтения символов из файла и для моего примера.
Представим что мы отказались от буквы ё
которая самовластно вытеснила со всех слов и предложений старую и добрую буковку е
. Что же нам делать ? Впрочем ничего сложного, нам просто нужно декорировать уже имеющиеся у нас потоки, что бы заменить все необходимые буквы.
Представим что раньше у нас было так:
public static void main(String[] args) throws IOException {
try (var buffered = new BufferedReader(Files.newBufferedReader(Path.of("resources", "file.txt")))){
int b;
while ((b = buffered.read()) != -1) {
System.out.print((char) b);
}
}
}
А теперь мы просто создали класс, который наследует FilterReader
и переопределяем, в нашем случае, один его метод read
:
public class SpellingInputStream extends FilterReader {
protected SpellingInputStream(Reader in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return c == 'ё' ? 'е' : c == 'Ё'? 'Е': c;
}
}
В итоге вы просто оборачиваем один декоратор в другой и можем прощаться с буковкой ё
:
public static void main(String[] args) throws IOException {
try (var buffered = new SpellingInputStream(new BufferedReader(Files.newBufferedReader(Path.of("resources", "file.txt"))))){
int b;
while ((b = buffered.read()) != -1) {
System.out.print((char) b);
}
}
}
Фасад
Фасад (Facade) - структурный паттерн.
Данный паттерн представляет унифицированный интерфейс вместо набора интерфейсов некоторой подсистемы. Фасад представляет интерфейс более высокого уровня, который упрощает использование подсистемы.
Как никогда кстати нам пример описанный выше при разборе паттерна декоратор. Я думаю у любого начинающего java разработчика потоки ввода и вывода оставляли много вопросов, которые когда-то все таки желательно разобрать. Нооо.. если нам прямо и сейчас нужно быстро и удобно прочитать весь небольшой файл, что же нам остается ? Плодить кучу потоков в потоках и потоком погонять ? И тут нам на помощь стремится утилитный класс, который как многие другие утилитные классы, являются ярким примером реализации паттерна фасад.
Теперь ответьте себе - какой пример паттерна фасад вы знаете в Java Core ? Правильно, это утилитный класс Files
. Данный класс в себе хранит кучу полезных методов которые упрощают нам работу с файловой подсистемой.
Представим нам нужно вывести в консоль содержимое файла:
public static void main(String[] args) throws IOException {
try (var buffered = new BufferedReader(new FileReader(Path.of("resources", "file.txt").toFile()))){
String str;
while ((str = buffered.readLine()) != null) {
System.out.println(str);
}
}
}
Или используем наш утилитный класс:
public static void main(String[] args) throws IOException {
System.out.println(
Files.readString(Path.of("resources", "file.txt"))
);
}
Преимущество даже не в том что мы написали меньше кода, а в том, что во втором примере болей легкий для понимания и использования код. В этом и заключается работа фасада, он нас освобождает от необходимости быть вовлеченным в сложность работы подсистемы.
Итератор
Итератор (Iterator) - поведенческий паттерн.
Предоставляет последовательный доступ ко всем элементам составного объекта, не раскрывая его внутреннего представления.
Да, наверное, по частоте использования данный паттерн опередил бы саму стратегию, но все же я пожелал разместить его в данном месте.
Конечно, чаще всего мы используем его не очевидно, но я все равно спрошу:
Какой пример паттерна итератор вы знаете в Java Core ? Правильно, это интерфейс Iterator
, а точнее его реализации, коих много при работе, например, с коллекциями. Именно итератор нам помогает использовать for-each удобный цикл для доступа к элементам объектов. И не нужно забывать, что в принципе работы всех реализаций итераторов лежит сама суть паттерна итератор.
Теперь ответьте, как можно не отметить данный паттерн, когда речь о них заходит? Только не говорите, что вы забываете о такой базовой штуке как цикл во время собеседования. Просто вам нужно хорошо запомнить - благодаря чему нам так легко проходится по элементам решая повседневные задачи. Буду занудой и еще раз скажу - это паттерн итератор, в виде реализаций интерфейса Iterator
в Java Core.
За примером далеко ходить не надо, ведь базовый интерфейс всех коллекций Collection
реализует интерфейс Iterable
, который главным образом возвращает Iterator
public interface Iterable<T> {
Iterator<T> iterator();
//...
}
Так же главный принцип данного паттерна в том, что мы не раскрываем внутреннего представления и наша любимая Java этого придерживается, или, вернее сказать, предоставляет нам инструменты, что бы наш код мог следовать данному правилу. Не буду приводить пример на этот счет, так как в интернете при любом обзоре на данный интерфейс вы найдете этому подтверждение.
Шаблонный метод
Шаблонный метод (template method) - поведенческий паттерн.
Служит одной цели - программированию шагов некоторого алгоритма в семействе классов произведенных от базового класса, определяющего структуру этого самого класса. Например, если паттерн стратегия меняет поведение исходя из настроек, заданных клиентом, то шаблонный метод же направлен на создание иерархии классов, реализующих общие алгоритмы с некоторой модификацией
Пример данного паттерна самый неочевидный из всех выше описанных, но по примеру остальных, я все равно вас спрошу: Какой пример паттерна шаблонный метод вы знаете в Java Core ? Правильно, это - AbstractList
. От данного класса есть много производных классов, а так как этот класс помечен ключевым словом abstact
, то экземпляр данного класса, в принципе, сам по себе не может существовать. В данном классе есть методы намеренно помеченные так же abstact
, что обязывает потомков реализовывать данные методы, но так же в данном классе есть методы, что используют эти абстрактные методы, и будут успешно выполнять свою задачу с любым потомком, так как полиморфизм предоставляет нам такие возможности, которые являются ключевыми в реализации паттерна шаблонный метод.
Внутри AbstractList
есть нам уже известный внутренний класс реализующий интерфейс Iterator
, а он в свою очередь в своем методе обращается к абстрактному методу get
:
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException(e);
}
}
Принцип шаблонного паттерна нас призывает реализовывать методы в соответствии с теми целями, для которых он был создан, только так общие алгоритмы будут работать везде предсказуемо, как то и требуется. В примере выше, все реализации AbstractList
будут успешно использовать один и тот же next
метод итератора, если верно его реализуют.
Так же стоит отметить, что в Java любой метод по умолчанию может быть переопределен, если только мы не указали ключевое слово
final
.
В чем сложность
Хочу отметить, что иной раз легче применить тот или иной паттерн чем определить его название, так как их применение не всегда так однородно для каждого случая, это объясняется еще тем, что обычно мы решаем комплексные задачи, и они имеют такое же комплектное решение, поэтому даже те примеры, что я описал выше для каждого паттерна, имеют место для обсуждения, и кто-то найдет для них более подходящие паттерны в силу своего видения, важно для себя определить - почему вы решили, что для эдакой задачи вы будете использовать паттерн декоратор, а вон для той будете использовать паттерн стратегия, и будет вам счастье.
Для примера возьму вот такой код:
public static void main(String[] args) throws IOException {
Files.lines(
Path.of("resources", "file.txt")
);
}
Немного расслабившись, ответьте на мой последний вопрос: какой паттерн по вашему мнению применен в реализации метода Files.lines
. Правильно.. это.. Сейчас не могу оценить ваши ответы, буду ждать их в комментариях, но заранее могу сказать, что это ваше мнение, и в таком деле вы можете гнуть свою линию.
Но вот на мой субъективный взгляд тут как минимуму задействовано два паттерна: фасад и итератор.
P.S. химеры и триггеры...
Здесь нет ничего полезного, просто немного лирики....
Химеры... Они есть и их как бы нет, мы их не замечаем в упор - о них говорят, мы даже соприкасаемся с ними, но осознать не можем... до поры до времени. Возьмем, например, свободу - мать мировых революций. В частности, французская революция невероятным всплеском пробудилась и прошла свой "славный" путь со словами на устах - "Свобода, Равенство, Братство". Но разве люди не думали о свободе до тех памятных событий ?!Разве ее пленительность не была очевидна до?! Но так или иначе в какой-то момент эти слова заразили общество. Спрашивается - случайным ли образом ? Здесь что-то нечисто... Я думаю, тут без тайного оружия не обошлось, и я даже дам этому оружию название - "боевая химера". Оружие что нацелено на наши головы, что делает из ничего - идею фикс. Идея которая тайно набирает силу, конечно, не без жертв, так как ее сила это чья та отнятая жизнь, да, жертвоприношения это вам не бессмысленный суеверный обряд. Властители мира издавна использовали данный инструмент что бы влиять на умы своего окружения. Мир меняется, жертвы человеческие сменили жертвы животных, но в более огромных количествах... да, скотобойни теперь это самый огромный фактор производства "боевых химер". Но не достаточно напитать силой химеру, ее еще нужно активировать в нужный момент, эдакая ментальная бомба для пробуждения химеры, именно за данную часть отвечает "триггеры", которые вместе с жертвоприношениями создают идеи которые меняют мир, а вы думали, что все так просто ?!
Хочу отметить, что вы уже прочитали данную статью, для вас теперь химера обросла плотью, и вы не сможете представить свою жизнь без паттернов - это отныне ваша идея фикс, а эта статья и есть триггер.
Сразу хочу отметить - в создании данной химеры не пострадало ни одно живое существо, хватило лишь энное число моих мертвых нервных клеток.