Предыдущая Оглавление Следующая
Предположим, Вы пишете игру. Стратегию. У Вас есть классы "Ферма", "Фермер", "Рыцарь". Где-то в коде для производства экземпляров Вы пишете
CFarm *theFarm = new CFarm; CFarmer *theFarmer = new CFarmer; CKnight *theKnight = new CK night;
или наподобие того. Они взаимодействуют между собой, могут друг друга производить или уничтожать, изменять состояния. Но стратегия предполагает противника. Пусть будут гномы, и они представлены классами "Шахта", "Шахтер", "Топорник": CMine, CMiner, CAxer. Алгоритм (движок, engine) принципиально один и тот же, и будет скучно переписывать куски кода только из-за того, что конструкторы НЕВИРТУАЛЬНЫЕ . Следовательно, нужно как-то виртуализовать производство юнитов, а способ для этого один: выделить производство в специальный класс (фабрику объектов), и в нем объявить виртуальный метод производства.
Подробнее:
CProductive *CreateProductive(); CWorker *CreateWorker(); CWarrior *CreateWarrior();
Это структура паттерна. Клиент использует продукты, и обращается за их
производством к абстрактной фабрике.
Этот паттерн очень похож на абстрактную фабрику, но в нем акцентируется пошаговое конструирование объекта - в отличие от фабрики, где конструируется семейство классов.
Предположим, что в Вашей игре нужно строить дворец правителя. Дворец состоит из трона, тюрьмы и часовни. Ваша игра должна создать дворец для каждой расы, и для создания дворца определяется абстрактный класс строитель:
class CPalaceBuilвer { public: // Эти функции выстраивают дворец virtual void buildPalace(){}; virtual void buildTron(){}; virtual void buildPrison(){}; virtual void buildChurch(){}; // А эта возвращает уже построенный дворец, // для базового класса пустышку. virtual CPalace* getPalace ( return NULL); }
Функции построения могут быть чистыми виртуальными или нет. Во всяком случае CPalaceBuilder должен объявить интерфейс для строительства дворца. Конкретные дворцы для гномов и людей должны создаваться классами-строителями, производными от CPalaceBuilder. Теперь движок Вашей игры может создавать дворцы для разных рас по одному алгоритму, если передавать в функцию создания разные указатели на разные конкретные строители:
CPalace* MyGame::createPalace(CPalaceBuilder *builder) { // Обратились к строителю для начала построения builder->buildPalace(); // Теперь все части дворца builder->buildTron(); builder->buildPrison();builder->buildChurch(); // Теперь получаем собранный дворец - для строителя // это сигнал, что все закончено return builder->getPalace(); }
Совсем необязательно, чтобы продуктом был какой-то красивый составной объект. Вполне возможно, что Ваше приложение читает файл и просто желает его преобразовать в произвольный формат. Тогда преобразованием будет заниматься исключительно специализированный строитель - по одному на каждый формат. Типа такого:
CTargetDoc* CMyApp::convertTo(CConvTo& _cc) { // Начали новый документ _cc.startTarget(); // считываем до конца источник и направляем // стоителю слова на обработку while (!myFile.eof()) _cc.convertToken(myFile.readToken()); // Если все закончено, получить целевой документ return _cc.getTarget(); }
Мы будем получать документ в формате, определяемом параметром _cc - который собственно и есть строитель.
Структура паттерна такова: директор направляет строителю запросы
на создание частей целевого объекта, и по завершении строительства запрашивает
целевой объект полностью и сразу.
Фабричный метод - это основание для работы абстрактной фабрики и строителя. Предположим, наша игра должна производить территорию.
class CGame { public: virtual CTerrain* createTerrain(); //.... } class CTerrain { //.... }
Оба класса абстрактные, потому что игра может вестись за людей или гномов, и соответственно территории могут быть людские или гномьи, а просто игры и территории не бывает. Игра должна породить территорию в зависимости от того, кто она (игра) на самом деле.
class CHumanTerrain : public CTerrain { //.... } class CHumanGame : public CGame { CTerrain* createTerrain () { return new CHumanTerrain; }; }
Производящий метод createTerrain и есть фабричный метод. Структура паттерна такова: класс CCreator желает получать
CProduct, но предоставляет своим конкретным потомкам право решать, какой
конкретный продукт производить.
Предположим, что Ваша стратегия может манипулировать воинами CWarrior, поведение которых практически одинаково (т.е. они могут охранять, патрулировать, сражаться и идти) - единственно чем они отличаются -видом вооружения. Значит, лучники и копейщики (CArcher, CPikeman) порождаются от базового класса CWarrior .
Производить их вручную неразумно. Поможет ли абстрактная фабрика? Конечно. Если видов вооружения не слишком много. А если много - то придется прописывать по конкретной фабрике на каждого воина, включая хлебореза. Если Вы считаете, что хлеборезу будет жирно иметь свою фабрику, можно найти и другой вариант: пусть хлеборез размножается самостоятельно. Нужно только абстрагироваться от этого процесса - функция размножения возлагается на CWarrior.
// Абстрактный класс class CWarrior { // Чистая виртуальная производящая функция // создающая клон, не обязательно полную копию. virtual CWarrior* clone() const =0; }; // конкретный класс class CArcher : public CWarrior { // производящая функция переопределена. CWarrior* clone () const { return new CArcher;}; };
Теперь клиент для получения экземпляра должен обратиться к предварительно заданному экземпляру, который специально для этого создан (вероятно) - он, собственно и есть прототип. Прототипом можно конфигурировать абстрактную фабрику. Он вообще является вырожденным случаем фабрики - он производит только один класс -который и есть он сам. Главная сложность - поддержка clone() со стороны всех классов.
Это структура паттерна.
В Вашей игре есть некий склад с ресурсами. Ваши воины там берут еду, танки загружают снаряды. Склад может быть только один. Как гарантировать, что Вы случайно не создали второй склад? Что юниты в любой части игры обратятся обязательно к одному складу? Или может Вы создали абстрактную фабрику, и опять же хотите, чтобы она была одна?
Единичность гарантировать просто - нужна такая фабрика, которая отработала бы только один раз - а на остальные запросы выдавала уже построенный экземпляр. Ничего страшного, если фабрика будет сама тем классом, который производит. Это мы уже видели в прототипе.
// декларация одиночки class CSingleton { public: // эта функция выдает единственный экземпляр static CSingleton* GetInstance (void); // удалить его тоже когда-то придется. static void DestroyInstance (void) { if (m_instance) delete m_instance;} ; private: // статический указатель на единственный экземпляр static CSingleton* m_instance; protected: CSingleton(){}; }; // Инициализация проходит нулем. CSingleton* CSingleton::m_instance = NULL; // Обращение к единственному экземпляру. CSingleton* CSingleton::GetInstance() { if (!m_instance) m_instance = new CSingleton; return m_instance; }
О порождающих паттернах все. К ним относятся.
Абстрактная фабрика (Abstract Factory). Строитель (Builder). Прототип (Prototype). Фабричный метод (Factory Method). Одиночка (Singleton).
Порождающие паттерты создают новые экземпляры при помощи производящих функций, подобные следующему :
CProduct* CFactory::createProduct () { return new CProduct;};
так что фабрика создает экземпляр, но не управляет его временем жизни. Это значит, что кто-то должен заботиться о правильном уничтожении экземпляров. Выбирать имеет смысл между абстрактной фабрикой, строителем и прототипом; одиночка и фабричный метод имеют специальное применение, они слишком негибкие. Все остальные подробности - у Гаммы сотоварищи; Addison-Wesley печатало "Desing Patterns" 14 раз - это о чем то говорит?