ООП в C++: от теории к практике – задачи и решения для новичков

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Начинающие программисты, желающие освоить объектно-ориентированное программирование на C++
  • Студенты и учащиеся, изучающие основы программирования и рассматривающие практические задания
  • Разработчики, желающие улучшить свои навыки в ООП и применении принципов на практике

    Объектно-ориентированное программирование — не просто академическая концепция, а мощный инструмент в руках разработчика. Многие начинающие программисты застревают на границе между пониманием теории ООП и её практическим применением. В этой статье я собрал коллекцию задач по C++, которые позволят вам пройти путь от простых упражнений до создания сложных архитектур. Каждое задание снабжено решением, объяснением и практическими советами, чтобы вы могли не просто писать код, а действительно понимать, как ООП решает реальные проблемы программирования. 🚀

Хотя статья посвящена ООП в C++, многие принципы универсальны для всех объектно-ориентированных языков. Если вы хотите расширить свои навыки и освоить еще один востребованный язык программирования, обратите внимание на Курс Java-разработки от Skypro. Java — язык с чистым ООП-подходом, где вы сможете применить изученные концепции в промышленной разработке. Курс включает практику на реальных проектах и поддержку наставников с опытом в индустрии.

Основы ООП на C++: базовые концепции и задачи

Фундамент объектно-ориентированного программирования строится на трёх китах: инкапсуляция, наследование и полиморфизм. Прежде чем погружаться в сложные конструкции, давайте убедимся, что вы уверенно владеете базовыми навыками создания и использования классов. 📚

Иван Петров, старший преподаватель программирования

Когда я только начал преподавать ООП, я часто сталкивался с одной и той же проблемой: студенты могли рассказать о принципах объектно-ориентированного программирования, но затруднялись применить их на практике. Тогда я разработал серию пошаговых упражнений, начиная с самых простых. Первое задание всегда одно и то же: создать класс, описывающий студента с именем, возрастом и средним баллом. Многие удивляются такой простоте, но именно здесь закладывается понимание инкапсуляции — как правильно организовать данные, какие методы сделать публичными, а какие приватными. Позже, когда мы переходим к наследованию и полиморфизму, студенты уже имеют твердую основу.

Давайте начнем с простого задания: создание класса для геометрической фигуры.

Задание 1: Создать класс Circle (круг)

Требования:

  • Приватное поле для хранения радиуса
  • Публичные методы для вычисления площади и периметра
  • Конструктор с параметром и без параметров
  • Метод для изменения радиуса с проверкой корректности значения

Решение:

cpp
Скопировать код
#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, который управляет коллекцией книг, позволяя добавлять книги, искать по названию или автору, выдавать и принимать книги.

cpp
Скопировать код
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, который инкапсулирует обычный массив, но добавляет проверку выхода за границы при доступе по индексу. Класс должен позволять изменять размер массива, сохраняя существующие элементы.

cpp
Скопировать код
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. Каждый класс должен иметь методы для вычисления площади и периметра. Реализуйте возможность вычисления суммарной площади набора разнородных фигур.

cpp
Скопировать код
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: Игровые персонажи с разными возможностями

Создайте систему для представления игровых персонажей с различными способностями. Используйте композицию вместо глубокой иерархии наследования.

cpp
Скопировать код
// Компоненты способностей персонажей
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, который может работать с любыми типами документов единообразно.

cpp
Скопировать код
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";
}
}
};

Демонстрация использования полиморфизма:

cpp
Скопировать код
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: Разработка упрощенной системы управления отелем

Создайте многоуровневое приложение для управления небольшим отелем, включающее следующие компоненты:

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

Ниже приведены ключевые части решения (из-за объема полное решение не приводится):

cpp
Скопировать код
// ***** СЛОЙ МОДЕЛЕЙ ДАННЫХ *****

// Комната в отеле
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/)

Проверь как ты усвоил материалы статьи
Пройди тест и узнай насколько ты лучше других читателей
Какие основные концепции ООП описаны в тексте?
1 / 5

Загрузка...