Skip to content

Latest commit

 

History

History
315 lines (221 loc) · 19.6 KB

File metadata and controls

315 lines (221 loc) · 19.6 KB

Практическая работа Модуль 1: «Компьютерный клуб»

Часть 1. Техническое задание

Перед тем как писать код, хороший разработчик внимательно читает ТЗ. Прочитай его целиком, прежде чем переходить к следующей части.


1.1 Описание задачи

Нам поступил заказ на разработку консольного прототипа системы учёта для компьютерного клуба.

Администратор клуба хочет систему, в которой можно:

  • регистрировать новых клиентов клуба (имя, никнейм, телефон);
  • начинать игровую сессию клиента за компьютером с выбором тарифа;
  • завершать игровую сессию — при завершении система должна автоматически посчитать и показать сумму к оплате;
  • смотреть список всех клиентов, у которых сейчас идёт активная сессия;
  • смотреть общее количество клиентов, когда-либо зарегистрированных в клубе — с момента самого первого запуска системы.

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

Программа работает в рамках одного запуска — сохранение данных между запусками в этой версии не требуется (это будет добавлено в следующей практике, когда будут пройдены темы, связанные с хранением данных).


1.2 Бизнес-правила

  1. Никнейм клиента уникален — нельзя зарегистрировать двух клиентов с одинаковым никнеймом.
  2. Номер телефона клиента тоже уникален — нельзя зарегистрировать двух клиентов с одинаковым номером.
  3. Номер телефона должен соответствовать формату +7XXXXXXXXXX (плюс, семёрка, затем ровно 10 цифр).
  4. Тариф игровой сессии — строго одно из двух фиксированных значений: standard или vip. Любое другое значение — ошибка.
  5. У одного клиента не может быть двух одновременно активных сессий.
  6. При завершении сессии стоимость рассчитывается автоматически на основе времени, прошедшего с начала сессии, и тарифа клиента. Администратор не вводит сумму вручную.
  7. Общее количество клиентов, зарегистрированных за всё время работы системы, должно накапливаться и не сбрасываться при создании новых объектов клуба внутри программы (даже если где-то в коде клуб «создаётся» повторно).

1.3 Требования к технологиям

Требование Детали
Язык Python 3.10+
Хранилище данных не требуется (всё хранится в оперативной памяти на время запуска)
Интерфейс консоль (input/print)
Сторонние библиотеки не использовать (только стандартная библиотека)

Часть 2. Задание для студента

Здесь описана точная архитектура, которую нужно реализовать. Отступать от неё без согласования с преподавателем нельзя — архитектура специально спроектирована так, чтобы закрепить пройденные темы Модуля 1.


2.1 Структура проекта

computer_club/
│
├── models/
│   ├── __init__.py
│   ├── client.py                # класс Client
│   └── session.py               # класс Session
│
├── club/
│   ├── __init__.py
│   └── computer_club.py         # класс ComputerClub (Singleton)
│
└── main.py                      # точка входа, консольное меню

2.2 Описание классов

models/client.py — класс Client

Представляет клиента компьютерного клуба. На этом этапе курса приватность атрибутов ещё не изучалась, поэтому все атрибуты остаются публичными — тема инкапсуляции будет разобрана в следующем модуле.

Атрибуты:

Атрибут Тип Описание
name str имя клиента
nickname str игровой никнейм клиента
phone str номер телефона в формате +7XXXXXXXXXX

Методы:

  • __init__(name, nickname, phone) — сохраняет данные клиента. Перед сохранением проверяет:

    • name — непустая строка;
    • nickname — непустая строка;
    • phone — проходит проверку is_valid_phone.

    Если любая проверка не пройдена — выбрасывается ValueError с понятным сообщением.

  • is_valid_phone(phone) -> boolстатический метод. Возвращает True, если строка начинается с +7, а после неё идёт ровно 10 цифр, иначе False. Метод не должен использовать ни self, ни cls — это самостоятельная проверка, зависящая только от переданного значения.

  • from_string(raw) -> Clientметод класса (альтернативный конструктор). Принимает строку вида "Иван;ivan_the_best;+79991234567" (значения разделены ;), разбирает её на составляющие и возвращает новый объект Client. Если строка не разбивается ровно на три части — выбрасывается ValueError. Метод должен создавать объект через cls(...), а не через жёстко прописанное имя класса.


models/session.py — класс Session

Представляет одну игровую сессию клиента за компьютером.

Атрибуты класса:

Атрибут Тип Описание
TARIFFS dict словарь допустимых тарифов вида {"standard": 100, "vip": 200} — цена указана в рублях за час

Атрибуты объекта (задаются в __init__):

Атрибут Тип Описание
client Client клиент, для которого открыта сессия
tariff str выбранный тариф ("standard" или "vip")
start_time datetime момент начала сессии, устанавливается автоматически через datetime.now()

Методы:

  • __init__(client, tariff) — проверяет, что tariff есть среди ключей TARIFFS (если нет — ValueError с перечислением допустимых тарифов), сохраняет client, tariff, и start_time = datetime.now().

  • __del__(self) — вызывается автоматически при удалении объекта сессии (то есть в момент, когда сессия завершается и убирается из активных). Должен:

    1. вычислить, сколько времени прошло с start_time до текущего момента (datetime.now() в момент вызова __del__);
    2. рассчитать стоимость сессии на основе прошедшего времени и тарифа из TARIFFS;
    3. вывести в консоль сообщение вида: [Чек] Сессия клиента ivan_the_best завершена. Время: 42.3 сек. К оплате: 1.17 руб.

    Обрати внимание: __del__ в этой задаче — не основной механизм хранения данных (мы уже разбирали на уроке, что полагаться на __del__ для критичных операций не стоит), а способ автоматически напечатать «чек» ровно в тот момент, когда сессия перестаёт существовать как активная. Реальный расчёт стоимости для полноценной системы стоило бы делать явным методом, а не полагаться только на __del__ — но в рамках этой практики нам важно закрепить именно понимание того, когда и почему __del__ вызывается.


club/computer_club.py — класс ComputerClub

Главный класс системы. Должен гарантировать, что в программе существует только один экземпляр клуба — используй магический метод __new__ (паттерн Singleton, разобранный на Уроке 5).

Атрибуты класса:

Атрибут Тип Описание
__instance ComputerClub | None хранит единственный экземпляр класса (используется только внутри __new__)
total_clients_ever_count int общее количество клиентов, когда-либо зарегистрированных за всё время работы программы

Атрибуты объекта (задаются в __init__):

Атрибут Тип Описание
clients list[Client] список всех зарегистрированных клиентов
active_sessions dict[str, Session] активные сессии: ключ — никнейм клиента, значение — объект Session

Методы:

  • __new__(cls, *args, **kwargs) — реализует Singleton: если экземпляр ещё не создан — создаёт его через super().__new__(cls) и сохраняет в cls.__instance; в любом случае возвращает cls.__instance.

  • __init__(self) — так как __init__ вызывается при каждом обращении к ComputerClub(), даже если объект уже существовал (это разбиралось на Уроке 5), метод должен содержать защиту от повторной инициализации: если clients и active_sessions уже были созданы ранее — просто выйти из метода, ничего не пересоздавая.

  • total_clients_ever(cls) -> intметод класса. Возвращает cls.total_clients_ever_count.

  • register_client(self, name, nickname, phone) -> Client — регистрирует нового клиента:

    • проверяет через find_client, что клиент с таким никнеймом ещё не зарегистрирован (иначе ValueError);
    • проверяет, что ни у одного из уже зарегистрированных клиентов нет такого же номера телефона (иначе ValueError);
    • создаёт объект Client, добавляет его в clients;
    • увеличивает total_clients_ever_count на 1;
    • возвращает созданный объект.
  • register_client_from_string(self, raw) -> Client — создаёт клиента через Client.from_string(raw), а затем регистрирует его точно так же, как register_client (то есть с теми же проверками уникальности — переиспользуй register_client внутри этого метода, не дублируя код).

  • find_client(self, nickname) -> Client | None — ищет клиента по никнейму среди clients, возвращает найденный объект или None.

  • start_session(self, nickname, tariff) -> Session — начинает сессию:

    • находит клиента через find_client (если не найден — ValueError);
    • проверяет, что у клиента ещё нет активной сессии в active_sessions (иначе — ValueError);
    • создаёт объект Session, сохраняет в active_sessions по ключу-никнейму, возвращает его.
  • close_session(self, nickname) — завершает сессию:

    • проверяет, что сессия для этого никнейма существует в active_sessions (иначе — ValueError);
    • удаляет запись из active_sessions (это должно быть единственным местом в программе, где хранилась ссылка на объект Session, поэтому удаление записи из словаря должно автоматически привести к вызову Session.__del__).
  • show_active_sessions(self) — выводит список всех активных сессий (никнейм, тариф, время начала). Если активных сессий нет — выводит сообщение об этом.


2.3 Схема связей между классами

main.py
  └── использует ComputerClub (Singleton, __new__)
        ├── хранит список Client
        └── хранит словарь активных Session
              └── Session ссылается на Client, для которого она открыта

2.4 Файл main.py — консольное меню

Точка входа в приложение. Здесь находится только консольный интерфейс — никакой бизнес-логики (вся логика проверок должна быть внутри классов, а не в main.py).

=== Компьютерный клуб ===
1. Зарегистрировать клиента
2. Зарегистрировать клиента из строки (имя;никнейм;телефон)
3. Начать игровую сессию
4. Завершить игровую сессию
5. Показать активные сессии
6. Показать общее количество клиентов за всё время
0. Выйти
# main.py — структура (не финальный код, только скелет)

from club.computer_club import ComputerClub

def main():
    club = ComputerClub()

    while True:
        # показать меню
        # обработать выбор пользователя
        # вызвать методы club.*, обернув в try/except ValueError
        pass

if __name__ == "__main__":
    main()

Часть 3. Пример использования (примерная реализация main.py)

Конкретная реализация всегда зависит от разработчика.

from club.computer_club import ComputerClub


def show_menu():
    print("\n=== Компьютерный клуб ===")
    print("1. Зарегистрировать клиента")
    print("2. Зарегистрировать клиента из строки (имя;никнейм;телефон)")
    print("3. Начать игровую сессию")
    print("4. Завершить игровую сессию")
    print("5. Показать активные сессии")
    print("6. Показать общее количество клиентов за всё время")
    print("0. Выйти")
    return input("Ваш выбор: ").strip()


def main():
    club = ComputerClub()

    while True:
        choice = show_menu()

        if choice == "1":
            name = input("Имя клиента: ").strip()
            nickname = input("Никнейм: ").strip()
            phone = input("Телефон (+7XXXXXXXXXX): ").strip()
            try:
                club.register_client(name, nickname, phone)
                print(f"Клиент {nickname} успешно зарегистрирован")
            except ValueError as e:
                print(f"Ошибка: {e}")

        elif choice == "2":
            raw = input("Введите строку в формате 'имя;никнейм;телефон': ").strip()
            try:
                club.register_client_from_string(raw)
                print("Клиент успешно зарегистрирован")
            except ValueError as e:
                print(f"Ошибка: {e}")

        elif choice == "3":
            nickname = input("Никнейм клиента: ").strip()
            tariff = input("Тариф (standard/vip): ").strip()
            try:
                club.start_session(nickname, tariff)
                print(f"Сессия для {nickname} начата")
            except ValueError as e:
                print(f"Ошибка: {e}")

        elif choice == "4":
            nickname = input("Никнейм клиента: ").strip()
            try:
                club.close_session(nickname)
            except ValueError as e:
                print(f"Ошибка: {e}")

        elif choice == "5":
            club.show_active_sessions()

        elif choice == "6":
            print(f"Всего клиентов за всё время: {ComputerClub.total_clients_ever()}")

        elif choice == "0":
            print("Работа программы завершена")
            break

        else:
            print("Неверный выбор.")


if __name__ == "__main__":
    main()

Ориентировочное время выполнения: 2–3 академических часа.