Programming Taskbook


E-mail:

Пароль:

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

 

ЮФУ SMBU

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

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

 

PT for OOP | Выполнение задания OOP1Creat1 | Решение на языке C++

PrevNext


Решение на языке C++

Разобравшись с принципами использования паттерна Factory Method и изучив заготовку, созданную для задания OOP1Creat1, приступим к выполнению задания. Чтобы завершить формирование требуемой системы классов, нам осталось реализовать набор «конкретных» классов-продуктов и классов-создателей.

В конкретные классы-продукты надо добавить строковое поле info и метод Transform, преобразующий это поле по правилам, описанным в задании. Кроме того, в эти классы надо добавить метод GetInfo, возвращающий текущее значение поля info. Конструкторы инициализируют поле info с помощью своего строкового параметра, причем для конкретного продукта 1 поле info должно содержать символы в нижнем регистре, а для конкретного продукта 2 — в верхнем.

Приведем начальную часть реализации классов ConcreteProduct1 и ConcreteProduct2, которую надо поместить на позицию, отмеченную комментарием «Implement the ConcreteProduct1 and ConcreteProduct2 descendant classes» (поскольку необходимо, чтобы к этому моменту в коде уже был определен класс Product):

class ConcreteProduct1 : public Product
{
    string info;
public:
    ConcreteProduct1(string info);
    string GetInfo() override;
    void Transform() override;
};

class ConcreteProduct2 : public Product
{
    string info;
public:
    ConcreteProduct2(string info);
    string GetInfo() override;
    void Transform() override;
};

string ConcreteProduct1::GetInfo()
{
    return info;
}

string ConcreteProduct2::GetInfo()
{
    return info;
}

Мы описали классы ConcreteProduct1 и ConcreteProduct2 и привели реализацию простых методов GetInfo для этих классов. Обратите внимание на необходимость указания слова public перед именем базового класса в заголовке описания класса. Модификатор override не является обязательным, однако он позволяет явным образом указать, что данный метод является переопределением метода базового класса. Таким образом, если при наборе имени переопределяемого метода будет допущена опечатка (например, Getinfo вместо GetInfo), то при компиляции она вызовет сообщение об ошибке, поскольку в базовом классе такой метод отсутствует.

Напомним также, что в конце описания класса необходимо указывать символ «точка с запятой».

Преобразование регистра символов, требуемое в конструкторах классов-продуктов, удобно выполнить с помощью алгоритма transform, поэтому подключим к нашей программе заголовочный файл <algorithm>:

#include <algorithm>

Нам не требуется явным образом указывать имя пространства имен std, так как в программе-заготовке присутствует соответствующая директива: using namespace std;

Кроме того, во всех программах-заготовках содержатся директивы подключения двух заголовочных файлов: <memory> и <vector>. Первый файл позволяет использовать в программе различные виды умных указателей, а второй — контейнеры типа vector<T>, которые могут оказаться полезными при решении многих задач.

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

#include <algorithm>
#include <vector>
#include <memory>
#include "pt4.h"
using namespace std;

Добавим реализацию конструкторов конкретных классов-продуктов:

ConcreteProduct1::ConcreteProduct1(string info)
{
    transform(info.begin(), info.end(), info.begin(),
        [](char c){ return tolower(c); });
    this->info = info;
}

ConcreteProduct2::ConcreteProduct2(string info)
{
    transform(info.begin(), info.end(), info.begin(),
        [](char c){ return toupper(c); });
    this->info = info;
}

Для преобразования исходной строки info к нижнему или верхнему регистру мы использовали алгоритм transform, указав в качестве его последнего параметра еще одно нововведение стандарта C++11 — лямбда выражение, в котором для каждого символа строки вызывается стандартная функция tolower или toupper. Это способ задания последнего параметра (функционального объекта) является одним из наиболее простых и наглядных.

Нам осталось реализовать метод Transform. Напомним фрагмент формулировки задания, описывающий действия метода Transform по преобразованию поля info:

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

Приведем один из вариантов реализации методов Transform. Обратите внимание на то, что символы строки info перебираются с конца, причем последний символ не анализируется, а новая строка вставляется после анализируемого символа.

void ConcreteProduct1::Transform()
{
    for (int i = info.length() - 2; i >= 0; i--)
        if (info[i] != ' ')
            info.insert(i + 1, " ");
}

void ConcreteProduct2::Transform()
{
    for (int i = info.length() - 2; i >= 0; i--)
        if (info[i] != '*')
            info.insert(i + 1, "**");
}

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

Реализация классов конкретных создателей ConcreteCreator1 и ConcreteCreator2 является более простой, поскольку в каждом из этих классов достаточно переопределить абстрактный метод FactoryMethod класса Creator. Уровень доступа к этому методу должен остаться прежним (protected).

Единственной особенностью в методе FactoryMethod является создание соответствующего объекта-продукта с применением умного указателя. Мы будем использовать шаблонную функцию make_shared<T>(params), в которой в качестве типа T указывается тип создаваемого объекта, а набор параметров params задает параметры, передаваемые конструктору создаваемого объекта.

Описание классов ConcreteCreator1 и ConcreteCreator2 и их методов FactoryMethod надо разместить на позиции, отмеченной комментарием «Implement the ConcreteCreator1 and ConcreteCreator2 descendant classes»:

class ConcreteCreator1 : public Creator
{
protected:
    shared_ptr<Product> FactoryMethod(string info) override;
};

class ConcreteCreator2 : public Creator
{
protected:
    shared_ptr<Product> FactoryMethod(string info) override;
};

shared_ptr<Product> ConcreteCreator1::FactoryMethod(string info)
{
    return make_shared<ConcreteProduct1>(info);
}

shared_ptr<Product> ConcreteCreator2::FactoryMethod(string info)
{
    return make_shared<ConcreteProduct2>(info);
}

Повторная компиляции программы также должна выполниться успешно.

Мы реализовали систему классов, соответствующую паттерну Factory Method. Осталось проверить ее правильность, обработав исходный набор данных, предлагаемый в задании.

Приведем еще раз завершающий абзац формулировки задания:

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

В рассматриваемом задании действия по проверке разработанной системы классов являются очень простыми. Нам даже не требуется использовать какие-либо структуры данных, поскольку строки можно обрабатывать последовательно, организуя их ввод в цикле в одну и ту же переменную. Эта часть решения должна быть помещена в функцию Solve (в которой уже присутствует вызов функции Task, инициализирующей задание). Для ввода исходных данных и вывода результатов, как обычно для заданий на языке С++, выполняемых с использованием электронного задачника Programming Taskbook, будем использовать специальный поток ввода-вывода pt, описанный в заголовочном файле pt4.h:

void Solve()
{
    Task("OOP1Creat1");
    ConcreteCreator1 c1;
    ConcreteCreator2 c2;
    for (int i = 0; i < 5; i++)
    {
        string s;
        pt >> s;
        pt << c1.AnOperation(s) << c2.AnOperation(s);
    }
}

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

Правильность полученных результатов свидетельствует о правильности разработанной нами системы классов. Обратите также внимание на текст, выведенный в разделе отладки. Он содержит десять слов «Product» и два слова «Creator»; это означает, что в программе было вызвано десять деструкторов для объектов типа Product и два деструктора для объектов типа Creator (точнее, для объектов какого-либо типа, производного от указанных типов, так как типы Product и Creator являются абстрактными). Выведенный в разделе отладки текст свидетельствует о том, что в нашей программе нет утечек памяти. Действительно, при ее работе мы создали два объекта типа Creator и десять раз вызвали их метод AnOperation, в каждом из которых был создан и обработан один объект типа Product. При этом объекты-создатели были созданы без выделения динамической памяти и были разрушены при завершении функции Solve, а объекты-продукты были созданы с применением функции make_shared и тоже были разрушены автоматически при разрушении связанных с ними умных указателей.

Итак, благодаря паттерну Factory Method нам удалось организовать разные варианты обработки различных продуктов, несмотря на то, что операция по их обработке (AnOperation) была реализована в единственном варианте в базовом классе Creator.

Чтобы увидеть протокол выполнения задания, в котором фиксируются все запуски учебных программ, достаточно щелкнуть в окне задачника на метке «Результаты (F2)» или нажать клавишу F2. На экране появится окно программы PT4Result, в котором будет содержаться примерно такой текст:

OOP1Creat1  c06/10 10:34 Ознакомительный запуск.--11
OOP1Creat1  c06/10 14:27 Задание выполнено!

Символ «c» перед датой означает, что задание выполнялось на языке C++.


PrevNext

 

Рейтинг@Mail.ru

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

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