Паттерны проектирования для java разработчиков. Factory

в 12:44, , рубрики: java, паттерны проектирования, програмирование на java, метки:

Как вы уже догадались из названия поста, речь пойдёт о широко известном паттерне проектирования Factory. Вы спросите: «зачем этот пост, ведь сеть уже и так наполнена массой подобных постов?». Но не судите меня за желание поделиться плодами своего труда.

Немножко о себе. Я молодой, не опытный, перспективный разработчик в области BigData, короче говоря junior. Получил задание разобраться в некоторых паттернах проектирования (на мой вкус), и первым я решил исследовать Factory Design Pattern.

Что собой представляет Factory

Паттерн относится к «порождающей» группе паттернов. Название «фабрика» говорит само за себя. Фабрика занимается созданием некоторой продукции. Мы поставляем ей сырье и в результате получаем полноценный продукт. Получается такой себе чёрный ящик, в котором механизм создания объектов скрыт. Это и есть цель паттерна фабрика. Скрыть излишки логики от глаз. Рисунок даст возможность лучше понять, как этот паттерн должен быть реализован.

image

Есть какая-то иерархия классов, для которой вы хотели бы иметь удобную фабрику и по желанию получать тот или иной объект без использования 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js