Programming Taskbook


E-mail:

Пароль:

Регистрация пользователя   Восстановление пароля

 

ЮФУ SMBU

Электронный задачник по программированию

©  М. Э. Абрамян (Южный федеральный университет, Университет МГУ-ППИ в Шэньчжэне), 1998–2024

 

PT for OOP | Выполнение задания OOP1Creat1 | Знакомство с заданием

PrevNext


Знакомство с заданием

В качестве примера мы рассмотрим начальное задание группы OOP1Creat, посвященное паттерну Factory Method (Фабричный метод). Это один из наиболее простых и распространенных паттернов; при его реализации мы познакомимся со всеми основными понятиями ООП, которые используются в большинстве заданий.

Для создания заготовки выбранного задания надо, как обычно для задачника Programming Taskbook, воспользоваться программой PT4Load, запустив ее из рабочего каталога с помощью ярлыка Load. Если задачник Programming Taskbook for OOP (являющийся расширением базового задачника Programming Taskbook) установлен на компьютере, то после ввода имени задания OOP1Creat1 мы увидим в нижней части окна программы PT4Load информацию о выбранной группе, а кнопка «Загрузка» станет доступной.

Прежде всего, необходимо определиться с языком и средой программирования, которые будут использоваться при выполнении задания. Мы рассмотрим варианты решения для различных языков, однако начнем с языка С++ — одного из наиболее распространенных объектно-ориентированных языков. Среду программирования (из числа доступных на данном компьютере) можно выбрать с помощью контекстного меню программы PT4Load, щелкнув правой кнопкой мыши в любой позиции ее окна и выбрав требуемую среду из появившегося списка. Выбранная среда указывается в заголовке окна; на приведенном рисунке это среда Visual Studio Code для языка C++.

После нажатия кнопки «Загрузка» (или клавиши Enter) будет автоматически создан проект для выбранного задания, запустится выбранная среда программирования и в нее загрузится созданный проект.

Для языка C++, как я для ряда других языков, предусмотрены специальные программы-заготовки к каждому заданию из задачника Programming Taskbook for OOP. Эти заготовки уже содержат фрагменты реализации требуемой системы классов, а также указания по завершению разработки этой системы (оформленные в виде комментариев). Иногда в тексте заготовки присутствуют и дополнительные примечания, связанные с особенностью реализации паттерна на данном языке программирования.

Пока мы не будем анализировать текст программы-заготовки для задания OOP1Creat1, а сразу запустим полученный проект (нажав клавишу F5). В результате мы увидим на экране окно задачника и дополнительное окно с рисунком — диаграммой классов для изучаемого паттерна. Диаграмма классов всегда отображается поверх других окон на экране; для ее увеличения или уменьшения достаточно щелкнуть на ней левой или правой кнопкой мыши соответственно.

Обычно формулировка задания занимает много места, и читать ее в окне задачника не очень удобно. Начиная с версии 4.21 базового задачника Programming Taskbook, можно быстро получить вариант формулировки задания в html-формате, щелкнув в окне задачника на метке «Режим (F4)» в его правом верхнем углу или просто нажав клавишу F4. При этом текст формулировки будет загружен в веб-браузер, используемый в системе по умолчанию.

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

В окне задачника можно скрыть раздел с формулировкой, нажав клавишу Del (или щелкнув мышью на метке со знаком «x» в правом верхнем углу раздела с формулировкой). Это позволит сразу увидеть разделы с исходными и результирующими данными (если программа уже вывела какие-либо результаты), а также раздел с примером верного решения. Повторное нажатие клавиши Del восстанавливает отображение раздела с формулировкой.

В нижней части окна, в специальном разделе отладки, выводится текст той части заготовки, которая относится к создаваемой системе классов. Раздел отладки можно развернуть на всё окно задачника, скрыв другие его разделы. Для этого достаточно нажать комбинацию клавиш Ctrl+пробел (повторное нажатие комбинации Ctrl+пробел восстанавливает ранее скрытые разделы).

Чтобы закрыть окно задачника и вернуться в среду Visual Studio, достаточно нажать клавишу Esc или клавишу F5 (т. е. ту же клавишу, которая запускает программу из среды Visual Studio).

Обсудив основные действия, связанные с запуском программы и настройкой вида окна задачника, обратимся к формулировке задания OOP1Creat1. Ниже она приведена в том виде, в котором отображается в окне веб-браузера.

OOP1Creat1°.

Graph\OOPFactoryMethod.png

Factory Method (Фабричный метод) — порождающий паттерн.

Известен также под именем Virtual Constructor (Виртуальный конструктор).

Частота использования: высокая.

Назначение: определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанцировать. Фабричный метод позволяет классу делегировать инстанцирование подклассам.

Участники:

• Product (Продукт) — определяет интерфейс объектов, создаваемых фабричным методом;

• ConcreteProduct (Конкретный продукт) — реализует интерфейс Product;

• Creator (Создатель) — объявляет фабричный метод, возвращающий объект типа Product; может также определять реализацию фабричного метода по умолчанию, возвращающую некоторый конкретный продукт; реализует методы, в которых используется объект Product, созданный фабричным методом;

• ConcreteCreator (Конкретный создатель) — замещает фабричный метод для создания конкретного продукта.

Задание 1. Реализовать две иерархии классов, в одну из которых входят абстрактный создатель Creator и два конкретных создателя ConcreteCreator1 и ConcreteCreator2, а в другую — абстрактный продукт Product и два конкретных продукта ConcreteProduct1 и ConcreteProduct2.

Абстрактный класс Product содержит два абстрактных метода, связанных с получением и преобразованием строки: метод GetInfo без параметров, возвращающий строку, и метод Transform без параметров, который ничего не возвращает. Классы ConcreteProduct1 и ConcreteProduct2 содержат строковое поле info, которое инициализируется в конструкторе с помощью одноименного параметра, после чего в конструкторе класса ConcreteProduct1 поле info преобразуется к нижнему регистру, а в конструкторе класса ConcreteProduct2 — к верхнему. Метод GetInfo в каждом подклассе возвращает текущее значение поля info, а метод Transform преобразует это поле следующим образом: для ConcreteProduct1 он добавляет дополнительный пробел после каждого непробельного символа поля info (кроме его последнего символа), а для ConcreteProduct2 он добавляет два дополнительных символа * (звездочка) после каждого символа, отличного от звездочки (кроме последнего символа).

Абстрактный класс Creator содержит абстрактный фабричный метод FactoryMethod(info) со строковым параметром info, возвращающий ссылку на объект Product. Этот метод определяется в классах ConcreteCreator1 и ConcreteCreator2, причем фабричный метод класса ConcreteCreator1 создает объект типа ConcreteProduct1, а фабричный метод класса ConcreteCreator2 создает объект типа ConcreteProduct2; в любом случае конструктору создаваемого объекта передается параметр info фабричного метода.

В абстрактном классе Creator дополнительно определить метод AnOperation(info), который создает продукт с помощью фабричного метода, передавая ему параметр info, дважды вызывает метод Transform созданного продукта и с помощью его метода GetInfo возвращает полученный результат. Использование фабричного метода в методе AnOperation приводит к тому, что выполнение метода AnOperation в подклассах класса Creator дает различные результаты, зависящие от свойств создаваемых продуктов, причем такое поведение реализуется без изменения кода метода AnOperation.

Тестирование разработанной системы классов. Даны пять строк. Используя конкретных создателей 1 и 2, применить к каждой из данных строк метод AnOperation и вывести возвращаемый результат этого метода (вначале выводятся результаты для первой строки, затем для второй и т. д.).

Поскольку задание OOP1Creat1 является первым заданием, связанным с изучаемым паттерном, его формулировка содержит не только само задание, но и общее описание паттерна.

Вначале указывается диаграмма классов, связанная с данным паттерном. В ней каждый класс изображается в отдельном прямоугольнике; вертикальная линия с треугольным значком обозначает наследование; имена классов набраны полужирным шрифтом, причем имена абстрактных классов дополнительно выделяются курсивным начертанием. Методы и поля классов, относящиеся к паттерну, указываются в нижней части прямоугольника класса; если требуется привести фрагмент реализации какого-либо метода, то этот фрагмент изображается в специальном прямоугольнике с «загнутым» правым верхним углом. Специальные связи между классами, о которых говорится в описании паттерна, изображаются горизонтальными (обычно пунктирными) линиями со стрелками. Если связь обеспечивается с помощью определенного поля класса, то линия является сплошной, а у ее начала указывается имя поля (в диаграмме к заданию OOP1Creat1 такой вариант стрелок отсутствует).

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

После диаграммы классов указывается имя паттерна на английском и русском языке, его синонимы (если они имеются), частота использования и назначение паттерна. Очень важным элементом описания паттерна является список участников, который, во-первых, вводит их имена (также на английском и русском языках) и, во-вторых, определяет связи между ними. Фактически список участников является развернутым комментарием к диаграмме классов.

Иногда в конце общего описания паттерна, после списка участников, приводится дополнительная информация (в данном задании подобная информация отсутствует).

Затем идет собственно текст задания, начинающийся со слова «Задание» и его номера. Задания нумеруются отдельно для каждого паттерна; обычно с паттерном связывается одно или два задания.

Текст задания можно разбить на две части: в первой части подробно описывается система классов, которую надо реализовать, во второй, завершающей, — набор исходных данных, требующих обработки, и набор результатов, который надо получить (правильность полученных результатов будет свидетельствовать о правильности разработанной системы классов, связанной с изучаемым паттерном). Вторая часть начинается с текста «Тестирование разработанной системы классов»; в данном задании она состоит из одного абзаца, завершающего формулировку.

Система классов, связанных с паттерном, обычно включает одну или две иерархии классов, а также отдельные классы, не входящие в какую-либо иерархию. В случае иерархии классов ее корнем (базовым классом) обычно является абстрактный класс, содержащий только абстрактные методы (хотя иногда абстрактный класс может содержать и реализации некоторых методов; кроме того, в некоторых случаях корнем иерархии является «конкретный» класс, для которого можно создавать экземпляры).

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

В начале описания каждой иерархии классов рассматривается базовый класс, причем автоматически создаваемые заготовки, как правило, уже включают описание этого класса. В первой из иерархий, связанных с паттерном Factory Method, базовым классом является абстрактный класс Product с двумя абстрактными методами, который описывается в заготовке следующим образом:

class Product
{
public:
    virtual string GetInfo() = 0;
    virtual void Transform() = 0;
    virtual ~Product()
    {
        Show("Product");
    }
};

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

Для выполнения задания надо реализовать два конкретных потомка данного класса, о чем говорит комментарий, следующий за описанием класса Product:

// Implement the ConcreteProduct1
//   and ConcreteProduct2 descendant classes

Паттерн Factory Method включает две иерархии классов: кроме классов-продуктов надо иметь классы-создатели, которые создают и используют экземпляры требуемых классов-продуктов. Заготовка задания содержит описание абстрактного базового класса для иерархии классов-создателей:

class Creator
{
protected:
    virtual shared_ptr<Product> FactoryMethod(string info) = 0;
public:
    string AnOperation(string info);
    virtual ~Creator()
    {
        Show("Creator");
    }
};

string Creator::AnOperation(string info)
{
    auto p = FactoryMethod(info);
    p->Transform();
    p->Transform();
    return p->GetInfo();
}

В данный класс тоже включен деструктор, выводящий его имя в раздел отладки.

Следует также обратить внимание на то, что абстрактный метод FactoryMethod имеет возвращаемый тип shared_ptr<Product>, а не тип «обычного» указателя Product*. Класс-шаблон shared_ptr<T> определяет так называемый умный, или интеллектуальный указатель (smart pointer). Эти указатели были добавлены в стандарт C++11 и обеспечивают автоматическое освобожение связанной с ними памяти благодаря механизму подсчета ссылок. При выполнении любого задания на языке C++ рекомендуется использовать умные указатели. Это, в частности, позволит избавиться от явного вызова операций new и delete. Для умных указателей можно использовать обычные операции разадресации * и ->.

Кроме виртуального деструктора класс Creator содержит еще один, невиртуальный метод, который не является абстрактным: это метод AnOperation, реализация которого наследуется без изменений любыми классами-потомками. В заготовке приводится полный текст реализации данного метода.

Заметим, что именно метод AnOperation (в сочетании с методом FactoryMethod) иллюстрирует основную идею паттерна Factory Method. Назначением фабричного метода является создание одного из конкретных классов-продуктов, входящих в иерархию. В данном случае фабричным методом является метод FactoryMethod, возвращающий ссылку на объект типа Product. В классе Creator метод FactoryMethod определен как абстрактный. В различных потомках класса Creator этот метод переопределяется для создания различных конкретных продуктов (объектов классов-потомков базового класса Product). Поэтому в методе AnOperation, который наследуют все классы-создатели, создается (и затем дважды преобразуется) некоторый класс-продукт (а именно тот, который создается фабричным методом конкретного класса-создателя). Таким образом, алгоритм обработки любого класса-продукта (в данном случае его двойное преобразование методом Transform) может быть реализован один раз в базовом классе-создателе, причем при его вызове в любом конкретном классе-создателе этот алгоритм будет применяться к различным продуктам, приводя к разным результатам. Именно для возможности единообразного создания различных продуктов вводится единый интерфейс для их создания, оформленный в виде фабричного метода (в нашем случае метода FactoryMethod).

Примечание 1. Фабричный метод FactoryMethod описан в классе Creator как защищенный (protected); таким образом, он может вызываться только в классах, входящих в иерархию классов-создателей, при реализации других методов. Впрочем, фабричный метод может описываться и как открытый (public); в этом случае он становится доступен для вызова внешними программами. Часто открытые фабричные методы появляются в составе другого порождающего паттерна — Abstract Factory (Абстрактная фабрика), которому посвящены задания OOP1Creat4–5.

Примечание 2. В методе AnOperation мы использовали еще одну полезную возможность, появившуюся в стандарте C++11: автоматическое определение типа переменной по типу ее инициализирующего значения. Для этого достаточно указать при описании переменной вместо ее типа слово auto.

Чтобы подчеркнуть факт неизменности метода AnOperation, предназначенного для обработки любых объектов-продуктов, комментарий в созданной заготовке, расположенный после описания класса Creator, имеет следующий вид:

// Implement the ConcreteCreator1
//   and ConcreteCreator2 descendant classes;
//   the AnOperation method should not be
//   overridden in these classes

Заметим, что на диаграмме классов соответствующий метод тоже имеет имя AnOperation. Как правило, в первом задании, связанном с изучением определенного паттерна, при описании системы классов используются имена, совпадающие с теми именами, которые указаны на диаграмме классов (и в списке участников). Это позволяет рассматривать диаграмму классов не только как абстрактное описание паттерна, но и как иллюстрацию к той конкретной системе классов, которая должна быть реализована в задании.


PrevNext

 

Рейтинг@Mail.ru

Разработка сайта:
М. Э. Абрамян, В. Н. Брагилевский

Последнее обновление:
01.01.2024