Про паттерны и собеседования (и про тот-паттерн-который-нельзя-называть)
На собеседованиях часто спрашивают про паттерны (что-то вроде «Расскажи про паттерны. Какие знаешь?»).
Хочется сразу сделать несколько оговорок:
1️⃣ когда говорят "паттерны", обычно подразумевают паттерны проектирования или Design Patterns - из известной книги "банды четырёх". Однако, паттерны – более широкое понятие. Есть паттерны микросервисной архитектуры, паттерны concurrency (вроде Thread Pool), паттерны интеграции (например, Circuit Breaker)
2️⃣ у "банды четырёх" 23 паттерна, но не все из них часто используются (возможно, некоторые даже не выходили за пределы страниц книги 😳 )
3️⃣ точнее - немногие из них программисту приходится реализовывать руками, потому что они используются под капотом во фреймворках и готовых библиотеках.
Например, Dependency Injection или Proxy в спринге — это уже готовые решения. Про такие надо знать, даже если не писал их ни разу самостоятельно, чтобы понимать, как оно работает внутри, и какие могут быть подводные камни
4️⃣ и последнее: паттерны — не rocket science, и при некотором упорстве многие из них можно изобрести самостоятельно, задавшись целью оптимизировать свой код. Но зачем, если их уже придумали за нас?
В общем-то, можно поделиться с собеседующим подобными размышлениями, и это будет неплохое начало ответа. А после добавить конкретики и — ВАЖНО — личного опыта работы с паттернами.
➡️ Ссылаться на личный опыт — в целом выигрышный приём при ответе на любой вопрос. Это покажет, что вы не просто запомнили нужные слова, а действительно понимаете, о чём говорите.
Так вот, после этого надо перейти к конкретике…
а конкретика в следующем посте
👻 Но главное – никогда не говорите про паттерн«синглтон»
Просто не произносите это слово вслух, поберегите себя
На собеседованиях часто спрашивают про паттерны (что-то вроде «Расскажи про паттерны. Какие знаешь?»).
Хочется сразу сделать несколько оговорок:
даже банальный DTO - тоже паттерн
(кстати, описан Фаулером в книге Patterns of Enterprise Application Architecture), т.е. можно сказать, что это архитектурный или же прикладной паттерн, в отличие в паттернов банды четырех, которые сфокусированы на объектно-ориентированном проектировании
Например, Dependency Injection или Proxy в спринге — это уже готовые решения. Про такие надо знать, даже если не писал их ни разу самостоятельно, чтобы понимать, как оно работает внутри, и какие могут быть подводные камни
В общем-то, можно поделиться с собеседующим подобными размышлениями, и это будет неплохое начало ответа. А после добавить конкретики и — ВАЖНО — личного опыта работы с паттернами.
Сделайте это своим паттерном прохождения технических интервью!
Так вот, после этого надо перейти к конкретике…
а конкретика в следующем посте
👻 Но главное – никогда не говорите про паттерн
Просто не произносите это слово вслух, поберегите себя
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥13👍4👏1
Обещанное продолжение про паттерны.
В прошлом посте были рассуждения о паттернах в целом, которыми можно поделиться на собеседовании, чтобы все поняли какой вы пирожочек. А теперь конкретика:
Паттерны делятся на три вида по их назначению:
1️⃣Порождающие (Creational) — решают, как создавать объекты. Например, Factory или Builder
2️⃣Структурные (Structural) — помогают выстраивать структуру системы. Adapter, Decorator - про то, как объекты и классы "складываются" вместе
3️⃣Поведенческие (Behavioral) — отвечают за взаимодействие объектов и разделение обязанностей. Strategy, Observer, Command — про поведение и логику
В Спринге паттерны встречаются повсюду:
➡️ Factory (порождающий) — BeanFactory или ApplicationContext.
➡️ Proxy (структурный) — используется для аспектов, транзакций
➡️ Chain of Responsibility (поведенческий) — фильтры в Spring Security
➡️ Builder (порождающий) — например, в RestTemplateBuilder
🤔 Что касается реализации паттернов руками (добавляете личный опыт к сухой теории) - недавно делал(а) Стратегию - это поведенческий паттерн для формирования семейства алгоритмов. Ситуация такая: есть объект, и некоторая операция над ним может осуществляться по-разному в зависимости от какого-то его параметра.
Можно было сделать через switch-case или if-else, но метод бы раздулся, да и новые варианты, которые точно будут в будущем добавляться, внедрять неудобно (обоснование, почему применение паттерна было целесообразно)
Как именно реализовал стратегию:
1️⃣ Написал интерфейс с одним методом,
например process, означающим ту самую вариативную операцию (метод принимает самого юзера, с которым надо работать, тут для себя вспомните про передачу объектов в джаве – в методе можно изменять существующий объект, пусть void не смущает):
2️⃣ Написал его реализации (имплементации)
(количество реализаций равно количеству вариантов бизнес-логики операции, в этом случае 2 - для обычных и для премиум юзеров):
3️⃣ Как это использовать?
В UserProcessor (или там где у нас выполняется операция над юзером) внедряем интерфейс UserStrategy как поле класса. А раз у этого интерфейса несколько реализаций, то можно передать любую из них (= инициализировать поле strategy объектом любого из этих классов, привет полиморфизм).
После для strategy вызываем тот самый метод process (внутри метода execute), и отрабатывает нужный нам вариант логики (т.е. переопределенный метод из конкретной реализации)
4️⃣ Ну и использовал в логике приложения
Есть юзер Иван, а у него поле со значением "premium". Иван мог бы быть и обычным юзером, но раз он премиум, то передаем в конструктор UserProcessor-а обработчик PremiumUserStrategy, далее передаем в метод execute Ивана
🥳 Получился нормальный ответ про паттерны.
❗️ Но в реальности, как правило, мы пишем приложения на Спринге, поэтому позже расскажу как реализовать такую схему с помощью спрингового DI (dependency injection), там просто кайф. Тогда картина станет полной
В прошлом посте были рассуждения о паттернах в целом, которыми можно поделиться на собеседовании, чтобы все поняли какой вы пирожочек. А теперь конкретика:
и да – представляем, что разговор идет в контексте ООП-design patterns от банды четырех
Паттерны делятся на три вида по их назначению:
1️⃣Порождающие (Creational) — решают, как создавать объекты. Например, Factory или Builder
2️⃣Структурные (Structural) — помогают выстраивать структуру системы. Adapter, Decorator - про то, как объекты и классы "складываются" вместе
3️⃣Поведенческие (Behavioral) — отвечают за взаимодействие объектов и разделение обязанностей. Strategy, Observer, Command — про поведение и логику
В Спринге паттерны встречаются повсюду:
Например, обработка юзера отличается в зависимости от его типа (обычный или премиум) – тут приведите пример из своей предметной области
Можно было сделать через switch-case или if-else, но метод бы раздулся, да и новые варианты, которые точно будут в будущем добавляться, внедрять неудобно (обоснование, почему применение паттерна было целесообразно)
Как именно реализовал стратегию:
1️⃣ Написал интерфейс с одним методом,
например process, означающим ту самую вариативную операцию (метод принимает самого юзера, с которым надо работать, тут для себя вспомните про передачу объектов в джаве – в методе можно изменять существующий объект, пусть void не смущает):
public interface UserStrategy {
void process(User user);
}2️⃣ Написал его реализации (имплементации)
(количество реализаций равно количеству вариантов бизнес-логики операции, в этом случае 2 - для обычных и для премиум юзеров):
public class RegularUserStrategy implements UserStrategy {
@Override
public void process(User user) {
// Логика обработки обычного юзера
}
}
public class PremiumUserStrategy implements UserStrategy {
@Override
public void process(User user) {
// Логика обработки премиум юзера с плюшками
}
}3️⃣ Как это использовать?
В UserProcessor (или там где у нас выполняется операция над юзером) внедряем интерфейс UserStrategy как поле класса. А раз у этого интерфейса несколько реализаций, то можно передать любую из них (= инициализировать поле strategy объектом любого из этих классов, привет полиморфизм).
После для strategy вызываем тот самый метод process (внутри метода execute), и отрабатывает нужный нам вариант логики (т.е. переопределенный метод из конкретной реализации)
public class UserProcessor {
private final UserStrategy strategy;
public UserProcessor(UserStrategy strategy) {
this.strategy = strategy;
}
public void execute(User user) {
strategy.process(user);
}
}4️⃣ Ну и использовал в логике приложения
Есть юзер Иван, а у него поле со значением "premium". Иван мог бы быть и обычным юзером, но раз он премиум, то передаем в конструктор UserProcessor-а обработчик PremiumUserStrategy, далее передаем в метод execute Ивана
User user = new User(“Иван”, “premium”);
UserProcessor processor = new UserProcessor(new PremiumUserStrategy());
processor.execute(user);
Напомню, что пример вам нужно выбрать из вашего домена и приложения, а чтобы рассказывать об этом уверенно, надо действительно сделать это своими руками.
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥13❤3👏2
В последней Intellij Idea 2024.3.4 появилась классная фича -
Нет, там много классных фич, на эта стоит прям на первом месте под заголовком "Главное".
Касается панели "Structure", которая раньше была так себе, а теперь прям вау.
Не буду утомлять текстом, смотрите скрины на примере простенького проекта одного из моих менти (скрин собственно из "What`s New in IntelliJ IDEA" там же в конце)
логическая структура проекта.
Нет, там много классных фич, на эта стоит прям на первом месте под заголовком "Главное".
Касается панели "Structure", которая раньше была так себе, а теперь прям вау.
Не буду утомлять текстом, смотрите скрины на примере простенького проекта одного из моих менти (скрин собственно из "What`s New in IntelliJ IDEA" там же в конце)
6👍13🔥6🤔2❤1👏1
Про Стратегию в Spring-приложении.
В этом посте писал о том, что в реальности Стратегия чаще применяется не совсем так, как это обычно показывается в книгах по ООП-паттернам.
Вдруг понял, что в тексте это сложно преподнести так, как я хочу, поэтому в творческом кураже запилил первое на канале видео:
Код на гитхабе - ReportStrategy
Примечание: в видео не всё учтено (не судите строго, хотя нет - судите, можете даже писать гневные комменты, и чем больше - тем лучше 🤔 ), так что в репозиторий внесены некоторые дополнения с комментариями:
➡️ поля в стратегиях объявлены как final
➡️ добавлена валидация списка стратегий во избежание NPE в рантайме
➡️ используется EnumMap
В этом посте писал о том, что в реальности Стратегия чаще применяется не совсем так, как это обычно показывается в книгах по ООП-паттернам.
Вдруг понял, что в тексте это сложно преподнести так, как я хочу, поэтому в творческом кураже запилил первое на канале видео:
📺 Youtube
📺 Rutube
📺 ВК-видео
Код на гитхабе - ReportStrategy
Примечание: в видео не всё учтено (
Please open Telegram to view this post
VIEW IN TELEGRAM
7🔥11👍8⚡4❤2
Клиент -> запрос -> сервер. Давайте попробуем "сказать людям" 😄 что веб-разработка всё же несколько сложнее, чем на этой картинке, и начнём с малого
По стрелке request данные от клиента к серверу (клиент-серверная архитектура же) могут приходить разными способами.
Здесь составлю короткую памятку по аннотациям спринга для этих способов
➡️ берём часть пути (например, /users/{id})
➡️ удобно, когда нужно получить динамический сегмент URL (обычно ID сущностей)
➡️ берём Query-параметры (GET url?param=value) - они тоже в урле, как и @PathVariable, но не являются частью пути, а добавляются в хвосте через знак вопроса
➡️ если параметров несколько, они разделяются амперсандом &
➡️ такие параметры могут быть обязательными или нет (required=false)
➡️ используется для простых атомарных параметров, например, для фильтрации (указываются условия для выборочного возвращения каких-то запрашиваемых объектов)
➡️ берём JSON/XML из тела запроса (обращайте внимание на header запроса Content-Type - application/json или application/xml)
➡️ для сложных объектов. Spring автоматически преобразует данные в DTO или другой объект, указанный как параметр метода контроллера
➡️ берём query-параметры и собираем из них объект (DTO).
Т.е. данные передаются как при @RequestParam, но мы не хотим использовать их по-отдельности. Конечно, в объекте должны быть поля с теми же именами, что и передаваемые параметры
➡️ часто используется для пагинации и сортировки
(тут есть хитрость для формирования документации вашего восхитительного api в сваггере - чтобы поля этого объекта отображались как отдельные параметры, нужно применить рядом @ParameterObject, но работать будет и без неё)
➡️ если вообще не указать аннотацию перед объектом, то формирование этого объекта будет такое же, как с аннотацией @ModelAttribute, т.е. можно сказать, что она используется по умолчанию
➡️ также применяется для приёма данных в виде "multipart/form-data", если нужно передавать параметры и сразу файлы (загляните в постман, там есть такой вариант в разделе Body, указываются пары Key-Value с типом text/file)
➡️ берём отдельные части из multipart/form-data (а не собираем в объект как с @ModelAttribute)
➡️ полезно для загрузки файлов
➡️ берём заголовки из запроса (например, User-Agent, Authorization)
➡️ берём куки. Зачем? Потому что можем
🤔🤔🤔
И напоследок еще один каверзный вопрос из реальных собеседований -
можно ли передавать тело в get-запросе?🤔
По стрелке request данные от клиента к серверу (клиент-серверная архитектура же) могут приходить разными способами.
Здесь составлю короткую памятку по аннотациям спринга для этих способов
1. @PathVariable
2. @RequestParam
3. @RequestBody
4. @ModelAttribute
Т.е. данные передаются как при @RequestParam, но мы не хотим использовать их по-отдельности. Конечно, в объекте должны быть поля с теми же именами, что и передаваемые параметры
(тут есть хитрость для формирования документации вашего восхитительного api в сваггере - чтобы поля этого объекта отображались как отдельные параметры, нужно применить рядом @ParameterObject, но работать будет и без неё)
5. @RequestPart
6. @RequestHeader
7. @CookieValue
🤔🤔🤔
И напоследок еще один каверзный вопрос из реальных собеседований -
можно ли передавать тело в get-запросе?
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍8❤3🔥3👏1👌1
Неочевидное.
Что первое отвечает джавист на вопрос "для чего переопределять в классах методы equals и hashCode?".
🤔 Если отвечаете как-то иначе - прошу в комменты.
Но дальше может возникнуть вопрос - а разве мы так часто используем мапы, в которых ключи - наши объекты? Обычно же в ключах используем строки или числа, не?😨
И вот тут надо немного углубиться в Java Collection Framework (JCF).
На обычной схеме иерархии коллекций мы видим две отдельные ветки - по Map и по Set, и между ними не обозначено никаких связей . Но давайте зайдем в класс HashSet и посмотрим на банальную штуку - конструктор
Это что же получается - "нет никакого Сета, это всё компьютерная графика"? Получается, да. Сет - это замаскированная ХэшМапа. Которая сохранена в переменную с говорящим названием map:
А метод добавления элемента в Сет выглядит как добавление в мапу простым put:
Но есть нюанс: элемент e вставляется вместо ключа (первый параметр в put), а вместо значения - какой-то PRESENT.
А PRESENT - это просто константа-заглушка, dummy. Просто один самый простой объект Object, который используется во всех values внутренней ХэшМапы:
Итак, из двух обозначенных утверждений
1️⃣ - иквалс и хэшкод нужны для ключей ХэшМапы
2️⃣ - ХэшСет внутри является ХэшМапой и сохраняет элементы как ключи этой внутренней ХэшМапы
мы можем сделать третье утверждение:
(при этом использование ХэшСета с нашими собственными объектами - не такая уж и редкость).
Только теперь непонятно🤔 почему на схеме-то этого не показано?
- во-первых, там показано наследование и реализация интерфейсов, а Сет и Мап связаны по-другому, скорее композиционно;
- во-вторых, на этой схеме вообще много чего не показано, она очень упрощена!
Конец.
Хммм, а если есть не только HashSet, а еще LinkedHashSet и TreeSet, в них тоже внутри используется HashMap или...
Но об этом в другом посте
Что первое отвечает джавист на вопрос "для чего переопределять в классах методы equals и hashCode?".
➡️ Чтобы корректно сравнивать объекты, но для этого достаточно equals, а hashCode - чтобы использовать экземпляры этих классов как ключи в хэшмапе (на то она и "хэш-"). Также между методами equals и hashCode предусмотрен контракт, чтобы это всё правильно работало.
Но дальше может возникнуть вопрос - а разве мы так часто используем мапы, в которых ключи - наши объекты? Обычно же в ключах используем строки или числа, не?
И вот тут надо немного углубиться в Java Collection Framework (JCF).
На обычной схеме иерархии коллекций мы видим две отдельные ветки - по Map и по Set, и между ними не обозначено никаких связей . Но давайте зайдем в класс HashSet и посмотрим на банальную штуку - конструктор
public HashSet() {
map = new HashMap<>();
}
Это что же получается - "нет никакого Сета, это всё компьютерная графика"? Получается, да. Сет - это замаскированная ХэшМапа. Которая сохранена в переменную с говорящим названием map:
transient HashMap<E,Object> map;
А метод добавления элемента в Сет выглядит как добавление в мапу простым put:
public void add(E e) {
map.put(e, PRESENT);
}
Но есть нюанс: элемент e вставляется вместо ключа (первый параметр в put), а вместо значения - какой-то PRESENT.
А PRESENT - это просто константа-заглушка, dummy. Просто один самый простой объект Object, который используется во всех values внутренней ХэшМапы:
// Dummy value to associate with an Object in the backing Map
static final Object PRESENT = new Object();
Итак, из двух обозначенных утверждений
1️⃣ - иквалс и хэшкод нужны для ключей ХэшМапы
2️⃣ - ХэшСет внутри является ХэшМапой и сохраняет элементы как ключи этой внутренней ХэшМапы
мы можем сделать третье утверждение:
🤌 для элементов Сета корректность переопределения методов equals и hashCode так же важна, как и для ключей ХэшМапы
(при этом использование ХэшСета с нашими собственными объектами - не такая уж и редкость).
Только теперь непонятно
- во-первых, там показано наследование и реализация интерфейсов, а Сет и Мап связаны по-другому, скорее композиционно;
- во-вторых, на этой схеме вообще много чего не показано, она очень упрощена!
Конец.
Но об этом в другом посте
Please open Telegram to view this post
VIEW IN TELEGRAM
3🔥13👍6❤2✍2👨💻2
Есть три сета, которые нужно объединить:
Как бы вы это сделали? (тык в опрос ниже + горячо приветствуется обсуждение)
1)
2)
3) другим способом (или одним из указанных, но доработанным)
Set<String> set1 = Set.of("A1", "A2", ..., "A1000");
Set<String> set2 = Set.of("B1", "B2", ..., "B1000");
Set<String> set3 = Set.of("C1", "C2", ..., "C1000");Как бы вы это сделали? (тык в опрос ниже + горячо приветствуется обсуждение)
1)
Set<String> result = new HashSet<>();
result.addAll(set1);
result.addAll(set2);
result.addAll(set3);
2)
Set<String> result = Stream.of(set1, set2, set3)
.flatMap(Set::stream)
.collect(Collectors.toSet());
3) другим способом (или одним из указанных, но доработанным)
pu pu pu
2🤔6👍3🤓2💅1
Каким способом объединили бы сеты?
Anonymous Poll
40%
№ 1 (addAll каждого сета в новый сет)
49%
№2 (преобразовать в стримы и слить в один стрим)
11%
№ 3 (этот пункт без пояснений в комментах не принимается🤷♂️ 🤷♂️ 🤷♂️ )