Как вы уже догадались из названия поста, речь пойдёт о широко известном паттерне проектирования Factory. Вы спросите: «зачем этот пост, ведь сеть уже и так наполнена массой подобных постов?». Но не судите меня за желание поделиться плодами своего труда.
Немножко о себе. Я молодой, не опытный, перспективный разработчик в области BigData, короче говоря junior. Получил задание разобраться в некоторых паттернах проектирования (на мой вкус), и первым я решил исследовать Factory Design Pattern.
Что собой представляет Factory
Паттерн относится к «порождающей» группе паттернов. Название «фабрика» говорит само за себя. Фабрика занимается созданием некоторой продукции. Мы поставляем ей сырье и в результате получаем полноценный продукт. Получается такой себе чёрный ящик, в котором механизм создания объектов скрыт. Это и есть цель паттерна фабрика. Скрыть излишки логики от глаз. Рисунок даст возможность лучше понять, как этот паттерн должен быть реализован.
Есть какая-то иерархия классов, для которой вы хотели бы иметь удобную фабрику и по желанию получать тот или иной объект без использования new. В качестве базового класса может выступать интерфейс, абстрактный класс или обычный класс. Но помните, что паттерны — это скорее рекомендации, чем правила, и ваша реализация может отличатся (вы сможете это увидеть ниже в моем коде) от общепринятых подходов к его имплементации.
Ближе к коду
Модель
В качестве модели для фабрики я создал небольшую иерархию из абстрактного базового класса и двух его реализаций.
Базовый класс
public abstract class Computer {
protected String ram;
protected String hdd;
protected String cpu;
public Computer() {
}
public Computer(String ram, String hdd, String cpu) {
this.ram = ram;
this.hdd = hdd;
this.cpu = cpu;
}
public String getCPU() { return cpu; }
public String getHDD() { return hdd; }
public String getRAM() { return ram; }
@Override
public String toString() {
return "RAM= " + getRAM() + ", Hdd= " + getHDD() + ", CPU= " + getCPU();
}
}
Реализация
public class Server extends Computer {
public Server(){}
public Server(String ram, String hdd, String cpu) {
super(ram, hdd, cpu);
}
}
public class PC extends Computer {
public PC(){}
public PC(String ram, String hdd, String cpu) {
super(ram, hdd, cpu);
}
}
Разобравшись с нашей иерархией мы можем приступать к реализации фабрики. Я подготовил несколько реализаций.
Простая Фабрика
Эта реализация не выделяется ничем особенным.
public class SimpleFactory {
private static final Map<String, Class<? extends Computer>> factoryForm = new HashMap<>();
private static Computer computer;
static {
factoryForm.put("pc", PC.class);
factoryForm.put("server", Server.class);
}
private SimpleFactory(){}
private static Computer getComputerTrobelsome(String type, String ram, String hdd, String cpu) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NullPointerException {
if( factoryForm.containsKey(type)) {
return factoryForm.get(type).getConstructor(String.class, String.class, String.class).newInstance(ram, hdd, cpu);
}
throw new NullPointerException();
}
public static Computer getComputer(String type, String ram, String hdd, String cpu) {
try {
computer = getComputerTrobelsome(type, ram, hdd, cpu);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}catch (NullPointerException e){
System.out.println("No such computer type.");
}
return computer;
}
}
Есть Map, который содержит классы, при помощи которых можно будет создать интересующие нас объекты. Метод getComputer принимает необходимее аргументы, достаёт из factoryForm необходимый класс и при помощи нехитрой рефлексии мы получаем интересующие нас объекты. Да, использование рефлексии чревато излишками в коде, и это плохо, но, во-первых, суть поста не в рефлексии, а в паттерне, во-вторых, я использовал рефлексию, потому что могу. В своей реализации можете использовать любой другой подход.
Enum в качестве фабрики
После реализации предыдущего примера я начал думать, что бы ещё такое сделать, и мне в голову пришла мысль: почему бы не использовать перечисления в качестве фабрики.
public enum EnamFactory {
PC(com.epam.pattern.factory.model.PC.class) {
@Override
public Computer getInstance(String ram, String hdd, String cpu) {
return new PC(ram, hdd, cpu);
}
}, SERVER(Server.class) {
@Override
public Computer getInstance(String ram, String hdd, String cpu) {
return new Server(ram, hdd, cpu);
}
};
private Class<? extends Computer> pattern;
EnamFactory(Class<? extends Computer> pattern) {
this.pattern = pattern;
}
public abstract Computer getInstance(String ram, String hdd, String cpu);
public static Computer getInstance(EnamFactory pattern, String ram, String hdd, String cpu) {
return pattern.getInstance(ram, hdd, cpu);
}
}
Здесь все предельно ясно. Создаём перечисление с каким-то абстрактный методом, который будет возвращать нам объект конкретных классов. Для каждого перечисления этот метод будет содержать логику для создания конкретных объектов.
Параметризованная фабрика
Мне всё ещё было недостаточно того, что я сделал. Потому представляю вашему вниманию последнюю на сегодня реализацию паттерна фабрика. Она универсальная и даёт возможность создавать объекты различных иерархий, даже если классы будут иметь конструкторы с различным набором аргументов, она будет работать. Конечно, возможно, я упустил некоторые возможные ошибки и буду благодарен, если вы любезно укажете на них в ваших комментариях.
public class GenericFactory<T> {
private static final Logger LOGGER = Logger.getLogger(GenericFactory.class);
private Map<String, Class<? extends T>> factoryForm = new HashMap<>();
private T computer;
public GenericFactory(Class<? extends T>... patterns) {
for (Class pattern : patterns) {
factoryForm.put(pattern.getSimpleName(), pattern);
}
}
private T getInstanceTrobelsome(String type, Object... params) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NullPointerException {
if (factoryForm.containsKey(type)) {
return factoryForm.get(type).getConstructor(getAttrClasses(params)).newInstance(params);
}
throw new NullPointerException();
}
private Class[] getAttrClasses(Object... params) {
Class[] classes = new Class[params.length];
for (int index = 0; index < params.length; index++) {
classes[index] = params[index].getClass();
}
return classes;
}
private T getInstanceHandler(String type, Object... params) {
try {
computer = getInstanceTrobelsome(type, params);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NullPointerException e) {
LOGGER.error("No such factory pattern !");
}
return computer;
}
public T getInstance(String type) {
return getInstanceHandler(type, new Object[0]);
}
public T getInstance(Class<? extends T> type) {
return getInstanceHandler(type.getSimpleName(), new Object[0]);
}
public T getInstance(Class<? extends T> type, Object... params) {
return getInstanceHandler(type.getSimpleName(), params);
}
public T getInstance(String type, Object... params) {
return getInstanceHandler(type, params);
}
}
Опять же, эта реализация полнится рефлексией, но благодаря этому и возможно такое поведение робота с несколькими иерархиями. Ещё один недостаток этой реализации: в качестве аргументов в конструкторе нельзя использовать примитивы. И, наконец, я приведу результат роботы этой чудесной фабрики.
Вторая модель
public abstract class Fruit {
protected String name;
private int weight;
public Fruit(){};
public Fruit(String name, Integer weight){
this.name = name;
this.weight = weight;
}
@Override
public String toString() {
return String.format("Data = %s, Wetdht = %s", name, weight);
}
}
public class Apple extends Fruit {
public Apple() {}
public Apple(String data, Integer weight) {
super(data, weight);
}
}
public class Pomegranate extends Fruit {
public Pomegranate(){}
public Pomegranate(String data, Integer weight){
super(data, weight);
}
}
Мейн
public class Main {
public static void main(String[] args) {
GenericFactory<Computer> computerFactory = new GenericFactory<>(PC.class, Server.class);
System.out.println("PCt"+computerFactory.getInstance(PC.class.getSimpleName()));
System.out.println("Servert"+computerFactory.getInstance(Server.class, "32", "23", "32"));
System.out.println("PCt"+computerFactory.getInstance("PC", "32", "23", "32"));
GenericFactory<Fruit> fruitFactory = new GenericFactory<>(Pomegranate.class, Apple.class);
System.out.println("Pomegranatet"+ fruitFactory.getInstance(Pomegranate.class));
System.out.println("Applet"+fruitFactory.getInstance(Apple.class, "adas", 23));
}
}
И наконец результат роботы программы
PC RAM= null, Hdd= null, CPU= null
Server RAM= 32, Hdd= 23, CPU= 32
PC RAM= 32, Hdd= 23, CPU= 32
Pomegranate Data = null, Wetdht = 0
Apple Data = adas, Wetdht = 23
Выводы
Паттерн Фабрика удаляем создание экземпляра из клиентского кода, что делает его более устойчивым, менее связанным и легко расширяемым. Обеспечивает абстракцию между реализацией и клиентскими классами посредством наследования.
Что ж, на этом все. Спасибо за внимание. Надеюсь, этот пост был для вас полезным. Буду благодарен за ваши комментарии (возможно, в посте были допущены какие-то ошибки или неточности).
Автор: HikenLVK