Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Фабричный метод - это порождающий шаблон проектирования, который предоставляет интерфейс для создания объектов в родительском классе, но позволяет подклассам изменять тип создаваемых объектов.
Проблема
Представьте, что вы создаете модуль игровых наград. Первая версия вашего приложения может обрабатывать только награду ЗОЛОТО, поэтому основная часть вашего кода находится внутри класса GoldReward.
Через некоторое время ваша игра становится довольно популярной. Каждый день вы получаете десятки запросов от игроков о добавлении новой валюты в приложение и просьбы разнообразить контент.
Отличные новости, правда? А как насчет кода? В настоящее время большая часть вашего кода связана с классом GoldReward. Добавление ГЕМОВ в приложение потребует внесения изменений во всю кодовую базу. Более того, если позже вы решите добавить в приложение еще один вид НАГРАД, вам, вероятно, придется снова внести все эти изменения. В результате вы получите довольно неприятный код, пронизанный условными выражениями, которые переключают поведение приложения в зависимости от типа НАГРАД.
Решение
Шаблон фабричного метода предлагает заменить прямые вызовы построения объекта (с использованием оператора new) на вызовы специального фабричного метода. Не волнуйтесь: объекты по-прежнему создаются с помощью оператора new, но он вызывается из фабричного метода. Объекты, возвращаемые фабричным методом, часто называют продуктами.
На первый взгляд это изменение может показаться бессмысленным: мы просто переместили вызов конструктора из одной части программы в другую. Однако учтите следующее: теперь вы можете переопределить фабричный метод в подклассе и изменить класс продуктов, создаваемых этим методом.
Однако есть небольшое ограничение: подклассы могут возвращать разные типы продуктов, только если эти продукты имеют общий базовый класс или интерфейс. Кроме того, тип возвращаемого значения для фабричного метода в базовом классе должен быть объявлен как этот интерфейс.
Например, классы GoldReward и GemReward должны реализовывать интерфейс наград, в котором объявляется метод rewardFor. Каждый класс реализует этот метод по-разному: золотая награда увеличивает золото, награда с гемами увеличивает гемы в профиле игрока. Фабричный метод в классе GoldRewardService возвращает объекты золотой награды, тогда как фабричный метод в классе GemRewardService возвращает гемы.
Код, использующий фабричный метод (часто называемый клиентским кодом), не видит разницы между фактическими продуктами, возвращаемыми различными подклассами. Клиент рассматривает все продукты как абстрактную награду. Клиент знает, что все награды должны иметь метод применения награды, но то, как именно он работает, не имеет значения для клиента.
Structure
1. Интерфейс GameItem, который является общим для всех объектов наград, которые могут быть созданы создателем и его подклассами.
Конкретные награды - это разные реализации интерфейса GameItem.
Класс ReawardCreator объявляет фабричный метод, который возвращает новые объекты наград. Важно, чтобы тип возвращаемого значения этого метода соответствовал интерфейсу продукта. Вы можете объявить фабричный метод абстрактным, чтобы заставить все подклассы реализовывать свои собственные версии метода. В качестве альтернативы базовый фабричный метод может возвращать некоторый тип награды по умолчанию. Обратите внимание: несмотря на название, создание продукта не является основной обязанностью создателя. Обычно класс создателя уже имеет некоторую базовую бизнес-логику, связанную с наградами. Фабричный метод помогает отделить эту логику от конкретных классов наград.
Конкретные создатели переопределяют базовый фабричный метод, поэтому он возвращает другой тип продукта. Обратите внимание, что фабричный метод не должен постоянно создавать новые экземпляры. Он также может возвращать существующие объекты из кеша, пула объектов или другого источника.
Используйте фабричный метод, если вы хотите предоставить пользователям вашей библиотеки или фреймворка способ расширения его внутренних компонентов.
Наследование - это, вероятно, самый простой способ расширить поведение библиотеки или фреймворка по умолчанию. Но как фреймворк распознает, что ваш подкласс следует использовать вместо стандартного компонента?
Решение состоит в том, чтобы сократить код, который создает компоненты в рамках платформы, до единого фабричного метода и позволить любому переопределить этот метод в дополнение к расширению самого компонента.
Как реализовать
Создали базовый интерфейс GameItem:
Создали базовый интерфейс GameItem
public interface GameItem {
void open();
}
2. Создадим пару наград и реализуем метод интерфейса:
public class GoldReward implements GameItem {
@Override
public void open() {
// todo add open business logic
System.out.println("GoldReward opened");
}
}
public class GemReward implements GameItem {
@Override
public void open() {
// todo add open business logic
System.out.println("GemReward opened");
}
}
3. Дальше нам потребуется ItemGenerator, который будет открывать награды и создавать их:
public abstract class ItemGenerator {
public void openReward() {
// ... other code ...
GameItem gameItem = createItem();
gameItem.open();
}
/**
* Subclasses will override this method in order to create
* specific reward objects.
*/
public abstract GameItem createItem();
}
4. Давайте создадим конкретные реализации ItemGenerator:
public class GoldGenerator extends ItemGenerator{
@Override
public GameItem createItem() {
return new GoldReward();
}
}
public class GemGenerator extends ItemGenerator{
@Override
public GameItem createItem() {
return new GemReward();
}
}
5. Протестируем, то что у нас получилось. Я буду в цикле доставать “случайный” генератор и открывать награду. В терминале можно будет увидеть сообщение об открытии награды.
public class Game {
public static void main(String[] args) {
Random random = ThreadLocalRandom.current();
List<ItemGenerator> generatorList = new ArrayList<>();
generatorList.add(new GemGenerator());
generatorList.add(new GoldGenerator());
for (int i = 0; i < 10; i ++){
int idx = Math.abs(random.nextInt() % 2) == 0 ? 0 : 1;
ItemGenerator itemGenerator = generatorList.get(idx);
itemGenerator.openReward();
}
}
}
Ссылка на код будет вот тут. Можно посмотреть реализацию этого паттерна.
На этом разбор фабричного метода закончен. Хотелось бы узнать, встречали ли вы у себя в проектах фабричный метод? Или может вы не осознанно писали код, который получался как фабричный метод? Спасибо что дочитали до конца