Friday, April 13, 2012

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

При разработке приложения бывают ситуации, когда оно должно иметь возможность работать с большим количеством различных ресурсов или в разных операционных системах. Соответственно, возникает необходимость в специальном механизме, который смог бы обеспечить выполнение этого требования. В то же время приложение должно быть гибким и добавление нового ресурса не должно приводить к изменению существующего кода. Шаблон Abstract Factory позволяет эффективно решить подобную проблему.

Abstract Factory


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

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

Альтернативные названия: Kit, Toolkit

Реализация:
AbstractFactory - абстрактный класс или интерфейс, который определяет механизмы создания абстрактных продуктов.

Product - абстрактный класс или интерфейс, который описывает базовые функции ресурса, который будет использоваться приложением.

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

ConcreteProduct - класс, порожденный от класса абстрактного продукта и реализующий определенный ресурс или операционную среду.


Представим, что мы решили создать игру про военные действия во время Второй мировой войны. У нас будет два противоборствующих государства - Германия и СССР. Мы только начали создавать игру, и поэтому у каждой стороны есть всего два типа техники - танк и самолет. Реализуем необходимые классы с помощью шаблона Abstract Factory.

Создадим интерфейс фабрики военной техники. Он содержит методы для создания танка и самолета:
public interface EngineryFactory {
    
    public Tank createTank();

    public Aircraft createAircraft();
}
Созданный интерфейс соответствует интерфейсу AbstractFactory на диаграмме.

Создадим абстрактный класс Tank. В нем описаны свойства танка, методы для получения/изменения значений этих свойств и метод getDescription(), с помощью которого можно узнать описание танка:
public abstract class Tank {
    private int speed;
    private int power;

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public int getPower() {
        return power;
    }

    public void setPower(int power) {
        this.power = power;
    }
    
    public abstract String getDescription();
}
Класс Tank - это наш аналог класса ProductA.

Аналогично, создадим класс Aircraft, описывающий абстрактный самолет:
public abstract class Aircraft {
    int speed;
    int power;
    int altitude;

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public int getPower() {
        return power;
    }

    public void setPower(int power) {
        this.power = power;
    }

    public int getAltitude() {
        return altitude;
    }

    public void setAltitude(int altitude) {
        this.altitude = altitude;
    }

    public abstract String getDescription();
}
Класс Aircraft соответствует классу ProductB.

Перейдем к созданию конкретной фабрики по производству конкретных танков и самолетов. Класс USSREngineryFactory реализует интерфейс EngineryFactory и позволяет создавать только советскую военную технику - танки "Т-34" и самолеты "Ил-2":
public class USSREngineryFactory implements EngineryFactory {

    @Override
    public Tank createTank() {
        return new T34_Tank();
    }

    @Override
    public Aircraft createAircraft() {
        return new Il2_Aircraft();
    }
}
Класс USSREngineryFactory соответствует классу ConcreteFactory1.

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

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

Создадим класс, описывающий самолет "Ил-2":
public class Il2_Aircraft extends Aircraft {
    private static final String NAME = "Il-2";
    private static final String COUNTRY = "USSR";

    public String getDescription() {
        return NAME + " " + COUNTRY;
    }
}
Класс Il2_Aircraft соответствует классу ConcreteProductB1.

Аналогично, создадим фабрику по производству немецкой военной техники - танков "Е-25" и самолетов "Мессершмитт Bf.110":
public class GermanEngineryFactory implements EngineryFactory {

    @Override
    public Tank createTank() {
        return new E25_Tank();
    }

    @Override
    public Aircraft createAircraft() {
        return new MesserschmittBf110_Aircraft();
    }
}
Класс GermanEngineryFactory соответствует классу ConcreteFactory2.

Класс E25_Tank соответствует классу ConcreteProductA2:
public class E25_Tank extends Tank {
    private static final String NAME = "E-25";
    private static final String COUNTRY = "Germany";

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

Класс MesserschmittBf110_Aircraft соответствует классу ConcreteProductB2:
public class MesserschmittBf110_Aircraft extends Aircraft {
    private static final String NAME = "Messerschmitt Bf.110";
    private static final String COUNTRY = "Germany";

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

Теперь все классы диаграммы созданы.

Для демонстрации примера создадим класс AbstractFactoryDemo. Создадим в нем метод showEnginery(), который будет принимать фабрику как аргумент и выводить информацию о ее военной технике, и передадим ему экземпляры фабрик USSREngineryFactory и GermanEngineryFactory:
public class AbstractFactoryDemo {
    public static void main(String[] args) {
        EngineryFactory engineryFactory = new USSREngineryFactory();

        System.out.println("USSR enginery:");
        showEnginery(engineryFactory);
        
        System.out.println();

        System.out.println("German enginery:");
        engineryFactory = new GermanEngineryFactory();
        showEnginery(engineryFactory);
    }
    
    public static void showEnginery(EngineryFactory engineryFactory) {
        Tank tank = engineryFactory.createTank();
        System.out.println(tank.getDescription());

        Aircraft aircraft = engineryFactory.createAircraft();
        System.out.println(aircraft.getDescription());
    }
}

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










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

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




















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

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

19 comments:

  1. Кратко и доходчиво. Как по мне, лучшее истолкование паттерна на весь рунет. Автору респект.

    ReplyDelete
  2. Спасибо Автору! Весь интернет рыл несколько дней! Наконец нашёл талковый пример и код!!!!

    ReplyDelete
  3. Просьба Автору!
    Пожалуйста истолкуйте так же грамотно и остальные шаблоны:

    строитель (builder);
    фабричный метод (factory method);
    ленивая инициализация (lazy initialization);
    объектный пул (object pool);
    прототип (prototype);
    одиночка (singleton).

    ReplyDelete
  4. Respect! Very good explanation! I'll recommend to my colleagues.

    ReplyDelete
  5. Спасибо большое за проделанный труд!! Действительно очень доступно изложено!!

    ReplyDelete
  6. Действительно все доходчиво и понятно, не то что в других зацмных статьях которые в Сети валяются. Спасибо.

    ReplyDelete
    Replies
    1. Пользуйтесь на здоровье!

      Delete
  7. Огромное спасибо автору! Успехов и счастья!

    ReplyDelete
  8. Огромнейшее спасибо! Так доходчиво мне еще ни одна книга/автор/учитель не объясняли. Да плюс на каком примере (ВОВ). Гениально, на мой взгляд.

    ReplyDelete
    Replies
    1. Рад, что вам это было полезно!

      Delete
    2. Это, скорее, на примере Wot:) Ведь е25 в боях не участвовали.

      Delete