Перейти к основному содержимому

Пример 5. Декомпозиция проекта при работе с множествами объектов

Исходная задача

Предположим, что у нас есть класс Rectangle, в котором описаны некоторые его свойства и методы взаимодействия с ними:

#include <iostream>

class Rectangle {
private:
int a_, b_;
public:
Rectangle() {}

Rectangle(int a, int b) {
this->a_ = a;
this->b_ = b;
}

int getA() {
return a_;
}

int getB() {
return b_;
}

void setA(int a) {
this->a_ = a;
}

void setB(int b) {
this->b_ = b;
}

void set(int a, int b) {
setA(a);
setB(b);
}
};

int main() {
Rectangle *rec = new Rectangle(1, 3);
std::cout << "I know about the rectangle. It has: "
<< std::endl;
std::cout << " a=" << rec->getA() << std::endl;
std::cout << " b=" << rec->getB() << std::endl;
delete rec;
}

Обратите внимание, что названия private переменных имеют постфикс _, чтобы отличать их от общедоступных. Также названий переменных и методов используется верблюжий (CamelCase) стиль.

к сведению

О лучших стилистических практиках рассказано здесь.

Предположим, что нам поставили следующую задачу: обрабатывать в программе множество объектов класса Rectangle.

Подход 1: хранение множества объектов в месте его использования

Объявление множества в функции main, а его обработчиков - в main.cpp

Первое предположение о решении задачи, которое может прийти на ум, может быть желанием хранить нужное нам множество там, где мы с ним будем работать. В нашем случае делать это в функции main. В том же файле, где содержится функция main (main.cpp), расположить функции работы с нашим множеством. Итоговый результат будет таким:

#include <iostream>

class Rectangle {
private:
int a_, b_;
public:
Rectangle() {}

Rectangle(int a, int b) {
this->a_ = a;
this->b_ = b;
}

int getA() {
return a_;
}

int getB() {
return b_;
}

void setA(int a) {
this->a_ = a;
}

void setB(int b) {
this->b_ = b;
}

void set(int a, int b) {
setA(a);
setB(b);
}

};

void showRectangles(Rectangle *arr, int n) {
for (int i = 0; i < n; i++) {
std::cout << "I know about the rectangle # " << i
<< ". It has: " << std::endl;
std::cout << " a=" << arr[i].getA() << std::endl;
std::cout << " b=" << arr[i].getB() << std::endl;
}
}

int calculateAreas(Rectangle *arr, int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i].getA() * arr[i].getB();
}
return sum;
}

int main() {
int n = 2;
Rectangle *arr = new Rectangle[n];
arr[0].set(1, 3);
arr[1].set(4, 6);

showRectangles(arr, n);
std::cout << std::endl;
std::cout << "Sum of the areas of all rectangles: "
<< calculateAreas(arr, n) << std::endl;
delete[] arr;
}

Функция showRectangles у нас показывает информацию о всех прямоугольниках в множества, calculateAreas - суммирует площади прямоугольников из множества.

А что делать, если в нашей программе мы будем работать не только с объектами класса Rectangle, но и с Circle, Triangle, Square? Тогда в обработчики множеств объектов соответствующих классов нам также придется размещать в main.cpp. Чем это грозит?

  1. Нам придется придумывать длинные названия функций, которые выполняют одни и те же действия, но для разных объектов. Например, для расчеты суммы площадей множеств нам придется использовать названия calculateAreasRectangle, calculateAreasTriangle и т.д. Но в C++ имеется механизм перегрузки функций, который позволит нам использовать имя функции calculateAreas для разных множеств.

    Перегрузка функций — это механизм С++, благодаря которому функции с разным количеством или типами параметров могут иметь одинаковое имя (идентификатор).

  2. Даже если мы применили механизм перегрузки функций, мы сталкиваемся с ростом объема main.cpp. В лабораторной работе №1 мы познакомились о том, что для декомпозиции проекта можно использовать механизм статических библиотек классов. Значит мы можем унести наши обработчики множеств на уровень статических библиотек и разместить их по соседству с объявлением соответствующих классов или в самих классах.

Объявление множества в функции main, а его обработчики сделать методами объектов класса Rectangle

Остановимся на том, что функции работы с множествами мы будем размещать в самих классах. Для примера мы продолжим использовать файл main.cpp. Для класса Rectangle результат наших преобразований будет выглядеть следующим образом:

#include <iostream>

class Rectangle {
private:
int a_, b_;
public:
Rectangle() {}

Rectangle(int a, int b) {
this->a_ = a;
this->b_ = b;
}

int getA() {
return a_;
}

int getB() {
return b_;
}

void setA(int a) {
this->a_ = a;
}

void setB(int b) {
this->b_ = b;
}

void set(int a, int b) {
setA(a);
setB(b);
}

void showRectangles(Rectangle *arr, int n) {
for (int i = 0; i < n; i++) {
std::cout << "I know about the rectangle # " << i
<< ". It has: " << std::endl;
std::cout << " a=" << arr[i].getA() << std::endl;
std::cout << " b=" << arr[i].getB() << std::endl;
}
}

int calculateAreas(Rectangle *arr, int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i].getA() * arr[i].getB();
}
return sum;
}
};

int main() {
int n = 2;
Rectangle *arr = new Rectangle[n];
arr[0].set(1, 3);
arr[1].set(4, 6);

arr[0].showRectangles(arr, n);
std::cout << std::endl;
std::cout << "Sum of the areas of all rectangles: "
<< arr[0].calculateAreas(arr, n) << std::endl;
delete[] arr;
}

В этой реализации мы сталкиваемся с несколькими проблемами:

  • Первая проблема данных методов, о которой мы можем увидеть, это то, что вызов методов работы с массивами происходит через конкретный объект. Иными словами, нам для вызова метода работы с массивом нужно обратиться к какому-то объекту из этого массива и вызвать у него требуемый метод.
  • Вторая вытекающая проблема заключается в том, что методы работы с массивами зачем-то имеют доступу к конкретному экземпляра класса, через который вызываются данные методы. А это может повлечь проблему безопасной работы с конкретным объектом в случае внесения каких-то изменений по неосторожности. Пример, где вы можете столкнуться с проблемами безопасного управления данными: сортировка множества.

Решить эти проблемы можно достаточно просто: сделать эти методы статическими методами класса!

Демонстрация использования статических методов представлена в примере 3.

Объявление множества в функции main, а его обработчики сделать статическими методами класса Rectangle

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

#include <iostream>

class Rectangle {
private:
int a_, b_;
public:
Rectangle() {}

Rectangle(int a, int b) {
this->a_ = a;
this->b_ = b;
}

int getA() {
return a_;
}

int getB() {
return b_;
}

void setA(int a) {
this->a_ = a;
}

void setB(int b) {
this->b_ = b;
}

void set(int a, int b) {
setA(a);
setB(b);
}

static void showRectangles(Rectangle *arr, int n) {
for (int i = 0; i < n; i++) {
std::cout << "I know about the rectangle # " << i
<< ". It has: " << std::endl;
std::cout << " a=" << arr[i].getA() << std::endl;
std::cout << " b=" << arr[i].getB() << std::endl;
}
}

static int calculateAreas(Rectangle *arr, int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i].getA() * arr[i].getB();
}
return sum;
}
};

int main() {
int n = 2;
Rectangle *arr = new Rectangle[n];
arr[0].set(1, 3);
arr[1].set(4, 6);

Rectangle::showRectangles(arr, n);
std::cout << std::endl;
std::cout << "Sum of the areas of all rectangles: "
<< Rectangle::calculateAreas(arr, n) << std::endl;
delete[] arr;
}

Теперь вызов любого метода работы с множеством объектов никак не нарушит данные объектов массива.

Мы рассмотрели один из возможных подходов для декомпозиции проекта, в котором происходит обработка множества объектов класса. Этот подход можно применить для выполнения данной лабораторной работы.

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

Декомпозиция файла main.cpp

Как ранее упоминалось, для демонстрации не использовалось выделение класса Rectangle в отдельные rectangle.cpp и rectangle.h. Если это сделать, то результат получится такой, как показан в репозитории по ссылке.

Создание статической библиотеки rectangle

После того, как мы Rectangle выделили в rectangle.cpp и rectangle.h, мы можем их упаковать в статическую библиотеку. Если это сделать, то результат получится такой, как показан в репозитории по ссылке.