Практическое использование UML-диаграмм: реализация классов для работы с документами на Python. Пример СЭД на минималках с блэк-джеком задачами согласования и девушками пользователями.
Простые конструкторы классов на Python
Чтобы реализовать постановку задачи, описанную в прошлой статье, создадим классы, определенные на UML-диаграмме.

Класс – это конструктор для создания объектов. Помимо определения статической структуры объекта, т.е. набора его полей (атрибутов) с типом данных для каждого из них, в конструкторе класса также описываются его методы – функции для работы с объектом этого класса. Например, согласно UML-диаграмме класса, документ можно опубликовать, а в маршрут можно добавить задачу. Для этого в конструкторах классов document и route предусмотрены соответствующие методы.
Чтобы вы могли повторить это упражнения, не устанавливая среду разработки, далее я приведу код на Python для запуска его в интерактивной среде Google Colab. Для удобства разделим код на ячейки. Сперва импортируем необходимые модули: datetime для работы с датой и временем, Enum для работы с перечислениями, а также List и Optional из пакета typing, который предоставляет инструменты для указания типов данных в аннотациях.
################################# ячейка 1 - импорт модулей ############### from datetime import datetime from enum import Enum from typing import List, Optional
Для работы с перечислениями в Python используется модуль Enum. Для перечислений не нужен метод инициализации, поскольку Enum – базовый класс, на основе которого создаются перечисления, автоматически управляет созданием своих элементов. Когда создается класс-перечисление, Python автоматически генерирует нужные методы для создания и управления элементами перечисления. Создадим классы-перечисления, в которых определены ограниченные списки значений, такие как роли пользователей, состояния жизненного цикла документов, задач и маршрутов, а также допустимые форматы файлов. В терминах домено-ориентированного проектирования (DDD, Domain-Driven Design) эти классы будут объектами-значениями (Value Object), которые не имеют собственной идентичности.
Код классов-перечислений:
################################# ячейка 2 - создание перечислений ###############
class role(Enum):
secretary = 'секретарь'
administrator = 'администратор'
employee = 'сотрудник'
class task_status(Enum):
new = 'новая'
assigned = 'поставлена'
started = 'принята к исполнению'
declined_by_executor = 'отклонена исполнителем'
performing = 'выполняется'
canceled_by_initiator = 'отменена инициатором'
completed_by_executor = 'согласована исполнителем'
rejected_by_executor = 'несогласована исполнителем'
frozen = 'заморожена'
accepted_by_initiator = 'принята инициатором'
finished = 'завершена'
class route_status(Enum):
new = 'новый'
started = 'начат'
in_progress = 'выполняется'
canceled = 'отменен'
completed = 'завершен'
class file_format(Enum):
pdf = 'pdf'
doc = 'doc'
xls = 'xls'
ppt = 'ppt'
class document_status(Enum):
new = 'новый'
published = 'опубликован'
on_review = 'на согласовании'
approved = 'согласован'
rejected = 'несогласован'
Далее создадим сами классы сущностей и агрегатов в терминах DDD. Поскольку для сущностей и агрегатов явно создаются объекты, нужно использовать метод __init__ в объявлении класса. Метод __init__ в Python называется конструктором класса: он используется для инициализации объектов класса и вызывается автоматически при создании нового экземпляра. Первый аргумент метода __init__ всегда должен быть self, который представляет собой экземпляр создаваемого класса. Это позволяет методу обращаться к атрибутам и методам объекта. Дополнительные аргументы могут быть добавлены для передачи данных при создании объекта. В методе __init__ обычно задаются начальные значения атрибутов объекта.
Для наглядности визуализации данных об объектах каждого класса добавим в конструкторы метод info. Он будет выводить информацию о значениях атрибутов объекта в виде словаря формата JSON (ключ-значение). Это особенно пригодится, чтобы посмотреть информацию об объектах-агрегатах: маршруте согласования документа, в который пользователь-инициатор добавил несколько задач. Например, следующий код показывает объявление класса user, который вызывается при создании нового пользователя.
Код класса user:
class user:
def __init__(self, login: str, email: str, phone: str, role: role):
self.login = login
self.email = email
self.phone = phone
self.role = role
def info(self):
return {
"login": self.login,
"email": self.email,
"phone": self.phone,
"role": self.role.value
}
Код метода инициализации принимает четыре аргумента:
- login — строка, представляющая логин пользователя.
- email — строка, представляющая адрес электронной почты пользователя.
- phone — строка, представляющая номер телефона пользователя.
- role — объект типа данных role, представляющий роль пользователя как объект класса role, ранее описанного как перечисление.
Метод info() возвращает информацию о пользователе в виде словаря. Ключи словаря — это строки, представляющие названия атрибутов, а значения — соответствующие атрибуты объекта класса user.
Аналогичный по набору методов код для создания объекта файл документа выглядит так:
class doc_file:
def __init__(self, file_format: file_format, size: int):
self.file_format = file_format
self.size = size
def info(self):
return {
"file_format": self.file_format.value,
"size": self.size
}
Класс, описывающий документ, будет сложнее, поскольку он содержит методы управления документом, а также атрибуты документа:
- number – строка с номером документа;
- created_timestamp – время и дата создания документа, устанавливаемая при инициализации объекта;
- published_timestamp – время и дата публикации документа, которая не задана (None), когда документ ещё не опубликован;
- status – статус, состояние жизненного цикла документа, который имеет значения из перечисления document_status, описанного ранее, например, new, published, on_review, approved, rejected;
- author – автор документа, т.е. объект класса user, создавший документ;
- doc_file — файл, связанный с документом, объект класса doc_file, который может быть не задан (None), если к документу не присоединен файл.
Код класса document:
class document:
def __init__(self, number: str, author: user, published_timestamp: Optional[datetime] = None, doc_file: Optional[doc_file] = None):
self.number = number
self.created_timestamp = datetime.now()
self.published_timestamp = published_timestamp
self.status = document_status.new
self.author = author
self.doc_file = doc_file
def add_file(self, doc_file: doc_file):
self.doc_file = doc_file
def delete_file(self):
self.doc_file = None
def publish_document(self):
self.status = document_status.published
self.published_timestamp = datetime.now()
def review_document(self):
self.status = document_status.on_review
def approve_document(self):
self.status = document_status.approved
def reject_document(self):
self.status = document_status.rejected
def info(self):
return {
"number": self.number,
"created_timestamp": self.created_timestamp.isoformat() if self.created_timestamp else None,
"published_timestamp": self.published_timestamp.isoformat() if self.published_timestamp else None,
"author": self.author.info(),
"status": self.status.value,
"file": self.doc_file.info() if self.doc_file else None
}
Класс document содержит следующие методы:
- __init__() — конструктор, который инициализирует объект документа с номером, автором, временной меткой публикации и файлом. При создании он заполняет временную метку создания документа и устанавливает его начальный статус new;
- add_file() – функция добавления или замены файла, связанного с документом;
- delete_file() — функция удаления файла, связанного с документом, устанавливает значение атрибута doc_file в None;
- publish_document () — функция публикации документа, устанавливает статус документа в значение published и временную метку публикации текущим датой и временем;
- review_document() — функция перевода документа в статус согласования (on_review);
- approve_document() — функция согласования документа, устанавливает ему статус approved;
- reject_document() – функция отказа в согласовании документа, устанавливает ему статус rejected;
- info() – функция получения информации о документе в виде словаря: номер, временные метки создания и публикации, информацию об авторе, статус и информацию о файле (если файл существует). Для форматирования временных меток в атрибутах created_timestamp и published_timestamp вызывается метод isoformat(), если они установлены.
Разобравшись с относительно простыми классами, далее рассмотрим более сложные классы задач и маршрутов, которые включают логику изменения жизненного цикла.
Взаимозависимые состояния и обратные вызовы
Как уже было отмечено выше, с классами task и route немного сложнее. Опишем класс задачи со следующими атрибутами:
- name – строка с названием;
- created_timestamp – время и дата создания задачи, устанавливается в момент создания объекта;
- status — текущий статус задачи, по умолчанию равен значению, заданном в перечислении, task_status.new;
- started_timestamp – время и дата начала выполнения задачи, может быть None;
- finished_timestamp – время и дата завершения задачи, может быть None;
- executor — исполнитель задачи, объект класса пользователь;
- _status_change_callback**: Приватный атрибут для хранения функции обратного вызова, которая будет вызываться при изменении статуса задачи.
Код класса task:
class task:
def __init__(self, name: str, executor: user, started_timestamp: Optional[datetime] = None, finished_timestamp: Optional[datetime] = None):
self.name = name
self.created_timestamp = datetime.now()
self.status = task_status.new
self.started_timestamp = started_timestamp
self.finished_timestamp = finished_timestamp
self.executor = executor
self._status_change_callback = None
def set_status_change_callback(self, callback):
self._status_change_callback = callback
def _notify_status_change(self):
if self._status_change_callback:
self._status_change_callback(self)
self.update_task_status()
def start_task(self):
self.status = task_status.started
self.started_timestamp = datetime.now()
self._notify_status_change()
def receive_task(self):
self.status = task_status.assigned
self._notify_status_change()
def decline_task(self):
self.status = task_status.declined_by_executor
self._notify_status_change()
def cancel_task(self):
self.status = task_status.canceled_by_initiator
self.finished_timestamp = datetime.now()
self._notify_status_change()
def perform_task(self):
self.status = task_status.performing
self._notify_status_change()
def accept_task(self):
self.status = task_status.accepted_by_initiator
self._notify_status_change()
def complete_task(self):
self.status = task_status.completed_by_executor
self._notify_status_change()
def reject_task(self):
self.status = task_status.rejected_by_executor
self._notify_status_change()
def freeze_task(self):
self.status = task_status.frozen
self._notify_status_change()
def finish_task(self):
self.status = task_status.finished
self.finished_timestamp = datetime.now()
self._notify_status_change()
def update_task_status(self):
if self.status in [task_status.accepted_by_initiator]:
self.finish_task()
def info(self):
return {
"name": self.name,
"created_timestamp": self.created_timestamp.isoformat() if self.created_timestamp else None,
"started_timestamp": self.started_timestamp.isoformat() if self.started_timestamp else None,
"finished_timestamp": self.finished_timestamp.isoformat() if self.finished_timestamp else None,
"executor": self.executor.info(),
"status": self.status.value
}
В этом коде определены следующие методы класса:
- __init__() – конструктор, инициализирует объект задачи с заданными параметрами;
- set_status_change_callback() — функция обратного вызова для уведомления об изменении статуса;
- _notify_status_change() — внутренний метод, вызывает функцию обратного вызова при изменении статуса задачи;
- start_task() – функции старта задачи, устанавливает статус задачи как task_status.started и фиксирует временную метку ее старта;
- receive_task() – функция назначения задачи на исполнителя, устанавливает статус задачи как task_status.assigned;
- decline_task() – функция отклонения задачи исполнителем, устанавливает ее статус как task_status.declined_by_executor;
- cancel_task() – функция отмены задачи инициатором, устанавливает ее статус как task_status.canceled_by_initiator и фиксирует временную метку завершения текущим временем;
- perform_task() – функция выполнения задачи, задает ее статус как task_status.performing;
- freeze_task() – функция заморозки задачи, устанавливает ее статус как task_status.frozen;
- complete_task() – функция согласования задачи, устанавливает ее статус как task_status.completed_by_executor и вызывает метод update_task_status;
- reject_task() – функция несогласования задачи, устанавливает ее статус как task_status.rejected_by_executor и вызывает метод update_task_status;
- accept_task() – функция принятия задачи инициатором, устанавливает ее статус как task_status.accepted_by_initiator и вызывает метод update_task_status;
- finish_task() – функция завершения задачи, устанавливает ее статус как task_status.finished и фиксирует временную метку завершения;
- update_task_status() – функция, которая проверяет статус задачи и вызывает метод finish_task, если задача была принята инициатором;
- info() – функция получения информации о задаче в виде словаря: имя, временные метки, информацию об исполнителе и текущий статус. Для форматирования временных меток в соответствующих атрибутах вызывается метод isoformat(), если они установлены.
Поскольку состояние маршрута меняется в зависимости от входящих в него задач, их необходимо связать. В рассмотренном примере это делается с помощью функций обратного вызова (callback): когда статус задачи изменяется, маршрут обновляет свой статус соответственно. Сallback передается как аргумент другой функции и вызывается в определённый момент, например, когда происходит какое-то событие. Например, функция обратного вызова используется для уведомления маршрута о том, что статус задачи изменился.
Когда задача добавляется в маршрут с помощью метода add_task, для этой задачи устанавливается функция обратного вызова с помощью метода set_status_change_callback:
def add_task(self, task: task):
task.set_status_change_callback(self.update_route_status)
self.tasks.append(task)
self.update_route_status()
Сам метод set_status_change_callback() описан в классе task. Он сохраняет переданную функцию обратного вызова в атрибуте _status_change_callback задачи:
def set_status_change_callback(self, callback):
self._status_change_callback = callback
Каждый раз, когда статус задачи изменяется, вызывается внутренний метод задачи _notify_status_change(), который проверяет наличие функции обратного вызова и вызывает её, передавая текущую задачу в качестве аргумента. Также здесь вызывается функция обновления статуса задачи:
def _notify_status_change(self):
if self._status_change_callback:
self._status_change_callback(self)
self.update_task_status()
Методы, изменяющие статус задачи, такие как start_task(), receive_task(), decline_task() и пр., вызывают метод _notify_status_change() после изменения статуса. Например, метод, который переводит задачу в состояние начата (started) и устанавливает ей время старта:
def start_task(self):
self.status = task_status.started
self.started_timestamp = datetime.now()
self._notify_status_change()
При вызове метода _notify_status_change() переданная функция обратного вызова, которая является методом update_route_status() у класса route, вызывается и обновляет статус маршрута в зависимости от текущего состояния всех задач:
def update_route_status(self, _=None):
if any(task.status == task_status.rejected_by_executor for task in self.tasks):
self.document.reject_document()
elif all(task.status == task_status.completed_by_executor for task in self.tasks):
self.document.approve_document()
elif all(task.finished_timestamp is not None for task in self.tasks):
self.complete_route()
elif any(task.status == task_status.started for task in self.tasks):
self.start_route()
self.document.publish_document()
elif any(task.status == task_status.assigned or task.status == task_status.performing for task in self.tasks):
self.status = route_status.in_progress
self.document.review_document()
else:
self.status = route_status.new
self.created_timestamp = datetime.now()
Таким образом, класс маршрута (route), который управляет процессом маршрутизации документа и связанными с ним задачами, будет иметь следующие атрибуты:
- created_timestamp – дата и время создания маршрута;
- status — статус маршрута, который может принимать значения из перечисления route_status;
- started_timestamp – дата и время старта маршрута (может быть None);
- finished_timestamp – дата и время завершения маршрута (может быть None);
- document – объект класса документ, связанный с маршрутом;
- initiator — инициатор маршрута, объекта класса пользователь;
- tasks — список задач, добавленных в маршрут, набор объектов класса task.
Код класса route:
class route:
def __init__(self, document: 'document', initiator: 'user', started_timestamp: Optional[datetime] = None, finished_timestamp: Optional[datetime] = None):
self.created_timestamp = datetime.now()
self.status = route_status.new
self.started_timestamp = started_timestamp
self.finished_timestamp = finished_timestamp
self.document = document
self.initiator = initiator
self.tasks: List[task] = []
def add_task(self, task: task):
task.set_status_change_callback(self.update_route_status)
self.tasks.append(task)
self.update_route_status()
def delete_task(self, task: task):
self.tasks.remove(task)
self.update_route_status()
def start_route(self):
self.status = route_status.started
self.started_timestamp = datetime.now()
self.document.publish_document()
for task in self.tasks :
task.status = task_status.assigned
def cancel_route(self):
self.status = route_status.canceled
self.finished_timestamp = datetime.now()
for task in self.tasks :
task.status = task_status.canceled_by_initiator
def perform_route(self):
self.status = route_status.in_progress
self.update_route_status()
def complete_route(self):
self.status = route_status.completed
self.finished_timestamp = datetime.now()
def get_all_tasks(self) -> List[dict]:
return [task.info() for task in self.tasks]
def update_route_status(self, _=None):
if any(task.status == task_status.rejected_by_executor for task in self.tasks):
self.document.reject_document()
elif all(task.status == task_status.completed_by_executor for task in self.tasks):
self.document.approve_document()
elif all(task.finished_timestamp is not None for task in self.tasks):
self.complete_route()
elif any(task.status == task_status.started for task in self.tasks):
self.start_route()
self.document.publish_document()
elif any(task.status == task_status.assigned or task.status == task_status.performing for task in self.tasks):
self.status = route_status.in_progress
self.document.review_document()
else:
self.status = route_status.new
self.created_timestamp = datetime.now()
def info(self):
return {
"created_timestamp": self.created_timestamp.isoformat() if self.created_timestamp else None,
"started_timestamp": self.started_timestamp.isoformat() if self.started_timestamp else None,
"finished_timestamp": self.finished_timestamp.isoformat() if self.finished_timestamp else None,
"initiator": self.initiator.info(),
"document": self.document.info(),
"status": self.status.value,
"tasks": self.get_all_tasks()
}
В этом коде определены следующие методы:
- __init__() — инициализация маршрута с заданными документом, инициатором и временными метками;
- add_task() – функция добавления задачи к маршруту и обновления его статуса;
- delete_task() – функция удаления задачи из маршрута и обновления его статуса;
- start_route() – функция старта маршрута с установкой соответствующего статуса и временной метки старта;
- cancel_route() – функция отмены маршрута с установкой соответствующего статуса и временной метки завершения;
- perform_route() – функция выполнения маршрута с установкой соответствующего статуса;
- complete_route() – функция завершения маршрута с установкой соответствующего статуса и временной метки завершения;
- get_all_tasks() – функция получения информации обо всех задачах маршрута в формате словарей, включая название, временные метки и исполнителя;
- update_route_status() – функция обновления статуса маршрута в зависимости от статусов входящих в него задач. Если все задачи завершены, маршрут помечается как завершённый. Если любая задача отклонена исполнителем, документ отклоняется, а маршрут переводится в состояние выполняется. Если все задачи согласованы исполнителями, документ становится согласован, а маршрут находится в состоянии выполняется до тех пор, пока инициатор не примет каждую задачу. Когда любая задача начата, маршрут тоже стартует, а неопубликованный документ публикуется. Если любая задача назначена или выполняется, маршрут переводится в состояние выполняется, а документ отправляется на согласование. Иначе маршрут остаётся новым и время создания обновляется.
- info() – функция получения информации о маршруте в виде словаря, включая информацию о документе, инициаторе и задачах. Для форматирования временных меток в соответствующих атрибутах вызывается метод isoformat(), если они установлены.
После определения всех классов, наконец, можно приступить к созданию объектов и работе с ними. Это рассмотрим далее.
Манипулирование объектами
Сперва создадим объекты классов:
################################# ячейка 3 - создание объектов ############### # Создание пользователей user_obj_1 = user(login="user1", email="user1@example.com", phone="1234567890", role=role.secretary) user_obj_2 = user(login="user2", email="user2@example.com", phone="0987654321", role=role.administrator) user_obj_3 = user(login="user3", email="user3@example.com", phone="1122334455", role=role.employee) # Создание файла doc_file_obj = doc_file(file_format=file_format.pdf, size=1024) # Создание документа doc_1 = document(number="DOC123", author=user_obj_1, doc_file=doc_file_obj) # Создание задачи task_obj_1 = task(name='задача 1', executor=user_obj_3) task_obj_2 = task(name='задача 2', executor=user_obj_2) # Создание маршрута route_obj = route(document=doc_1, initiator=user_obj_2) # Добавление задачи в маршрут route_obj.add_task(task_obj_1) route_obj.add_task(task_obj_2)
Выведем информацию о созданном маршруте:
#вывод информации о маршруте route_obj.info()

Выведем информацию о задачах маршрута, вызвав метод get_all_tasks() у объекта route_obj:
#вывод информации о всех задачах конкретного маршрута route_obj.get_all_tasks()
Вывод информации о сложных объектах
Теперь реализуем логику управления документами, стартовав маршрут. Входящие в него задачи перейдут в состояние поставлена.
#стартовать маршрут route_obj.start_route() #показать информацию о маршруте route_obj.info()

Предположим, один из исполнителей согласовал документ, а другой – нет.
############## новая ячейка - управление задачами #######################
task_obj_1.start_task() #стартовать задачу 1
print("---------------- задача 1 стартована ---------------- ")
print("Статус задачи: ", task_obj_1.status.value)
print("Время старта задачи: ", task_obj_1.started_timestamp)
print("\n---------------- задача 1 выполнена - согласовано ---------------- ")
task_obj_1.complete_task() #согласовать задачу 1
print("Статус задачи: ", task_obj_1.status.value)
print("\n---------------- выполненная задача 1 принята инициатором---------------- ")
task_obj_1.accept_task() #принять задачу 1
print("Статус задачи: ", task_obj_1.status.value)
print("Время завершения задачи: ", task_obj_1.finished_timestamp)
task_obj_2.start_task() #стартовать задачу 2
print("\n---------------- задача 2 стартована---------------- ")
print("Статус задачи: ", task_obj_2.status.value)
print("Время старта задачи: ", task_obj_2.started_timestamp)
task_obj_2.reject_task() #отказать в согласовании документа по задачу 2
print("\n---------------- задача 2 выполнена - не согласовано---------------- ")
print("Статус задачи: ", task_obj_2.status.value)
print("\n---------------- выполненная задача 2 принята инициатором---------------- ")
task_obj_2.accept_task() #принять задачу 2
print("Статус задачи: ", task_obj_2.status.value)
print("Время завершения задачи: ", task_obj_2.finished_timestamp)
#узнаем статус маршрута
print("\nСтатус маршрута: ", route_obj.status.value)
print("Время завершения маршрута: ", route_obj.finished_timestamp)
#узнаем статус документа
print("Статус документа: ", route_obj.document.status.value)

DDD, ООП и UML для аналитика
Код курса
BUML
Ближайшая дата курса
8 декабря, 2025
Продолжительность
22 ак.часов
Стоимость обучения
48 000 руб.
Хотя итоговый код немного отличается от первичной постановки задачи и проекта в виде набора UML-диаграмм, этап проектирования помог реализовать не только структуру доменных объектов, но и логику их поведения. Поэтому умение разрабатывать UML-диаграммы очень полезный навык для аналитика. Освоить его вы сможете на моих курсах в Школе прикладного бизнес-анализа на базе нашего лицензированного учебного центра обучения и повышения квалификации системных и бизнес-аналитиков в Москве:



