Найти в Дзене
Nuances of programming

Знакомство с фабричным методом

Источник: Nuances of Programming Что такое фабричный метод? Фабричный метод (англ. Factory Method или Factory Design Pattern)  —  это порождающий шаблон проектирования, предоставляющий подклассам интерфейс для инстанцирования нужных экземпляров некоторого класса. Иными словами, подклассы берут на себя обязанность по созданию экземпляров базового класса. Преимущества фабричного метода Разбор фабричного метода на примере Предположим, что создаем игру, в которой есть разные типы персонажей  —  Warrior (воин), Wizard (маг) и Archer (лучник). Каждый из этих персонажей обладает своим набором способностей и видов атаки. Посмотрим, как будет выглядеть структура кода без фабричного метода. public class Warrior{
@Override
public void attack() {
System.out.println("Warrior attacks with a sword!");
}
@Override
public void defend() {
System.out.println("Warrior defends with a shield!");
}
} public class Wizard{
@Override
public void attack() {
Оглавление

Источник: Nuances of Programming

Что такое фабричный метод?

Фабричный метод (англ. Factory Method или Factory Design Pattern)  —  это порождающий шаблон проектирования, предоставляющий подклассам интерфейс для инстанцирования нужных экземпляров некоторого класса. Иными словами, подклассы берут на себя обязанность по созданию экземпляров базового класса.

Преимущества фабричного метода

  1. Инкапсуляция: фабричный метод инкапсулирует создание объектов, позволяя отделить клиентский код от создаваемых им объектов. В итоге мы получаем возможность легко изменять и расширять процесс их создания, не влияя на клиентский код.
  2. Гибкость: этот шаблон упрощает внедрение в систему новых типов объектов без изменения существующего кода. Это особенно важно в ситуациях, когда необходимые типы объектов остаются неизвестны вплоть до среды выполнения.
  3. Переиспользуемость: за счет централизации создания объектов в фабричном классе, или интерфейсе, мы получаем возможность повторно использовать этот код для создания объектов в различных частях приложения.
  4. Тестируемость: при использовании фабричного метода упрощается тестирование процесса создания объектов, поскольку его реализацию можно заменить макетом объекта для проведения такого тестирования.
  5. Обслуживание: применение фабричного метода упрощает дальнейшее обслуживание кода, так как изменения в реализации создания объектов можно будет вносить в одном месте, а не по всей базе кода.

Разбор фабричного метода на примере

Предположим, что создаем игру, в которой есть разные типы персонажей  —  Warrior (воин), Wizard (маг) и Archer (лучник). Каждый из этих персонажей обладает своим набором способностей и видов атаки.

Посмотрим, как будет выглядеть структура кода без фабричного метода.

public class Warrior{
@Override
public void attack() {
System.out.println("Warrior attacks with a sword!");
}

@Override
public void defend() {
System.out.println("Warrior defends with a shield!");
}
}

public class Wizard{
@Override
public void attack() {
System.out.println("Wizard attacks with magic!");
}

@Override
public void defend() {
System.out.println("Wizard defends with a spell!");
}
}

public class Archer{
@Override
public void attack() {
System.out.println("Archer attacks with a bow!");
}

@Override
public void defend() {
System.out.println("Archer defends with a dodge!");
}
}

public class Game {
public static void main(String[] args) {
// Создание персонажа Warrior
Character warrior = new Warrior();
warrior.attack();
warrior.defend();

// Создание персонажа Wizard
Character wizard = new Wizard();
wizard.attack();
wizard.defend();

// Создание персонажа Archer
Character archer = new Archer();
archer.attack();
archer.defend();

// выполнение действий с персонажами ...
}
}

В этой версии кода мы вручную создаем каждый тип объекта персонажа (Wizard, Warrior, Archer) и вызываем их методы attack() и defend() напрямую. Такой подход будет работать для небольших проектов с ограниченным числом объектов, но по мере роста их числа и расширения связанной с ними логики станет громоздким и сложным в обслуживании.

В чем проблема такой структуры кода?

Класс Game необходимо изменять при каждом добавлении нового персонажа, что по мере усложнения класса приведет к проблемам с обслуживанием. К примеру, если в игру добавляется новый персонаж Knight, нам потребуется изменить класс Game, добавив в него новый кейс switch для Knight и реализовав логику для создания нового объекта Knight.

Теперь посмотрим, как можно реализовать тот же код с помощью фабричного метода.

Тут нам на помощь приходит интерфейс.

Сначала мы определим интерфейс Character , который будет представлять обобщенного персонажа игры:

public interface Character {
void attack();
void defend();
}

Далее мы создадим конкретные классы, реализующие интерфейс Chatacter. Вот пример класса Warrior:

Игровой персонаж Warrior:

public class Warrior implements Character {
@Override
public void attack() {
System.out.println("Warrior attacks with a sword!");
}

@Override
public void defend() {
System.out.println("Warrior defends with a shield!");
}
}
Аналогичным образом мы создадим классы Wizard и Archer, реализующие интерфейс Character.

Игровой персонаж Wizard:

public class Wizard implements Character {
@Override
public void attack() {
System.out.println("Wizard attacks with magic!");
}

@Override
public void defend() {
System.out.println("Wizard defends with a spell!");
}
}

Игровой персонаж Archer:

public class Archer implements Character {
@Override
public void attack() {
System.out.println("Archer attacks with a bow!");
}

@Override
public void defend() {
System.out.println("Archer defends with a dodge!");
}
}

Теперь мы определим абстрактный класс CharacterFactory, определяющий фабричный метод для создания персонажей:

public abstract class CharacterFactory {
public abstract Character createCharacter();
}

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

Теперь можно создавать конкретные классы, расширяющие CharacterFactory и реализующие метод CreateCharacter() для возвращения конкретного типа персонажа. Вот пример WarriorFactory:

public class WarriorFactory extends CharacterFactory {
@Override
public Character createCharacter() {
return new Warrior();
}
}

По той же схеме мы создадим классы WizardFactory и ArcherFactory, которые расширят CharacterFactory и реализуют метод CreateCharacter() для возвращения Wizard или Archer соответственно.

Наконец, можно использовать CharacterFactory и его подклассы для создания новых персонажей в классе Game:

public class Game{
public static void main(String[] args) {
CharacterFactory factory = new WarriorFactory();
Character character = factory.createCharacter();
character.attack();
character.defend();
}
}

В этом примере у нас получилось три класса, реализующих интерфейс Character: Warrior, Wizard и Archer. У нас также есть три фабричных класса: WarriorFactory, WizardFactory и ArcherFactory, каждый из которых создает нужный тип объекта Character.

В классе Game мы создаем фабричный объект (CharacterFactory) и далее используем его для создания объекта Character (character). Затем можно использовать методы attack и defend в объекте character, не зная конкретный класс создаваемого объекта.

Преимущество фабричного метода в том, что он позволяет создавать объекты без тесной привязки кода к конкретным классам. Это делает код более гибким и удобным в обслуживании, поскольку мы можем легко подставлять различные реализации Character без изменения остального кода.

А теперь реальная магия

Игровой персонаж Ninja:

Предположим, что хотим добавить в игру новый тип персонажа  —  Ninja.

Если мы используем фабричный метод, то сможем легко добавить этот тип, создав для него новый фабричный класс без изменения класса Game:

public class Ninja implements Character {
@Override
public void attack() {
System.out.println("Wizard attacks with magic!");
}

@Override
public void defend() {
System.out.println("Wizard defends with a spell!");
}
}

public class NinjaFactory extends CharacterFactory {
@Override
public Character createCharacter() {
return new Ninja();
}
}

public class Game {
public static void main(String[] args) {
CharacterFactory factory = new NinjaFactory();
Character ninja = factory.createCharacter();
ninja.attack();
ninja.defend();
}
}

Как вы видите, здесь мы просто создаем новый класс NinjaFactory, расширяющий абстрактный класс CharacterFactory и реализующий метод CreateCharacter для возвращения нового объекта Ninja. Далее в классе Game мы используем NinjaFactory для создания объекта Ninja без непосредственной отсылки к классу Ninja.

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

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

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

Читайте также:

Читайте нас в TelegramVK

Перевод статьи Sumonta Saha Mridul: The factory design pattern