Паттерн проектирования AbstractFactory
( Алексей Желев )
- Введение
- Фаза 1
- Фаза 2
- Фаза 3
- Фаза 4
- Фаза 5
- Фаза 6
- Фаза 7
- Фаза 8 и последняя
- Дополнительные рассуждения
- Когда необходимо использовать паттерн Абстрактная фабрика?
- Используемая литература:
Введение
Программируя не один год, я время от времени наталкивался на мысль, что многие куски кода можно каким то образом объединить, систематизировать, выделить что ли. Также пришло понимание того, что старый подход к программированию — сразу браться за кодирование, уже не подходит. Я стал понимать, что написание серьезного программного продукта состоит из нескольких этапов:
- анализ
- проектирование
- кодирование
- тестирование
- внедрение
и т.д. и т.п.
Причем анализ и проектирование, на мой взгляд, являются самыми тяжелыми и важными этапами. Можно сказать, что от них зависит судьба проекта. Может пройти несколько месяцев анализа и проектирования, прежде чем вы примитесь за кодирование. А тестирование начинается уже на этапе анализа, а не только после этапа кодирования (как я думал раньше).
Именно проектирование (если быть точнее — объектно-ориентированное проектирование) стало меня привлекать. Впервые о паттернах проектирования, я услышал 3 года назад. Но к своему стыду я не обратил на них никакого внимания (наверно сказалось отсутствие практического опыта разработки проектов). Необходимо было немало времени, чтобы осознать их (паттернов) важность. Попытался прочитать несколько страниц из книги Design Patterns (gung of four) на английском языке, но понял, что не осилю ее. Не только потому, что мой английский оставляет желать лучшего, но и потому что материя сложная. Тогда решил прочитать книгу на русском языке. Спасибо сайту http://ooad.asf.ru/. Там выложена книга в переводе (что большая редкость сегодня). Прочитав книгу — мало что понял. Понятно было одно: надо каждый паттерн разбирать досконально.
Прочитав о паттерне AbstractFactory, в моей голове осталось лишь одно. Ощущение того, что это, что-то важное, хотя и непонятное. Прочитал второй раз — никаких изменений. Меня это стало немного дразнить. Конечно, я понимал, что материя не из легких, но я также понимал, что этот материал очень важен. Очень важно понять его, «пропустить» через себя. Иначе все бесполезно. Так я и пришел к своим рассуждениям.
Фаза 1
Дано: Client1, Client2, …, ClientN, ClientCreator, MacWindow
Задача: Создать объект типа MacWindow.
Схема:

Имплементация:
public class MacWindow {
Public MacWindow () {
}
}
public class Client1 {
MacWindow macWindow = null;
public Client1() {
macWindow = new MacWindow ();
}
}
public class Client2 {
MacWindow macWindow = null;
public Client2() {
macWindow = new MacWindow ();
}
}
...
public class ClientN {
MacWindow macWindow = null;
public ClientN() {
macWindow = new MacWindow ();
}
}
public class ClientCreator{
Client1 client1 = null;
Client2 client2 = null;
…
ClientN clientN = null;
Public clientCreator() {
}
client1 = new Client1();
client2 = new Client2();
…
clientN = new ClientN();
}
Фаза 2
Дано: Client1, Client2, …, ClientN, ClientCreator, MacWindow, SwingWindow
Недостаток Фазы 1 состоит в том, что при необходимости изменить создаваемый объект у клиентских классов, необходимо изменить ВСЕ клиентские классы, т.е.
public class Client1 {
SwingWindow swingWindow = null;
public Client1() {
swingWindow = new SwingWindow();
}
}
…
public class ClientN {
SwingWindow swingWindow = null;
public ClientN() {
swingWindow = new SwingWindow();
}
}
Решение:
Создать родительский класс для различных типов окон
Схема:

public abstract class Window {
}
public class MacWindow extends Window {
}
public class SwingWindow extends Window {
}
public class Client1 {
Window window = null;
public Client1() {
}
public void setWindow(Window window) {
this.window = window;
}
}
public class Client2 {
Window window = null;
public Client2() {
}
public void setWindow(Window window) {
this.window = window;
}
}
...
public class ClientN {
Window window = null;
public ClientN() {
}
public void setWindow(Window window) {
this.window = window;
}
}
Указание конкретного типа окна выносится в класс ClientCreator
public class ClientCreator{
Client1 client1 = null;
Client2 client2 = null;
…
ClientN clientN = null;
Public clientCreator() {
}
client1 = new Client1();
client1.setWindow(new MacWindow());
client2 = new Client2();
client2.setWindow(new SwingWindow());
…
clientN = new ClientN();
clientN.setWindow(new SwingWindow());
}
При необходимости изменить создаваемый объект, необходимо изменить только класс ClientCreator, т.е.:
public class ClientCreator {
Client1 client1 = null;
Client2 client2 = null;
…
ClientN clientN = null;
Public clientCreator() {
}
client1 = new Client1();
client1.setWindow(new SwingWindow());
client2 = new Client2();
client2.setWindow(new MacWindow());
…
clientN = new ClientN();
clientN.setWindow(new MacWindow());
}
Фаза 3
Решение из Фазы 2, также подходит и при добавлении нового типа создаваемого объекта.
Добавим новый тип создаваемых объектов — MacScrollBar, SwingScrollBar с родительским классом ScrollBar

public class Client1 {
Window window = null;
ScrollBar scrollbar = null;
public Client1({
}
public void setWindow(Window window) {
this.window = window;
}
public void setScrollBar(ScrollBar scrollbar) {
this.scrollBar = scrollbar;
}
}
public class Client2 {
Window window = null;
ScrollBar scrollbar = null;
public Client2({
}
public void setWindow(Window window) {
this.window = window;
}
public void setScrollBar(ScrollBar scrollbar) {
this.scrollBar = scrollbar;
}
}
...
public class ClientN {
Window window = null;
ScrollBar scrollbar = null;
public ClientN({
}
public void setWindow(Window window) {
this.window = window;
}
public void setScrollBar(ScrollBar scrollbar) {
this.scrollBar = scrollbar;
}
}
public class ClientCreator{
Client1 client1 = null;
Client2 client2 = null;
…
ClientN clientN = null;
public clientCreator() {
}
client1 = new Client1()
client1.setWindow(new SwingWindow());
client1.setScrollBar(new SwingScrollBar());
client2 = new Client2();
client2.setWindow(new MacWindow());
client2.setScrollBar(new MacScrollBar());
…
clientN = new ClientN();
clientN.setWindow(new SwingWindow());
clientN.setScrollBar(new SwingScrollBar());
}
Фаза 4
Так как клиентские классы, в общем случае, создаются из различных мест в проекте, то схема в Фазе 2 преобразуется в следующий вид:

Решение из Фазы 2 и 3 не подходит, так как при необходимости изменить создаваемый объект, необходимо менять код в КАЖДОМ классе ClientCreator-ов
Решение:
Родительские классы сами определяют тип создаваемых объектов.
Схема:

public abstract class Window {
public static Window createWindow() {
if (mac) {
return new MacWindow();
}
if (swing) {
return SwingWindow();
}
}
public class MacWindow extends Window {
}
public class SwingWindow extends Window {
}
public abstract class ScrollBar {
public static ScrollBar createScrollBar() {
if (mac) {
return new MacScrollBar();
}
if (swing) {
return SwingScrollBar();
}
}
public class MacScrollBar extends ScrollBar{
}
public class SwingScrollBar extends ScrollBar {
}
public class Client1 {
Window window = null;
ScrollBar scrollbar = null;
public Client1() {
}
public void setWindow() {
window = Window.createWindow();
}
public void setScrollBar() {
scrollbar = ScrollBar.createScrollBar();
}
}
public class Client2 {
Window window = null;
ScrollBar scrollbar = null;
public Client2() {
}
public void setWindow() {
window = Window.createWindow();
}
public void setScrollBar() {
scrollbar = ScrollBar.createScrollBar();
}
}
...
public class ClientN {
Window window = null;
ScrollBar scrollbar = null;
public ClientN() {
}
public void setWindow() {
window = Window.createWindow();
}
public void setScrollBar() {
scrollbar = ScrollBar.createScrollBar();
}
}
public class ClientCreator1 {
Client1 client = null;
public ClientCreator1() {
client = new Client1();
}
}
public class ClientCreator2 {
Client2 client = null;
public ClientCreator2() {
client = new Client2();
}
}
…
public class ClientCreatorN {
Client1 client = null;
public ClientCreatorN() {
client = new ClientN();
}
}
Данная схема решает ряд проблем:
- При добавлении нового типа создаваемых классов (Button, MacButton, SwingButton) код клиентских классов и их создателей (ClientCreator) не меняется.
- При добавлении новых подтипов (MotifWindow, MotifScrollBar, MotifButton) код клиентских классов и их создателей (ClientCreator) не меняется.
Фаза 5
Недостаток схемы в Фазе 4 состоит в том, что при добавлении нового подтипа (subType1,…, subTypeN) необходимо добавлять код в КАЖДОМ родительском классе.
Схема:

public class Window {
if (mac) {
}
if (swing) {
}
if (subType1) {//добавляем новый подтип
}
…
if (subTypeN) {{//добавляем новый подтип
}
}
public class ScrollBar {
if (mac) {
}
if (swing) {
}
if (subType1) {{//добавляем новый подтип
}
…
if (subTypeN) {{//добавляем новый подтип
}
}
Следуя до сих пор используемой логике, изолируем выбор подтипа в отдельный класс — AbstractFactory. Таким образом, при добавлении нового подтипа изменения коснутся ТОЛЬКО AbstractFactory.

public abstract class AbstractFactory {
protected Window window = null;
protected ScrollBar scrollbar = null;
…
protected AnyCreateObject createObject = null;
public static Window createWindow() {
if (motif) {
window = new MotifWindow();
}
if (swing) {
window = new SwingWindow();
}
…
}
public static ScrollBar createScrollBar() {
if (motif) {
scrollbar = new MotifScrollBar();
}
if (swing) {
scrollbar = new SwingScrollBar();
}
…
}
public static AnyCreateObject createAnyCreateObject () {
if (motif) {
createObject = new AnyCreateObject();
}
if (swing) {
createObject = new AnyCreateObject();
}
…
}}
public class Client1 {
private AbstractFactory factory = null;
private Window window =null;
private ScrollBar scrollbar = null;
public Client1() {
window = factory.createWindow();
scrollbar = factory.createScrollBar();
}
}
public class Client2 {
private AbstractFactory factory = null;
private Window window =null;
private ScrollBar scrollbar = null;
public Client2() {
window = factory.createWindow();
scrollbar = factory.createScrollBar();
}
}
…
public class ClientN {
private AbstractFactory factory = null;
private Window window =null;
private ScrollBar scrollbar = null;
public ClientN() {
window = factory.createWindow();
scrollbar = factory.createScrollBar();
}
}
public class ClientCreator1 {
private Client1 client = null;
public ClientCreator1() {
client = new Client1();
}
}
public class ClientCreator2 {
private Client2 client = null;
public ClientCreator2() {
client = new Client2();
}
}
..
public class ClientCreatorN {
private ClientN client = null;
public ClientCreatorN() {
client = new ClientN();
}
}
Фаза 6
Из кода AbstractFactory в Фазе5 видно, что при каждом условии создается семейство объектов. Наиболее лучшим решением является создание отдельного класса для каждого семейства.
Схема:

public abstract class AbstractFactory {
protected Window window = null;
protected ScrollBar scrollbar = null;
…
protected TypeN typeN = null;
public static AbstractFactory createFactory() {
if (mac) {
return new MacFactory();
}
if (swing) {
return new SwingFactory();
}
…
if (typeN) {
return new TypeNFactory();
}
}
public abstract Window createWindow();
public abstract ScrollBar createScrollBar();
…
public abstract TypeN createTypeN();
}
public class MacFactory extends AbstractFactory {
public MacFactory() {
}
public Window createWindow() {
return new MacWindow();
}
pubic ScrollBar createScrollBar() {
return new MacScrollBar();
}
…
public TypeN createTypeN() {
return new MacTypeN();
}
}
public class SwingFactory extends AbstractFactory {
public SwingFactory () {
}
public Window createWindow() {
return new SwingWindow();
}
pubic ScrollBar createScrollBar() {
return new SwingScrollBar();
}
…
public TypeN createTypeN() {
return new SwingTypeN();
}
}
…
public class Client1 {
private AbstractFactory factory = null;
private Window window = null;
private ScrollBar scrollbar = null;
..
private TypeN typeN = null;
public Client1() {
factory = AbstractFactory.createFactory();
window = factory.createWindow();
scrollbar = factory.createScrollBar();
typeN = factory.createTypeN();
}
}
public class Client2 {
private AbstractFactory factory = null;
private ScrollBar scrollbar = null;
public Client2() {
factory = AbstractFactory.createFactory();
scrollbar = factory.createScrollBar();
}
}
…
public class ClientN {
private AbstractFactory factory = null;
private Window window = null;
public ClientN() {
factory = AbstractFactory.createFactory();
window = factory.createWindow();
}
}
public class ClientCreator1 {
private Client1 client = null;
public CleintCreator1() {
client = new Client1();
}
}
…
public class ClientCreatorN {
private ClientN client = null;
public CleintCreatorN() {
client = new ClientN();
}
}
Фаза 7
Рассмотрим ситуацию, когда у нас есть набор независимых объектов различных типов Type1, Type2,…, TypeN
Тогда получается, что абстрактный класс AbstractFactory становится конкретным классом, так как других вариантов как кроме создания объектов тип Type1, Type2,…, TypeN не существует
Схема:

public class Factory {
private Type1 type1 = null;
private Type2 type2 = null;
private TypeN typeN = null;
public static Factory createFactory() {
return new Factory();
}
public Type1 createType1() {
type1 = new Type1();
return type1;
}
public Type2 createType2() {
type2 = new Type2();
return type2;
}
…
public TypeN createTypeN() {
typeN = new TypeN();
return typeN;
}
}
public class Client1 {
Factory factory = null;
public Client1() {
factory = Factory.createFactory();
}
public void doSomeThing() {
Type1 = factory.createType1();
Type25 = factory.createType25();
}
}
public class Client2 {
Factory factory = null;
public Client2() {
factory = Factory.createFactory();
}
public void doSomeThing() {
Type15 = factory.createType15();
Type45 = factory.createType45();
}
}
Но как только у нас появляется подтип одного из наших типов, то класс Factory становится абстрактным. И как следствие появляются подклассы SubType1Factory, SubType2Factory, ..., SubTypeNFactory класса AbstractFactory, который вернет объект одного из подтипов.
Схема:

public abstract class AbstractFactory {
private Type1 type1 = null;
// у типа Type2 появились подтипы –
// Type2SubType1, Type2SubType2,..,Type2SubTypeN
private Type2 type2 = null;
private TypeN typeN = null;
public static Factory createFactory() {
if (subType1) {
factory = new Type2SubType1();
}
if (subType2) {
factory = new Type2SubType2();
}
…
if (subTypeN) {
factory = new Type2SubTypeN();
}
}
public Type1 createType1() {
type1 = new Type1();
return type1;
}
public abstract Type2 createType2();
…
public TypeN createTypeN() {
typeN = new TypeN();
return typeN;
}
}
public class SubType1Factory extends AbstractFactory {
public Type2 createType2() {
return new Type2SubType1();
}
}
public class SubType2Factory extends AbstractFactory {
public Type2 createType2() {
return new Type2SubType2();
}
}
…
public class SubTypeNFactory extends AbstractFactory {
public Type2 createType2() {
return new Type2SubTypeN();
}
}
Фаза 8 и последняя
Как поступить в случае если AbstractFactory является интерфейсом. Решение, предложенное в предыдущих фазах (когда AbsractFactory является абстрактным классом) не подходит, так как мы не можем выполнить операцию Factory.createFactory();
Решение:
http://forum.java.sun.com/thread.jsp? forum=425&thread=392101
Схема:

Создаем «фабрику фабрик» — класс FactoryManager, т.е.
public class FactoryManager {
AbstractFactory factory = null;
public static Factory createFactory() {
if (subType1) {
factory = new Type2SubType1();
}
if (subType2) {
factory = new Type2SubType2();
}
…
if (subTypeN) {
factory = new Type2SubTypeN();
}
}
public interface AbstractFactory {
public Type1 createType1();
public Type2 createType2();
public TypeN createTypeN();
}
public class Client1 {
Type1 type25 = null;
AbstractFactory factory = null;
FactoryManager factoryManager = null;
public Client1() {
factoryManager = new FactoryManager();
factory = factoryManager.createFactory();
type25 = factory.createType25();
}
}
public class Client2 {
Type46 type46 = null;
Type50 type50 = null;
AbstractFactory factory = null;
FactoryManager factoryManager = null;
public Client2() {
factoryManager = new FactoryManager();
factory = factoryManager.createFactory();
type46 = factory.createType46();
type50 = factory.createType50();
}
}
…
public class ClientN {
Type1 type1 = null;
Typ2 type2 = null;
…
TypeN typeN = null;
AbstractFactory factory = null;
FactoryManager factoryManager = null;
public Client1() {
factoryManager = new FactoryManager();
factory = factoryManager.createFactory();
type1 = factory.createType1();
type2 = factory.createType2();
…
typeN = factory.createTypeN();
}
}
Так как фабрики существуют в единственном экземпляре, то лучше использовать паттерн Singleton для их создания. Например, вместо:
new MacWindowFactory()
написать:
MacWindowFactory.getInstance();
Дополнительные рассуждения

public class Factory {
Product1 product1 = null;
…
ProductM productM = null;
public Factory() {
}
Product1 public createProduct1() {
If (Type1) {
product1 = new Type1_Product1();
}
…
if (TypeN) {
product1 = new TypeN_Product1();
}
}
…
ProductM public createProductM() {
If (Type1) {
productM = new Type1_ProductM();
}
…
if (TypeN) {
productM = new TypeN_ProductM();
}
}
}
Если количество продуктов = M, а количество типов = N, то максимальное количество действий необходимых для создания продукта = N * M, т.е.
КД =N*M
т.е. количество действий зависит как от количества продуктов, так и от количества типов

public abstract class AbstractFactory {
public Product1 product1 = null;
…
public Product? product? = null;
public AbstractFactory getFactory() {
if (Type1) {
return new Type1Factory();
}
…
if (TypeN) {
return new TypeNFactory();
}
}
public abstract Product1 createProduct1();
…
public abstract ProductM createProductM();
}
public class Type1Factory {
public Product1 createProduct1() {
return new Type1_Product1();
}
…
public ProductM createProductM() {
return new Type1_ProductM();
}
…
public class TypeNFactory {
public Product1 createProduct1() {
return new TypeN_Product1();
}
…
public ProductM createProductM() {
return new TypeN_ProductM();
}
Если количество продуктов = M, а количество типов = N, то максимальное количество действий необходимых для создания продукта = N
КД = N
т.е. количество действий НЕ зависит как от количества создаваемых продуктов
Когда необходимо использовать паттерн Абстрактная фабрика?
Пример:
public class Client1() {
JButton button = null;
public Client1() {
button = new JButton();
}
}
…
public class ClientN() {
JButton button = null;
public ClientN() {
button = new JButton();
}
}
Что будет если нам нужно заменить класс JButton на MyButton, то необходимо в КАЖДОМ клиентском классе заменить JButton на MyButton. На первый взгляд вышеуказанный пример стоит реализовать с помощью паттерна абстрактная фабрика. Таким образом, отпадает необходимость менять код КАЖДОГО клиента. В результате у нас получается примерно следующая схема:

, а код имеет примерно следующий вид:
public class Client1{
Button button = null;
ButtonFactory bf = null;
pubic Client1(ButtonFactory bf) {
this.bf = bf;
button = bf.createButton();
}
}
…
public class ClientN{
Button button = null;
ButtonFactory bf = null;
pubic ClientN(ButtonFactory bf) {
this.bf = bf;
button = bf.createButton();
}
}
public abstract class ButtonFactory {
public createFactory() {
if (swing) {
return SwingButtonFactory();
}
if (myType) {
return MyTypeButtonFactory();
}
}
public Button createButton();
}
public class SwingButtonFactory() extends ButtonFactory{
Button public createButton() {
return new JButton();
}
}
public class MyTypeButtonFactory() extends ButtonFactory{
Button public createButton() {
return new MyTypeButton();
}
}
Можно заметить, что из-за одного создаваемого продукта (Button) создаются 3 класса (ButtonFactory, SwingButtonFactory, MyTypeButtonFactory).
Более удачным решением в этом случае является создание MyTypeButton в качестве наследника JButton. В КАЖДОМ клиентском классе мы, как и было сказано выше, просто заменяем JButton на MyButton. Эту замену можно сделать с помощью какого-нибудь инструмента по замену текста.
Итак, можно сказать следующее. Используйте паттерн абстрактная фабрика, когда:
- Класс заранее не знает, классы каких продуктов он должен создавать (полиморфизм). Режим run-time
- Абстрактная фабрика возвращает больше чем один тип продуктов
В пункте 1 можно использовать клаузу if для определения класса создаваемого продукта. Но в случае добавления нового продукта нужно будет добавлять новый класс продуктов в КАЖДУЮ if клаузу в КАЖДОМ клиентском классе.
Используемая литература:
- “Приемы объектно-ориентированного проектирования. Паттерны проектирования”, Э.Гамма, Р.Хелм, Р.Джонсон, Дж.Влиссидес. (Питер, 2001; ISBN:5272003551)
