Название шаблона
Абстрактная фабрика (Abstract factory).
Тип
Порождающий шаблон проектирования (Creational).
Описание
Абстрактная фабрика предоставляет интерфейс, позволяющий порождать семейства объектов c заданными интерфейсами. При этом их реализации могут варьироваться.
Данный шаблон используется в случае, если:
- система не должна зависеть от способа создания и реализации входящих в нее объектов;
- (и) система работает с семействами объектов;
- (и) входящие в семейство объекты должны использоваться совместно.
Обратите внимание, клиентский код использует в работе только интерфейсы. Реализации Абстрактной фабрики и порождаемых ею объектов скрыты. Такой подход уменьшает зависимости между объектами и повышает гибкость, за счет возможности изменения реализаций.
Часто можно увидеть совместное использование Абстрактной фабрики с другими шаблонами:
Схожие шаблоны и их отличия
Абстрактная фабрика | Фабричный метод | Строитель |
Порождает семейство объектов с определенными интерфейсами. | Порождает один объект с определенным интерфейсом. | Создает в несколько шагов один сложный (составной) объект. |
Интерфейс, реализуемый классами. | Метод класса, который переопределяется потомками. | Интерфейс строителя, реализуемый классами, и класс для управления процессом. |
Скрывает реализацию семейства объектов. | Скрывает реализацию объекта. | Скрывает процесс создания объекта, порождает требуемую реализацию. |
Кроме того, Абстрактную фабрику можно рассматривать как набор Фабричных методов. Обратное утверждение не верно, поскольку произвольный набор не гарантирует создание объектов семейства.
Реализация шаблона в общем виде
- разрабатываем интерфейсы объектов семейства и Абстрактной фабрики;
- создаем семейства объектов и реализации Абстрактной фабрики для них;
- в программе, например, в зависимости от версии ОС, конфигурации или другого параметра, порождается необходимая реализация Абстрактной фабрики.
- в дальнейшем используется только интерфейсы как Абстрактной фабрики, так и порождаемых ей объектов.
Примеры реализации
1. Стандартный подход
Наглядный пример Абстрактной фабрики это поддержка разных стилей графического интерфейса приложения. Предположим, у нас есть "стандартный" (Defalut GUI) и "раскрашенный" (Skinned GUI) вид окон и элементов управления.
Определим интерфейсы объектов и Абстрактной фабрики (методы и свойства убраны для краткости):
1 2 3 4 5 6 7 8 9 10 | public interface IWindow { }
public interface IButton { }
public interface ITextBox { }
public interface IGUIFactory
{
IWindow CreateWindow();
IButton CreateButton();
ITextBox CreateTextbox();
}
|
Создадим реализацию каждого семейства и их Абстрактной фабрики:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /// Default GUI
public class DefaultWindow : IWindow { }
public class DefaultButton : IButton { }
public class DefaultTextBox : ITextBox { }
public class DefaultGUIFactory : IGUIFactory
{
public IWindow CreateWindow() { return new DefaultWindow(); }
public IButton CreateButton() { return new DefaultButton(); }
public ITextBox CreateTextbox() { return new DefaultTextBox(); }
}
/// Skinned GUI
public class SkinnedWindow : IWindow { }
public class SkinnedButton : IButton { }
public class SkinnedTextBox : ITextBox { }
public class SkinnedGUIFactory : IGUIFactory
{
public IWindow CreateWindow() { return new SkinnedWindow(); }
public IButton CreateButton() { return new SkinnedButton(); }
public ITextBox CreateTextbox() { return new SkinnedTextBox(); }
}
|
Все готово и осталось только рассмотреть пример использования. Пусть это будет метод, создающий окно для ввода строки пользователем:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public string GetUserInput(IGUIFactory guiFactory)
{
IWindow wndInput = guiFactory.CreateWindow();
IButton btnOk = guiFactory.CreateButton();
IButton btnCancel = guiFactory.CreateButton();
ITextBox tbInput = guiFactory.CreateTextbox();
wndInput.AddChild(btnOk);
wndInput.AddChild(btnCancel);
wndInput.AddChild(tbInput);
return tbInput.GetText();
}
|
При вызове этого метода, необходимо передать ему экземпляр нужной Абстрактной фабрики (DefaultGUIFactory или SkinnedGUIFactory) для получения окна заданного вида.
2. Использование generics (общих типов/шаблонов)
Очень часто процесс создания классов, реализующих один и тот же интерфейс, идентичен. В таких случаях код Абстрактных фабрик будет отличаться только именами классов. Поэтому вполне логично использовать generics. Предыдущий вариант можно переделать вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class GUIFactoryGeneric<TWindow, TButton, TTextBox> : IGUIFactory
where TWindow : IWindow, new ()
where TButton : IButton, new ()
where TTextBox : ITextBox, new ()
{
public IWindow CreateWindow() { return new TWindow(); }
public IButton CreateButton() { return new TButton(); }
public ITextBox CreateTextbox() { return new TTextBox(); }
}
public class DefaultGUIFactory :
GUIFactoryGeneric<DefaultWindow, DefaultButton, DefaultTextBox> { };
public class SkinnedGUIFactory :
GUIFactoryGeneric<SkinnedWindow, SkinnedButton, SkinnedTextBox> { };
|
Но есть проблема: данный вариант не может гарантировать, что с помощью GUIFactoryGeneric порождается именно семейство объектов. Что, ну кроме здравого смысла, мешает написать вот такой странный код: GUIFactoryGeneric<SkinnedWindow, DefaultButton, DefaultTextBox>? Хотя, это может быть даже просто опечатка.
Для решения этой проблемы скроем generic-класс внутри статического. А для создания экземпляров Абстрактной Фабрики используем параметризованный Фабричный метод.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public static class GUI
{
private class GUIFactoryGeneric<TWindow, TButton, TTextBox> : IGUIFactory
where TWindow : IWindow, new ()
where TButton : IButton, new ()
where TTextBox : ITextBox, new ()
{
public IWindow CreateWindow() { return new TWindow(); }
public IButton CreateButton() { return new TButton(); }
public ITextBox CreateTextbox() { return new TTextBox(); }
}
public enum Style { Default, Skinned }
public static IGUIFactory GetFactory(Style guiStyle)
{
switch (guiStyle) {
case Style.Default:
return new GUIFactoryGeneric<DefaultWindow, DefaultButton, DefaultTextBox>();
case Style.Skinned:
return new GUIFactoryGeneric<SkinnedWindow, SkinnedButton, SkinnedTextBox>();
}
throw new ArgumentException( "An invalid guiStyle: " + guiStyle.ToString());
}
}
|
Теперь, используя GetFactory(), можно создавать только заданные реализации Абстрактной Фабрики.