Pages

Tuesday, April 17, 2012

Шаблон проектирования Factory Method

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

Factory Method


Тип: порождающий шаблон

Назначение: определяет стандартный метод создания объекта, не связанный с вызовом конструктора, оставляя решение о том, какой именно объект создавать, за подклассами.

Альтернативные названия: Virtual Constructor

Реализация:
Product - интерфейс "фабрикуемых" объектов.

ConcreteProduct - реализация класса Product. Создание объектов этого класса возлагается на класс ConcreteCreator.

Creator - интерфейс, который определяет собственно метод, "фабрикующий" объекты - factoryMethod().

ConcreteCreator - класс, расширяющий интерфейс Creator и содержащий реализацию метода factoryMethod(). Этот метод может возвращать любой объект, который реализует интерфейс Product.


Перейдем к практике. Продолжим военную тематику, затронутую ранее при описании шаблона Abstract Factory. Наша цель - разработать механизм для создания объектов различных танков, используя шаблон проектирования Factory Method.

Создадим интерфейс, который будет отвечать за создание объектов типа Tank:
public interface TankCreator {

    Tank getTank(String name);
}
TankCreator соответствует интерфейсу Creator на диаграмме, а метод getTank() - методу factoryMethod().

Создадим класс USSRTankCreator, который будет реализовывать интерфейс TankCreator. Переопределим метод getTank() так, чтобы он возвращал конкретный экземпляр объекта типа Tank в зависимости от переданного аргумента:
public class USSRTankCreator implements TankCreator {

    @Override
    public Tank getTank(String name) {
        if ("T-34".equals(name)) {
            return new T34_Tank();
        } else if ("T-35".equals(name)) {
            return new T35_Tank();
        } else if ("T-38".equals(name)) {
            return new T38_Tank();
        }

        return null;
    }
}
Класс USSRTankCreator соответствует классу ConcreteCreator.

Создадим интерфейс Tank. Он содержит метод getDescription(), который возвращает описание танка:
public interface Tank {

    String getDescription();
}
Интерфейс Tank соответствует интерфейсу Product.

Создадим класс, описывающий танк "Т-34". Переопределим метод getDescription() так, чтобы он возвращал информацию об этом танке:
public class T34_Tank implements Tank {
    private static final String NAME = "T-34";
    private static final String COUNTRY = "USSR";

    public String getDescription() {
        return NAME + " " + COUNTRY;
    }
}

Аналогично, создадим классы, описывающие танки "Т-35" и "Т-38":
public class T35_Tank implements Tank {
    private static final String NAME = "T-35";
    private static final String COUNTRY = "USSR";

    public String getDescription() {
        return NAME + " " + COUNTRY;
    }
}
public class T38_Tank implements Tank {
    private static final String NAME = "T-38";
    private static final String COUNTRY = "USSR";

    public String getDescription() {
        return NAME + " " + COUNTRY;
    }
}
Классы T34_Tank, T35_Tank и T38_Tank - это конкретные "фабрикуемые" продукты. Они соответствуют классу ConcreteProduct.

Для демонстрации примера создадим класс FactoryMethodDemo. Создадим объект типа TankCreator, с помощью его фабричного метода getTank() получим экземпляры объектов различных танков и выведем информацию о них:
public class FactoryMethodDemo {

    public static void main(String[] args) {
        TankCreator tankCreator = new USSRTankCreator();

        Tank tank = tankCreator.getTank("T-34");
        System.out.println(tank.getDescription());

        tank = tankCreator.getTank("T-35");
        System.out.println(tank.getDescription());

        tank = tankCreator.getTank("T-38");
        System.out.println(tank.getDescription());
    }
}

Запустим метод main():







В процессе работы программы мы создали несколько различных объектов типа Tank без явного вызова их конструкторов из метода main().

Структура проекта:

















Исходный код созданного приложения доступен по ссылке http://code.google.com/p/factory-method-demo/.

Рекомендуемые посты:

3 comments:

  1. Могу ошибаться, но все таки фабричный метод служит больше для того. чтобы не порождать классы, зависящие от приложения.
    Вот пример который мне понравился http://dron.by/post/pattern-proektirovaniya-factory-method-fabrichnyj-metod-na-php.html

    Грубо говоря, основная цель - избавиться в наследуемом классе от почти полного копипастинга метода суперкласса если возникнет такая "опасность". То есть - то что нам нужно изменить в методе суперкласса мы вынесем в отделный маленький метод,и уже только его будем переопределять в наследнике. И абстрактный класс/интерфейс не всегда нужен в принципе.
    Поправьте, если не прав.
    Иван.

    ReplyDelete
    Replies
    1. Здравствуйте, Иван! Описанный мною шаблон я встречал в таком виде (UML и подобный код) во многих книгах по шаблонам проектирования. Поэтому, мне кажется, что я описал именно Factory Method

      Delete
  2. Ну а зачем всё это? Почему просто не написать:
    Tank tank = new T34_Tank(); System.out.println(tank.getDescription());
    Tank tank = new T35_Tank(); System.out.println(tank.getDescription()); Просто, понятно.
    Нахрена, простите, нужен Ваш TankCreator ?

    ReplyDelete