Эта статья не логическое продолжение, а взгляд с другого бока.
Фабричный метод — это один из паттернов проектирования.
Классу заранее неизвестно, объекты каких подклассов ему нужно создавать.
Например есть класс Природа, который имеет свои подклассы: Человек, Животное, Растение.
Классу Природа может быть нужно создать объект тип которого любой из этих подклассов. Заранее неизвестно, какой из трёх.
В разных местах программы может быть необходимость приблизительно такого вида конструкции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Функция1 (Природа X){ switch (X){ Человек: СоздатьЧеловека; Животное: СоздатьЖивотное; Растение: СоздатьРастение; } } Функция2 (Природа X){ switch (X){ Человек: СоздатьЧеловека; Животное: СоздатьЖивотное; Растение: СоздатьРастение; } } //МНОГО-МНОГО ФУНКЦИЙ С ТАКИМ КОДОМ И ПОСЛЕДНЯЯ С ТАКИМ ЖЕ ФункцияN (Природа X){ switch (X){ Человек: СоздатьЧеловека; Животное: СоздатьЖивотное; Растение: СоздатьРастение; } } |
В какой-то момент времени требуется добавить какой-то новый тип в понятия природы, например, тип микроб, тогда нужно "прочесать" все функции и везде добавить этот тип, а это много работы, много кода, следовательно легко ошибиться и где-то пропустить добавление, удаление или исправление.
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 |
Функция1 (Природа X){ switch (X){ Человек: СоздатьЧеловека; Животное: СоздатьЖивотное; Растение: СоздатьРастение; Микроб: СоздатьМикроба; } } Функция2 (Природа X){ switch (X){ Человек: СоздатьЧеловека; Животное: СоздатьЖивотное; Растение: СоздатьРастение; Микроб: СоздатьМикроба; } } //МНОГО-МНОГО ФУНКЦИЙ С ТАКИМ КОДОМ И ПОСЛЕДНЯЯ С ТАКИМ ЖЕ ФункцияN (Природа X){ switch (X){ Человек: СоздатьЧеловека; Животное: СоздатьЖивотное; Растение: СоздатьРастение; Микроб: СоздатьМикроба; } } |
Вовнутрь любой функции в какой-то момент времени подаётся указатель на объект типа Природа, где этот указатель может указывать на любой подтип класса Природа. Работа с принятым таким образом параметром, благодаря полиморфизму, вполне себе нормальная. Объект сам поймёт к какому подтипу он относится.
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 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <iostream> using std::cout; class Nature{ public: virtual void info() = 0; }; class Man:public Nature{ public: void info(){ cout << "MAN\n"; } }; class Animal:public Nature{ public: void info(){ cout << "Animal\n"; } }; void foo(Nature *X){ X->info(); //<-- Сам понимает к какому подтипу относится в момент вызова } //////////////////////////// int main(){ Man man_; Animal animal_; Nature *man = &man_; Nature *animal = &animal_; foo(man); foo(animal); } |
Но что, если нужно создать объект во время работы функции? Вот если на вход подан отсыл на животное, то создать внутри функции объект типа животное и вернуть указатель на тип животное, а если отсыл на человека, то создать внутри функции объект типа человек и вернуть указатель на человека, то что? Указатель-то там очень старается показать, что тип объекта своего он знает и выбирает всё в соответствиях тому типу, а мы тип объекта, на который указатель указывает, знаем? Можно создать указатель на Nature, но это не то. Ведь нужно создавать объект подтипа Nature, а не типа Nature да и класс Nature абстрактный (нам должен быть не доступный). Класс Nature как и мы не знает какого подтипа нужно создать объект. Ну а какого типа new Вы предлагаете? Какой тип должна возвращать создающая объект функция?
1 2 3 |
КАКОЙ_ТИП foo(Nature *X){ //Меняем функцию foo или точнее собрались её изменить так, return new [КАКОЙ_ТИП] //чтобы она возвращала указатель на один из подтипов природы } |
На помощь приходит фабричный метод. Это то самое оно; это то, что он даёт; это то, ради чего он существует; это то, зачем он нужен; это то, почему он описывается в паттернах.
Что у нас есть?
Есть класс, а у него какие-то подтипы.
Что нам надо?
Нужно наплодить этих подтипов немереное количество, да ещё и в случайном каком-то порядке (не совсем случайном конечно, порядок зависим от типов, приходящих вовнутрь функции, в каком порядке типы приходят, в том и создаются, а так как прийти могут в любом порядке, то получается, что в случайном). Заранее известно, когда нужно создавать объект, но неизвестен его тип.
Способ решения?
Фабричный метод.
Как это выглядит в коде?
Я буду использовать классический способ.
Класс Фабрика общий, потому что какого типа объект будет создаваться изначально — неизвестно. Это "руководящий класс", он сам ничего делать не будет, а только укажет на основные процессы, существующие в его производстве.
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 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <iostream> using std::cout; class Nature{ public: virtual void info() = 0; }; class Man:public Nature{ public: void info(){} }; class Animal:public Nature{ public: void info(){} }; /*Создаём руководящий класс*/ class Factory{ public: virtual Nature* create() = 0; //Чистая виртуальная функция показывает, что в производстве будет метод create //тип возвращаемого объекта Nature* - это указатель на общий класс, подтипы которого будут выбираться, указатель на Nature }; /* Пока бесполезная функция, типа мы пытались, пытались и всё в корень сломали template <typename T> T* foo(T *X){ return new T; } */ //////////////////////////// int main(){ } |
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include <iostream> using std::cout; class Nature{ public: virtual void info() = 0; }; class Man:public Nature{ public: void info(){} }; class Animal:public Nature{ public: void info(){} }; /*Создаём руководящий класс*/ class Factory{ public: virtual Nature* create() = 0; //Чистая виртуальная функция показывает, что в производстве будет метод create }; /*Создаём производство*/ class ManFactory:public Factory{ public: Nature* Create(){ return new Man; //<--- Создаём объект типа "человек" } }; class AnimalFactory:public Factory{ public: Nature* Create(){ return new Animal; //<--- Создаём объект типа "животное" } }; /*Создали производство*/ //////////////////////////// int main(){ } |
Обратите внимание, что внутри нашего названного производства как раз создаются объекты подтипов основного класса (не фабричного, а исходного).
return-ом возвращаются два типа указателей: на тип Man (человек) и на тип Animal (животное); Эти типы являются подтипом класса Природа.
Сколько у класса Nature подтипов, объекты, которых предполагается создавать (генерировать), столько и нужно создать классов для производства, по аналогии.
Вот и вся хитрость фабричного метода. Создали какой-то обобщённый класс (руководитель-инструктор) и создали от него производные классы для каждого создаваемого подтипа (само производство). Это важно, что для каждого. Сколько подтипов — столько и производящих, так скажем, станков.
Осталось только понять, как это можно использовать. Я в начале темы задался вопросом создания функции, поэтому применю сию конструкцию для создания различных объектов изнутри какой-то функции, классу не принадлежащей. На самом деле по разному можно использовать, просто это один из способов.
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
#include <iostream> using std::cout; class Nature{ public: virtual void info() = 0; virtual ~Nature(){} //<-- Виртуальный деструктор }; class Man:public Nature{ public: void info(){ cout << "I'm is man!\n"; //<-- Информация на экран для наглядности } }; class Animal:public Nature{ public: void info(){ cout << "I'm is animal!\n";//<-- Информация на экран для наглядности } }; /*Создаём руководящий класс*/ class Factory{ public: virtual Nature* Create() = 0; //Чистая виртуальная функция показывает, что в производстве будет метод Create virtual ~Factory(){} //Виртуальный деструктор }; /*Создаём производство*/ class ManFactory:public Factory{ public: Nature* Create(){ return new Man; //<-- Создаём объект типа "Человек" } }; class AnimalFactory:public Factory{ public: Nature* Create(){ return new Animal;//<-- Создаём объект типа "Животное" } }; /*Создали производство*/ /*Та самая функция, мозг выносящая*/ Nature* foo(Factory *value){ return value->Create(); } //////////////////////////// int main(){ ManFactory man; //Переменные для каждого подтипа AnimalFactory animal; Factory *ptr1 = &man; //Указатели на объекты нужных типов Factory *ptr2 = &animal; /////////////////////////// Nature *ObjectNature1 = foo(ptr1); //Создали человечка Nature *ObjectNature2 = foo(ptr2); //Создали животное ObjectNature1->info(); //Вывели информацию. См выше, в методы info дописан вывод на экран. ObjectNature2->info(); delete ObjectNature1, delete ObjectNature2; //Почистили память } |
Здесь опасность представляет утечка. В данном примере утечки нет. Я, изучая эту тему, столкнулся со множеством сайтов, где об утечке вообще не заботятся. Терять адреса ни в коем случае нельзя. Любым способом эти адреса нужно куда-то сохранять, чтобы корректно удалить. По этой причине у меня использованы несколько указателей, на некоторых сайтах примеры с записью указателей в массив. Это важно понимать, что указатель на начало выделенной однажды памяти легко может убежать на другой адрес, после чего тот, предыдущий адрес, куда он указывал, уже будет невозможно найти.
Чем лучше вы понимаете полиморфизм — тем проще вам будет понять, как это так получилось. Если вы не осилили ещё полиморфизм, то для изучения этой темы ваше время ещё не пришло. Здесь практически всё самое важное построено на полиморфизме.
Супер, мне очень понравилось, для базового, начального понимая этого паттерна, спасибо!