Абстрактная фабрика - Паттерн разработки

Определение

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

Когда используется и в чем его достоинства

  • Создает семейство связанных или зависимых объектов
  • Предоставляет библиотеку объектов, открывая интерфейс, а не конкретные реализации.
  • Требуется для изоляции конкретных классов от их родительских классов.
  • Используется если система должна быть независимой от того, как ее объекты созданы, составлены и представлены.
  • Вводит ограничения.
  • Является альтернативой шаблону фасад при скрытии платформенно-зависимых классов.
  • Легко расширять на новую систему или семейство объектов.

Пример

Предположим, что разрабатывается некое приложение, интерфейс пользователя которого содержит три модуля, представленных соответствующими классами: Locale (весь текст интерфейса), Images (картинки, баннеры) и Help (справка пользователя). Представим себе, что приложение предназначено для США и Японии. И в зависимости от того, какая страна выбрана в его настройках, должен загружаться соответствующий интерфейс: английская или японская локализация, баннеры, справка. Шаблон абстрактной фабрики подсказывает нам решение задачи в том, чтобы представить Locale, Images и Help интерфейсами, создать для них нужные нам реализации, а затем написать интерфейс фабрики UIAbstractFactory, в котором будут указаны фабричные методы, инициализирующие необходимые нам объекты. Далее мы пишем две реализации фабрики, в котором имплементируем эти методы, а в коде нашего приложения просто выбираем, какую из фабрик мы хотим использовать, основываясь на параметре.


Для этого примера создадим новый проект, а в нем пакет org.test.technerium.patterns.abstractfactory

Итак, интерфейсы нашего UI (User Interface - интерфейса пользователя):

Локализация

package org.test.technerium.patterns.abstractfactory;

public interface Locale {
    public String getLocaleCountry();
}

Баннеры

package org.test.technerium.patterns.abstractfactory;

public interface Images {
    public String getImagesCountry();
}

Справка

package org.test.technerium.patterns.abstractfactory;

public interface Help {
    public String getHelpCountry();
}

Как видим, в нашем примере интерфейсы максимально упрощены, в них всего по одному методу, чтобы не отвлекать внимание от главного - абстракции.

Сразу же создадим интерфейс фабрики:

package org.test.technerium.patterns.abstractfactory;

public interface UIAbstractFactory {
    public Help getHelp();
    public Images getImages();
    public Locale getLocale();
}

Здесь запланированы три метода, предположительно независимые один от другого, хотя можно было бы использовать один метод, возвращающий, к примеру, Map или Collection нужных нам объектов интефейса.

Займемся реализацией элементов интерфейса для США и Японии:

Локализация

package org.test.technerium.patterns.abstractfactory;

public class JapaneseLocaleImpl implements Locale{

    @Override
    public String getLocaleCountry() {
        return "Japan";
    }

}

Баннеры

package org.test.technerium.patterns.abstractfactory;

public class JapaneseImagesImpl implements Images{

    @Override
    public String getImagesCountry() {
        return "Japan";
    }

}

Справка

package org.test.technerium.patterns.abstractfactory;

public class JapaneseHelpImpl implements Help{

    @Override
    public String getHelpCountry() {
        return "Japan";
    }

}

Вот такие простые реализации. То же самое сделаем для США, три класса реализующие UI:

Локализация

package org.test.technerium.patterns.abstractfactory;

public class USLocaleImpl implements Locale{

    @Override
    public String getLocaleCountry() {
        return "United States";
    }

}

Баннеры

package org.test.technerium.patterns.abstractfactory;

public class USImagesImpl implements Images{

    @Override
    public String getImagesCountry() {
        return "United States";
    }

}

Справка

package org.test.technerium.patterns.abstractfactory;

public class USHelpImpl implements Help{

    @Override
    public String getHelpCountry() {
        return "United States";
    }

}

Теперь у нас реализовано три семейства объектов (локализация, баннеры, справка), по две реализации в каждом. Необходимо добавить соответствующие две фабрики:

Фабрика, инициализирующая объекты для японского UI

package org.test.technerium.patterns.abstractfactory;

public class JapaneseUIFactory implements UIAbstractFactory{

    @Override
    public Help getHelp() {
        return new JapaneseHelpImpl();
    }

    @Override
    public Images getImages() {
        return new JapaneseImagesImpl();
    }

    @Override
    public Locale getLocale() {
        return new JapaneseLocaleImpl();
    }
    
}

Фабрика для США:

package org.test.technerium.patterns.abstractfactory;

public class USUIFactory implements UIAbstractFactory{

    @Override
    public Help getHelp() {
        return new USHelpImpl();
    }

    @Override
    public Images getImages() {
        return new USImagesImpl();
    }

    @Override
    public Locale getLocale() {
        return new USLocaleImpl();
    }
    
}

Оба класса содержат фабричные методы, которые создают объекты для данной страны.

Можно уже написать и код, запускающий пример.

Создадим тестовый клиент, который будет анализировать, какая страна выбрана, затем инициализировать соответствующую фабрику, а далее из нее получать по одному объекты UI и вызывать в них их методы:

package org.test.technerium.patterns.abstractfactory;

public class TestClient {
    public void run(String countryId){//Параметр определяет страну
        UIAbstractFactory uiFactory = null; //Инициализируем фабрику
        if(countryId.equals("JP"))
            uiFactory = new JapaneseUIFactory(); // Создаем фабрику UI для Японии
        if(countryId.equals("US"))
            uiFactory = new USUIFactory(); // Создаем фабрику для США
        
        if(uiFactory != null){
            Help help = uiFactory.getHelp(); //Справка
            log("help created for: " + help.getHelpCountry());
            
            Images images = uiFactory.getImages(); // Баннеры
            log("UI images created for: " + images.getImagesCountry());
            
            Locale locale = uiFactory.getLocale(); // Локализация
            log("UI locale created for: " + locale.getLocaleCountry());
        }else{
            log("Wrong country id");
        }
    }
    
    private void log(String msg){
        System.out.println(msg);
    }
}

Ну и собственно тестовый класс для запуска примера:

package org.test.technerium.patterns.abstractfactory;

public class RunAbstractFactoryTest {
    public static void main(String args[]){
        TestClient test = new TestClient(); //Создаем наш тестовый клиент
        System.out.println("Run example for Japan");
        String countryCode = "JP"; //Выбранная страна - Япония
        test.run(countryCode); // запускаем клиент

        System.out.println("Run example for US");
        countryCode = "US";//Выбираем США
        test.run(countryCode);

        System.out.println("Run example for DE");
        countryCode = "DE"; // Выбираем Германию, которую еще не поддерживаем
        test.run(countryCode);
    }
}

Запускаем RunAbstractFactoryTest и в консоли видим:

Run example for Japan
help created for: Japan
UI images created for: Japan
UI locale created for: Japan
Run example for US
help created for: United States
UI images created for: United States
UI locale created for: United States
Run example for DE
Wrong country id

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

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

Поэтому руководствоваться следует в первую очередь соотношением усилий, потраченных на первоначальное введение такого уровня абстракции (интерфейсы семейств объектов UI, интерфейс фабрики) и усилий, которые, скорее всего, придется тратить на поддержку и расширение кода в будущем.


Надеюсь, теперь вы получили представление о паттерне Абстрактная фабрика на примере Java. Все примеры в архиве проекта для Eclipse можно скачать по ссылке.

Возможно, вам будут интересны и другие шаблоны, представленные в обзорной статье

Была ли статья полезна: 

Добавить комментарий

HTML

  • Разрешённые HTML-теги: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <pre> <p>
  • Строки и параграфы переносятся автоматически.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Поместите примеры вашего исходного кода в теги <code>...</code> or <source>...</source> и он будет красиво отформатирован.

Plain text

  • Поместите примеры вашего исходного кода в теги <code>...</code> or <source>...</source> и он будет красиво отформатирован.
  • Строки и параграфы переносятся автоматически.
  • Разрешённые HTML-теги: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <code> <source>
CAPTCHA
Пожалуйста, подтвердите, что вы человек.