ООП в C++: от теории к практике – задачи и решения для новичков
Для кого эта статья:
- Начинающие программисты, желающие освоить объектно-ориентированное программирование на C++
- Студенты и учащиеся, изучающие основы программирования и рассматривающие практические задания
Разработчики, желающие улучшить свои навыки в ООП и применении принципов на практике
Объектно-ориентированное программирование — не просто академическая концепция, а мощный инструмент в руках разработчика. Многие начинающие программисты застревают на границе между пониманием теории ООП и её практическим применением. В этой статье я собрал коллекцию задач по C++, которые позволят вам пройти путь от простых упражнений до создания сложных архитектур. Каждое задание снабжено решением, объяснением и практическими советами, чтобы вы могли не просто писать код, а действительно понимать, как ООП решает реальные проблемы программирования. 🚀
Хотя статья посвящена ООП в C++, многие принципы универсальны для всех объектно-ориентированных языков. Если вы хотите расширить свои навыки и освоить еще один востребованный язык программирования, обратите внимание на Курс Java-разработки от Skypro. Java — язык с чистым ООП-подходом, где вы сможете применить изученные концепции в промышленной разработке. Курс включает практику на реальных проектах и поддержку наставников с опытом в индустрии.
Основы ООП на C++: базовые концепции и задачи
Фундамент объектно-ориентированного программирования строится на трёх китах: инкапсуляция, наследование и полиморфизм. Прежде чем погружаться в сложные конструкции, давайте убедимся, что вы уверенно владеете базовыми навыками создания и использования классов. 📚
Иван Петров, старший преподаватель программирования
Когда я только начал преподавать ООП, я часто сталкивался с одной и той же проблемой: студенты могли рассказать о принципах объектно-ориентированного программирования, но затруднялись применить их на практике. Тогда я разработал серию пошаговых упражнений, начиная с самых простых. Первое задание всегда одно и то же: создать класс, описывающий студента с именем, возрастом и средним баллом. Многие удивляются такой простоте, но именно здесь закладывается понимание инкапсуляции — как правильно организовать данные, какие методы сделать публичными, а какие приватными. Позже, когда мы переходим к наследованию и полиморфизму, студенты уже имеют твердую основу.
Давайте начнем с простого задания: создание класса для геометрической фигуры.
Задание 1: Создать класс Circle (круг)
Требования:
- Приватное поле для хранения радиуса
- Публичные методы для вычисления площади и периметра
- Конструктор с параметром и без параметров
- Метод для изменения радиуса с проверкой корректности значения
Решение:
#include <iostream>
#define _USE_MATH_DEFINES
#include <math.h>
class Circle {
private:
double radius;
public:
// Конструктор без параметров
Circle() : radius(1.0) {}
// Конструктор с параметром
Circle(double r) {
setRadius(r);
}
// Геттер для радиуса
double getRadius() const {
return radius;
}
// Сеттер с проверкой корректности
void setRadius(double r) {
if (r > 0) {
radius = r;
} else {
std::cout << "Ошибка: радиус должен быть положительным\n";
radius = 1.0; // Устанавливаем значение по умолчанию
}
}
// Вычисление площади
double area() const {
return M_PI * radius * radius;
}
// Вычисление периметра (длины окружности)
double perimeter() const {
return 2 * M_PI * radius;
}
};
Это простое задание демонстрирует основные концепции ООП:
- Инкапсуляция: поле radius скрыто от прямого доступа и доступно только через методы
- Абстракция: класс предоставляет интерфейс для работы с кругом, скрывая детали реализации
- Валидация данных: метод setRadius проверяет корректность входных данных
Рассмотрим еще несколько базовых заданий для закрепления основных концепций ООП:
Задание 2: Банковский счет
Создайте класс Account, представляющий банковский счет с методами для внесения, снятия денег и проверки баланса. Предусмотрите защиту от некорректных операций (например, отрицательные суммы или снятие больше, чем есть на счете).
Задание 3: Часы с будильником
Разработайте класс AlarmClock, который имеет текущее время, время будильника, методы для установки времени и будильника, а также метод tick(), который увеличивает текущее время на одну минуту и проверяет, не пора ли срабатывать будильнику.
| Концепция ООП | Применение в задании | Что отрабатывается |
|---|---|---|
| Инкапсуляция | Сокрытие полей класса | Защита данных, контроль доступа |
| Абстракция | Предоставление публичного интерфейса | Разделение интерфейса и реализации |
| Конструкторы | Инициализация объектов | Правильная настройка начального состояния |
| Методы | Реализация поведения объектов | Функциональность класса |

Задания на инкапсуляцию и работу с классами в C++
Инкапсуляция — это не просто сокрытие данных, а механизм контроля целостности объекта и обеспечения его правильного состояния. Давайте перейдем к более сложным заданиям, которые раскрывают мощь этого принципа. 🛡️
Задание 4: Система управления библиотекой
Создайте класс Book, который инкапсулирует информацию о книге: название, автор, ISBN, доступность для выдачи. Затем реализуйте класс Library, который управляет коллекцией книг, позволяя добавлять книги, искать по названию или автору, выдавать и принимать книги.
class Book {
private:
std::string title;
std::string author;
std::string isbn;
bool available;
public:
Book(const std::string& t, const std::string& a, const std::string& i)
: title(t), author(a), isbn(i), available(true) {}
// Геттеры
std::string getTitle() const { return title; }
std::string getAuthor() const { return author; }
std::string getISBN() const { return isbn; }
bool isAvailable() const { return available; }
// Методы для выдачи и возврата
bool checkout() {
if (available) {
available = false;
return true;
}
return false;
}
void returnBook() {
available = true;
}
// Метод для вывода информации
void displayInfo() const {
std::cout << "Название: " << title << "\n";
std::cout << "Автор: " << author << "\n";
std::cout << "ISBN: " << isbn << "\n";
std::cout << "Доступность: " << (available ? "Доступна" : "Выдана") << "\n";
}
};
class Library {
private:
std::vector<Book> books;
public:
void addBook(const Book& book) {
books.push_back(book);
}
Book* findBookByTitle(const std::string& title) {
for (auto& book : books) {
if (book.getTitle() == title) {
return &book;
}
}
return nullptr;
}
std::vector<Book*> findBooksByAuthor(const std::string& author) {
std::vector<Book*> result;
for (auto& book : books) {
if (book.getAuthor() == author) {
result.push_back(&book);
}
}
return result;
}
bool checkoutBook(const std::string& title) {
Book* book = findBookByTitle(title);
if (book && book->isAvailable()) {
return book->checkout();
}
return false;
}
void returnBook(const std::string& title) {
Book* book = findBookByTitle(title);
if (book) {
book->returnBook();
}
}
void displayAllBooks() const {
for (const auto& book : books) {
book.displayInfo();
std::cout << "-----------------------\n";
}
}
};
В этом задании мы видим более сложную структуру, где два класса взаимодействуют между собой. Класс Library использует коллекцию объектов Book и предоставляет интерфейс для работы с ними.
Задание 5: Умный массив с проверкой границ
Создайте класс SafeArray, который инкапсулирует обычный массив, но добавляет проверку выхода за границы при доступе по индексу. Класс должен позволять изменять размер массива, сохраняя существующие элементы.
class SafeArray {
private:
int* data;
int size;
public:
// Конструктор
SafeArray(int initialSize) : size(initialSize) {
data = new int[size](); // Инициализируем нулями
}
// Деструктор
~SafeArray() {
delete[] data;
}
// Запрещаем копирование для простоты
SafeArray(const SafeArray&) = delete;
SafeArray& operator=(const SafeArray&) = delete;
// Доступ к элементу с проверкой границ
int& at(int index) {
if (index < 0 || index >= size) {
throw std::out_of_range("Index out of bounds");
}
return data[index];
}
// Константный доступ
const int& at(int index) const {
if (index < 0 || index >= size) {
throw std::out_of_range("Index out of bounds");
}
return data[index];
}
// Получение размера
int getSize() const {
return size;
}
// Изменение размера
void resize(int newSize) {
if (newSize <= 0) {
throw std::invalid_argument("Size must be positive");
}
int* newData = new int[newSize]();
// Копируем существующие элементы
int elementsToCopy = std::min(size, newSize);
for (int i = 0; i < elementsToCopy; i++) {
newData[i] = data[i];
}
// Освобождаем старую память и обновляем указатели
delete[] data;
data = newData;
size = newSize;
}
};
Обратите внимание на несколько важных аспектов инкапсуляции в этом задании:
- Доступ к элементам массива осуществляется только через метод at(), который проверяет границы
- Память управляется внутри класса (выделение в конструкторе, освобождение в деструкторе)
- Запрещено копирование объектов для предотвращения ошибок с двойным освобождением памяти
- Класс предоставляет безопасный интерфейс для работы с массивом
Мария Соколова, разработчик системного ПО
Один из моих первых серьезных проектов на C++ был связан с обработкой финансовых данных. Я быстро поняла, насколько важна инкапсуляция, когда обнаружила ошибку в коде — один из методов позволял установить отрицательную сумму транзакции. Такая ошибка могла привести к серьезным последствиям при работе программы. После этого я полностью переписала класс Transaction, добавив проверки во все сеттеры и переделав часть публичных членов в приватные. Это помогло избежать неконсистентного состояния объектов, а код стал более надежным. Теперь, когда я обучаю новых разработчиков, я всегда подчеркиваю, что инкапсуляция — это не просто теоретическая концепция, а реальный инструмент для предотвращения ошибок и обеспечения стабильной работы программы.
При работе с инкапсуляцией важно понимать, какие операции могут нарушить целостность объекта и обеспечить соответствующие проверки. Вот сравнение часто используемых подходов:
| Подход | Преимущества | Недостатки |
|---|---|---|
| Прямой доступ к полям (public) | Простота использования, меньше кода | Невозможность валидации, невозможность изменить реализацию |
| Геттеры/сеттеры для всех полей | Базовая валидация, возможность изменить реализацию | Избыточный код, неполная инкапсуляция логики |
| Специализированные методы с бизнес-логикой | Полная инкапсуляция, поддержка инвариантов объекта | Требуется тщательное продумывание API класса |
| Иммутабельные объекты | Гарантированная согласованность, потокобезопасность | Необходимость создавать новые объекты при изменениях |
Наследование и композиция: практикум с решениями
Наследование и композиция — два фундаментальных механизма построения сложных систем из простых компонентов. Правильный выбор между ними часто определяет гибкость и расширяемость всего программного решения. 🧩
Начнем с задания на классическое наследование.
Задание 6: Иерархия геометрических фигур
Создайте базовый класс Shape и производные классы Rectangle, Circle, Triangle. Каждый класс должен иметь методы для вычисления площади и периметра. Реализуйте возможность вычисления суммарной площади набора разнородных фигур.
class Shape {
public:
virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual void display() const {
std::cout << "Shape with area: " << area()
<< ", perimeter: " << perimeter() << "\n";
}
virtual ~Shape() {} // Виртуальный деструктор для корректного удаления
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r > 0 ? r : 1.0) {}
double area() const override {
return M_PI * radius * radius;
}
double perimeter() const override {
return 2 * M_PI * radius;
}
void display() const override {
std::cout << "Circle with radius: " << radius << ", area: "
<< area() << ", perimeter: " << perimeter() << "\n";
}
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h)
: width(w > 0 ? w : 1.0), height(h > 0 ? h : 1.0) {}
double area() const override {
return width * height;
}
double perimeter() const override {
return 2 * (width + height);
}
void display() const override {
std::cout << "Rectangle with width: " << width << ", height: " << height
<< ", area: " << area() << ", perimeter: " << perimeter() << "\n";
}
};
class Triangle : public Shape {
private:
double a, b, c; // Стороны треугольника
bool isValid() const {
return (a + b > c) && (a + c > b) && (b + c > a);
}
public:
Triangle(double side1, double side2, double side3)
: a(side1), b(side2), c(side3) {
if (!isValid()) {
std::cout << "Invalid triangle sides, using default values\n";
a = b = c = 1.0;
}
}
double area() const override {
double s = (a + b + c) / 2; // Полупериметр
return std::sqrt(s * (s – a) * (s – b) * (s – c)); // Формула Герона
}
double perimeter() const override {
return a + b + c;
}
void display() const override {
std::cout << "Triangle with sides: " << a << ", " << b << ", " << c
<< ", area: " << area() << ", perimeter: " << perimeter() << "\n";
}
};
// Функция для вычисления суммарной площади набора фигур
double totalArea(const std::vector<Shape*>& shapes) {
double sum = 0.0;
for (const auto& shape : shapes) {
sum += shape->area();
}
return sum;
}
Теперь рассмотрим пример, демонстрирующий предпочтение композиции перед наследованием.
Задание 7: Игровые персонажи с разными возможностями
Создайте систему для представления игровых персонажей с различными способностями. Используйте композицию вместо глубокой иерархии наследования.
// Компоненты способностей персонажей
class AttackAbility {
private:
int power;
double criticalChance;
public:
AttackAbility(int p, double c) : power(p), criticalChance(c) {}
int calculateDamage() const {
// Упрощенное вычисление урона
if ((rand() % 100) / 100.0 < criticalChance) {
return power * 2; // Критический удар
}
return power;
}
};
class DefenseAbility {
private:
int armor;
double blockChance;
public:
DefenseAbility(int a, double b) : armor(a), blockChance(b) {}
int mitigateDamage(int incomingDamage) const {
// Упрощенное вычисление смягчения урона
if ((rand() % 100) / 100.0 < blockChance) {
return 0; // Полный блок
}
return std::max(0, incomingDamage – armor);
}
};
class MagicAbility {
private:
int spellPower;
int manaPool;
public:
MagicAbility(int sp, int mp) : spellPower(sp), manaPool(mp) {}
bool castSpell(int manaCost) {
if (manaPool >= manaCost) {
manaPool -= manaCost;
return true;
}
return false;
}
int getSpellDamage(int baseSpellDamage) const {
return baseSpellDamage + spellPower;
}
void regenerateMana(int amount) {
manaPool += amount;
}
};
// Базовый класс для всех персонажей
class Character {
private:
std::string name;
int health;
int level;
// Опциональные компоненты (могут быть nullptr)
std::unique_ptr<AttackAbility> attackAbility;
std::unique_ptr<DefenseAbility> defenseAbility;
std::unique_ptr<MagicAbility> magicAbility;
public:
Character(const std::string& n, int h, int l)
: name(n), health(h), level(l) {}
// Методы для установки способностей
void setAttackAbility(int power, double criticalChance) {
attackAbility = std::make_unique<AttackAbility>(power, criticalChance);
}
void setDefenseAbility(int armor, double blockChance) {
defenseAbility = std::make_unique<DefenseAbility>(armor, blockChance);
}
void setMagicAbility(int spellPower, int manaPool) {
magicAbility = std::make_unique<MagicAbility>(spellPower, manaPool);
}
// Методы для использования способностей
bool canAttack() const { return attackAbility != nullptr; }
bool canDefend() const { return defenseAbility != nullptr; }
bool canUseMagic() const { return magicAbility != nullptr; }
int attack() const {
if (canAttack()) {
return attackAbility->calculateDamage();
}
return 0; // Нет способности атаковать
}
int defend(int incomingDamage) {
int actualDamage = canDefend()
? defenseAbility->mitigateDamage(incomingDamage)
: incomingDamage;
health = std::max(0, health – actualDamage);
return actualDamage;
}
bool castSpell(int manaCost, int& spellDamage) {
if (canUseMagic() && magicAbility->castSpell(manaCost)) {
spellDamage = magicAbility->getSpellDamage(manaCost * 2); // Упрощенная формула
return true;
}
spellDamage = 0;
return false;
}
// Геттеры
std::string getName() const { return name; }
int getHealth() const { return health; }
int getLevel() const { return level; }
// Метод для вывода информации
void displayInfo() const {
std::cout << "Character: " << name << ", Level: " << level
<< ", Health: " << health << "\n";
std::cout << "Abilities: "
<< (canAttack() ? "Attack " : "")
<< (canDefend() ? "Defense " : "")
<< (canUseMagic() ? "Magic" : "")
<< "\n";
}
};
Сравнение подходов с наследованием и композицией:
- Наследование: удобно, когда есть четкая иерархия "является" (например, Круг является Фигурой)
- Композиция: более гибкий подход, когда объект "имеет" разные компоненты (персонаж имеет способности)
- При использовании наследования изменение в базовом классе влияет на все производные классы
- Композиция позволяет конфигурировать объекты "на лету", добавляя или удаляя компоненты
Задание 8: Расширяемая система транспортных средств
Разработайте систему для представления различных транспортных средств. Используйте комбинацию наследования и композиции для максимальной гибкости.
Подсказки:
- Создайте базовый класс Vehicle с общими свойствами (вес, скорость, позиция)
- Выделите отдельные компоненты: двигатель (Engine), трансмиссию (Transmission), топливную систему (FuelSystem)
- Реализуйте наследование для основных типов (Car, Motorcycle, Truck)
- Используйте композицию для добавления компонентов к каждому транспортному средству
Полиморфизм и виртуальные функции в действии
Полиморфизм — это способность объектов с одинаковым интерфейсом иметь различную реализацию. В C++ этот механизм реализуется преимущественно через виртуальные функции. Рассмотрим задания, раскрывающие мощь полиморфизма для создания гибких и расширяемых систем. 🔄
Задание 9: Система обработки документов
Создайте иерархию классов для различных типов документов (текстовые, электронные таблицы, изображения) с общим интерфейсом для открытия, сохранения и печати. Затем реализуйте класс DocumentManager, который может работать с любыми типами документов единообразно.
class Document {
public:
virtual void open() = 0;
virtual void save() = 0;
virtual void print() = 0;
virtual std::string getType() const = 0;
virtual ~Document() {}
};
class TextDocument : public Document {
private:
std::string filename;
std::string content;
public:
TextDocument(const std::string& name) : filename(name) {}
void open() override {
std::cout << "Opening text document: " << filename << "\n";
// Здесь был бы код для чтения файла
content = "Sample text content";
}
void save() override {
std::cout << "Saving text document: " << filename << "\n";
// Здесь был бы код для записи в файл
}
void print() override {
std::cout << "Printing text document: " << filename << "\n";
std::cout << "Content: " << content << "\n";
}
std::string getType() const override {
return "Text Document";
}
// Специфический метод для текстовых документов
void spellCheck() {
std::cout << "Spell checking document: " << filename << "\n";
}
};
class SpreadsheetDocument : public Document {
private:
std::string filename;
int rows, cols;
public:
SpreadsheetDocument(const std::string& name, int r = 10, int c = 10)
: filename(name), rows(r), cols(c) {}
void open() override {
std::cout << "Opening spreadsheet: " << filename << "\n";
// Здесь был бы код для чтения файла
}
void save() override {
std::cout << "Saving spreadsheet: " << filename << "\n";
// Здесь был бы код для записи в файл
}
void print() override {
std::cout << "Printing spreadsheet: " << filename << "\n";
std::cout << "Dimensions: " << rows << "x" << cols << "\n";
}
std::string getType() const override {
return "Spreadsheet Document";
}
// Специфический метод для электронных таблиц
void calculateFormulas() {
std::cout << "Calculating formulas in spreadsheet: " << filename << "\n";
}
};
class ImageDocument : public Document {
private:
std::string filename;
int width, height;
public:
ImageDocument(const std::string& name, int w = 800, int h = 600)
: filename(name), width(w), height(h) {}
void open() override {
std::cout << "Opening image: " << filename << "\n";
// Здесь был бы код для чтения файла
}
void save() override {
std::cout << "Saving image: " << filename << "\n";
// Здесь был бы код для записи в файл
}
void print() override {
std::cout << "Printing image: " << filename << "\n";
std::cout << "Resolution: " << width << "x" << height << "\n";
}
std::string getType() const override {
return "Image Document";
}
// Специфический метод для изображений
void resize(int newWidth, int newHeight) {
std::cout << "Resizing image from " << width << "x" << height
<< " to " << newWidth << "x" << newHeight << "\n";
width = newWidth;
height = newHeight;
}
};
class DocumentManager {
private:
std::vector<std::unique_ptr<Document>> documents;
public:
// Добавление документа в менеджер
void addDocument(std::unique_ptr<Document> doc) {
documents.push_back(std::move(doc));
}
// Открытие всех документов
void openAll() {
for (auto& doc : documents) {
doc->open();
}
}
// Сохранение всех документов
void saveAll() {
for (auto& doc : documents) {
doc->save();
}
}
// Печать всех документов
void printAll() {
for (auto& doc : documents) {
doc->print();
}
}
// Получение документа по индексу
Document* getDocument(size_t index) {
if (index < documents.size()) {
return documents[index].get();
}
return nullptr;
}
// Вывод списка всех документов
void listDocuments() {
std::cout << "Document list:\n";
for (size_t i = 0; i < documents.size(); ++i) {
std::cout << i << ": " << documents[i]->getType()
<< "\n";
}
}
};
Демонстрация использования полиморфизма:
int main() {
DocumentManager manager;
// Добавляем разные типы документов
manager.addDocument(std::make_unique<TextDocument>("report.txt"));
manager.addDocument(std::make_unique<SpreadsheetDocument>("budget.xlsx", 100, 20));
manager.addDocument(std::make_unique<ImageDocument>("photo.jpg", 1920, 1080));
// Работаем со всеми документами единообразно
manager.listDocuments();
manager.openAll();
manager.printAll();
manager.saveAll();
// Работаем с конкретным документом, используя его специфические методы
Document* doc = manager.getDocument(0);
if (doc && doc->getType() == "Text Document") {
// Для доступа к специфическим методам нужно приведение типа
TextDocument* textDoc = static_cast<TextDocument*>(doc);
textDoc->spellCheck();
}
return 0;
}
Ключевые аспекты полиморфизма:
- Базовый класс определяет общий интерфейс через чисто виртуальные функции
- Производные классы переопределяют эти функции, предоставляя собственную реализацию
- Клиентский код работает с указателями или ссылками на базовый класс
- При вызове виртуальной функции используется реализация из соответствующего производного класса
Задание 10: Система анимации игровых объектов
Создайте систему для анимации различных игровых объектов. Каждый тип объекта должен иметь свою уникальную анимацию, но управление всеми анимациями должно осуществляться через общий интерфейс.
- Создайте абстрактный базовый класс AnimatedObject с виртуальными методами update() и render()
- Реализуйте различные классы для анимации: PlayerCharacter, Enemy, Projectile, ExplosionEffect
- Создайте класс AnimationManager, который будет обновлять и отрисовывать все анимированные объекты
Сложные проекты: многоуровневая архитектура на C++
Настоящее мастерство в ООП проявляется при создании многоуровневых архитектур, где взаимодействуют десятки классов. Такие проекты требуют продуманного дизайна, соблюдения принципов SOLID и понимания шаблонов проектирования. Рассмотрим финальное, наиболее сложное задание. 🏗️
Задание 11: Разработка упрощенной системы управления отелем
Создайте многоуровневое приложение для управления небольшим отелем, включающее следующие компоненты:
- Слой моделей данных (комнаты, гости, бронирования, услуги)
- Слой бизнес-логики (проверка доступности номеров, расчет стоимости, политики отмены)
- Слой хранения данных (интерфейс репозитория и его реализации)
- Слой пользовательского интерфейса (консольный интерфейс)
Ниже приведены ключевые части решения (из-за объема полное решение не приводится):
// ***** СЛОЙ МОДЕЛЕЙ ДАННЫХ *****
// Комната в отеле
class Room {
private:
int roomNumber;
std::string roomType; // "Single", "Double", "Suite"
double basePrice;
bool hasAC;
bool hasMinibar;
public:
Room(int number, const std::string& type, double price,
bool ac = false, bool minibar = false)
: roomNumber(number), roomType(type), basePrice(price),
hasAC(ac), hasMinibar(minibar) {}
int getRoomNumber() const { return roomNumber; }
std::string getRoomType() const { return roomType; }
double getBasePrice() const { return basePrice; }
bool hasAirConditioner() const { return hasAC; }
bool hasMiniBar() const { return hasMinibar; }
// Вычисление полной стоимости комнаты с учетом доп. услуг
double calculateFullPrice() const {
double price = basePrice;
if (hasAC) price += 10.0;
if (hasMinibar) price += 15.0;
return price;
}
};
// Гость отеля
class Guest {
private:
int guestId;
std::string name;
std::string contactInfo;
std::string passportNumber;
public:
Guest(int id, const std::string& guestName,
const std::string& contact, const std::string& passport)
: guestId(id), name(guestName),
contactInfo(contact), passportNumber(passport) {}
int getGuestId() const { return guestId; }
std::string getName() const { return name; }
std::string getContactInfo() const { return contactInfo; }
std::string getPassportNumber() const { return passportNumber; }
};
// Бронирование
class Booking {
private:
int bookingId;
int roomNumber;
int guestId;
std::time_t checkInDate;
std::time_t checkOutDate;
double totalPrice;
bool isPaid;
public:
Booking(int id, int room, int guest,
std::time_t checkIn, std::time_t checkOut, double price)
: bookingId(id), roomNumber(room), guestId(guest),
checkInDate(checkIn), checkOutDate(checkOut),
totalPrice(price), isPaid(false) {}
int getBookingId() const { return bookingId; }
int getRoomNumber() const { return roomNumber; }
int getGuestId() const { return guestId; }
std::time_t getCheckInDate() const { return checkInDate; }
std::time_t getCheckOutDate() const { return checkOutDate; }
double getTotalPrice() const { return totalPrice; }
bool getIsPaid() const { return isPaid; }
void markAsPaid() { isPaid = true; }
};
// ***** СЛОЙ ХРАНЕНИЯ ДАННЫХ (ИНТЕРФЕЙСЫ) *****
// Интерфейс репозитория для комнат
class RoomRepository {
public:
virtual void addRoom(const Room& room) = 0;
virtual Room* getRoomByNumber(int roomNumber) = 0;
virtual std::vector<Room*> getAvailableRooms(std::time_t from, std::time_t to) = 0;
virtual std::vector<Room*> getAllRooms() = 0;
virtual ~RoomRepository() {}
};
// Интерфейс репозитория для гостей
class GuestRepository {
public:
virtual void addGuest(const Guest& guest) = 0;
virtual Guest* getGuestById(int guestId) = 0;
virtual std::vector<Guest*> getAllGuests() = 0;
virtual ~GuestRepository() {}
};
// Интерфейс репозитория для бронирований
class BookingRepository {
public:
virtual void addBooking(const Booking& booking) = 0;
virtual Booking* getBookingById(int bookingId) = 0;
virtual std::vector<Booking*> getBookingsByGuest(int guestId) = 0;
virtual std::vector<Booking*> getBookingsByRoom(int roomNumber) = 0;
virtual std::vector<Booking*> getBookingsInDateRange(std::time_t from, std::time_t to) = 0;
virtual std::vector<Booking*> getAllBookings() = 0;
virtual ~BookingRepository() {}
};
// ***** РЕАЛИЗАЦИИ РЕПОЗИТОРИЕВ В ПАМЯТИ *****
class InMemoryRoomRepository : public RoomRepository {
private:
std::vector<Room> rooms;
public:
void addRoom(const Room& room) override {
rooms.push_back(room);
}
Room* getRoomByNumber(int roomNumber) override {
for (auto& room : rooms) {
if (room.getRoomNumber() == roomNumber) {
return &room;
}
}
return nullptr;
}
std::vector<Room*> getAvailableRooms(std::time_t from, std::time_t to) override {
// В реальном приложении здесь была бы логика проверки доступности
// с использованием BookingRepository
std::vector<Room*> available;
for (auto& room : rooms) {
available.push_back(&room);
}
return available;
}
std::vector<Room*> getAllRooms() override {
std::vector<Room*> result;
for (auto& room : rooms) {
result.push_back(&room);
}
return result;
}
};
// Аналогичные реализации для GuestRepository и BookingRepository
// ***** СЛОЙ БИЗНЕС-ЛОГИКИ *****
class BookingService {
private:
RoomRepository& roomRepository;
GuestRepository& guestRepository;
BookingRepository& bookingRepository;
int nextBookingId;
public:
BookingService(RoomRepository& rooms, GuestRepository& guests, BookingRepository& bookings)
: roomRepository(rooms), guestRepository(guests),
bookingRepository(bookings), nextBookingId(1) {}
bool isRoomAvailable(int roomNumber, std::time_t checkIn, std::time_t checkOut) {
auto bookings = bookingRepository.getBookingsByRoom(roomNumber);
for (const auto& booking : bookings) {
// Проверка на пересечение дат
if (!(checkOut <= booking->getCheckInDate() ||
checkIn >= booking->getCheckOutDate())) {
return false;
}
}
return true;
}
Booking* createBooking(int guestId, int roomNumber,
std::time_t checkIn, std::time_t checkOut) {
Guest* guest = guestRepository.getGuestById(guestId);
Room* room = roomRepository.getRoomByNumber(roomNumber);
if (!guest || !room) {
return nullptr;
}
if (!isRoomAvailable(roomNumber, checkIn, checkOut)) {
return nullptr;
}
// Расчет количества дней
double days = std::difftime(checkOut, checkIn) / (60 * 60 * 24);
double totalPrice = room->calculateFullPrice() * days;
Booking booking(nextBookingId++, roomNumber, guestId,
checkIn, checkOut, totalPrice);
bookingRepository.addBooking(booking);
return bookingRepository.getBookingById(booking.getBookingId());
}
bool cancelBooking(int bookingId) {
Booking* booking = bookingRepository.getBookingById(bookingId);
if (!booking) {
return false;
}
// В реальном приложении здесь был бы код для отмены бронирования
// Например, проверка политики отмены и возможное начисление штрафа
return true;
}
std::vector<Room*> findAvailableRooms(std::time_t checkIn, std::time_t checkOut) {
std::vector<Room*> availableRooms;
auto allRooms = roomRepository.getAllRooms();
for (auto room : allRooms) {
if (isRoomAvailable(room->getRoomNumber(), checkIn, checkOut)) {
availableRooms.push_back(room);
}
}
return availableRooms;
}
};
// ***** КОНСОЛЬНЫЙ ИНТЕРФЕЙС ПОЛЬЗОВАТЕЛЯ *****
class HotelConsoleUI {
private:
BookingService& bookingService;
RoomRepository& roomRepository;
GuestRepository& guestRepository;
// Вспомогательные методы для взаимодействия с пользователем
public:
HotelConsoleUI(BookingService& service,
RoomRepository& rooms, GuestRepository& guests)
: bookingService(service), roomRepository(rooms), guestRepository(guests) {}
void displayMainMenu() {
// Вывод главного меню
}
void run() {
bool running = true;
while (running) {
displayMainMenu();
int choice = getUserChoice();
switch (choice) {
case 1:
showAvailableRooms();
break;
case 2:
createNewBooking();
break;
case 3:
cancelExistingBooking();
break;
case 4:
registerNewGuest();
break;
case 5:
running = false;
break;
default:
std::cout << "Неверный выбор. Попробуйте снова.\n";
}
}
}
// Методы для обработки различных действий пользователя
void showAvailableRooms() {
// Код для отображения доступных комнат
}
void createNewBooking() {
// Код для создания нового бронирования
}
void cancelExistingBooking() {
// Код для отмены существующего бронирования
}
void registerNewGuest() {
// Код для регистрации нового гостя
}
int getUserChoice() {
// Код для получения выбора пользователя
int choice;
std::cin >> choice;
return choice;
}
};
// ***** ОСНОВНАЯ ФУНКЦИЯ *****
int main() {
// Создание репозиториев
InMemoryRoomRepository roomRepo;
// Другие репозитории...
// Инициализация данных
roomRepo.addRoom(Room(101, "Single", 50.0, true, false));
roomRepo.addRoom(Room(102, "Double", 80.0, true, true));
roomRepo.addRoom(Room(103, "Suite", 120.0, true, true));
// Создание сервиса бронирования
BookingService bookingService(roomRepo, guestRepo, bookingRepo);
// Создание и запуск пользовательского интерфейса
HotelConsoleUI ui(
**Читайте также**
- [15 лучших книг по ООП: от новичка до профессионального разработчика](/python/luchshie-knigi-po-oop-dlya-nachinayushih/)
- [Объектно-ориентированное программирование: 4 принципа и применение](/python/obuektno-orientirovannoe-programmirovanie-chto-eto-i-zachem-nuzhno/)
- [Программирование для начинающих: изучаем основы с нуля до практики](/python/osnovy-programmirovaniya-dlya-nachinayushih/)
- [Полное руководство по разработке ПО: от идеи до внедрения](/profession/osnovy-razrabotki-programmnogo-obespecheniya/)
- [За рамками ООП: функциональное и процедурное программирование](/html/alternativy-oop-funkcionalnoe-i-procedurnoe-programmirovanie/)
- [ООП в Python: 10 практических заданий для роста от новичка к pro](/python/prakticheskie-zadaniya-po-oop-na-python/)
- [Объектно-ориентированное программирование: плюсы и минусы подхода](/javascript/preimushestva-i-nedostatki-oop/)
- [ООП: четыре принципа разработки эффективного и чистого кода](/java/chetyre-stolpa-oop-inkapsulyaciya-nasledovanie-polimorfizm-abstrakciya/)
- [ООП в Java: как абстрактные концепции превращаются в прибыль](/java/primery-oop-v-realnyh-proektah-na-java/)
- [Исходный код программы: как написать свою первую программу](/python/kak-napisat-ishodnyj-kod-programmy/)


