Как классы и связи между ними с UML-диаграммы классов реализуются в программном коде, почему для разработчика нет особой разницы между агрегацией и композицией, чем публичные методы отличаются от приватных, и когда в конструкторе инициализации перечисляются не все поля. Пишем на Python классы и объекты интернет-магазина.
От UML-моделей к программному коду: пример реализации
Продолжая говорить про проектирование информационных систем с помощью UML, сегодня рассмотрим, как модели, представленные в виде диаграммы классов, реализуются в программном коде. Как обычно, для демонстрации выберем практический пример интернет-магазина. Сперва сделаем сопоставление сущностей предметной области с классами в таблице.
Сущность домена | Свойства | Класс | Поле | Тип данных |
Товар | название (строка) | Product | name | str |
категория (строка) | category | str | ||
поставщик (ссылка на поставщика) | provider | Provider | ||
составные части (ссылки на другие товары) | elements | массив [Product] | ||
стоимость (вещественное число) | price | float | ||
Заказ | номер (целое число) | number | int | |
отметка времени (дата) | timestamp | date | ||
клиент (ссылка на клиента) | customer | Customer | ||
список заказанных товаров (ссылка на товар) и их количество (целое число) | products | массив [Product, int;] | ||
сумма (вещественное число) | sum | float | ||
Поставщик | название компании (строка) | Provider | company | str |
ИНН (строка) | inn | str | ||
адрес электронной почты (строка) | str | |||
телефон (строка) | phone | str | ||
физический адрес (строка) | address | str | ||
Покупатель | имя (строка) | Customer | name | str |
адрес электронной почты (строка) | str | |||
телефон (строка) | phone | str |
Далее составим UML-диаграмму классов.
Скрипт PlantUML для создания этой диаграммы приведен в прошлой статье.
Напишем класс, определяющий сущность домена Товар (Product):
class Product: def __init__(self, name: str, category: str, provider: Provider, price: float) self.name = name self.category = category self.provider = provider self.elements = [] self.price = price def add_element(self, product: Product): self.elements.append({ "element": product })
В Python метод __init__() инициализирует значения атрибутов при создании нового экземпляра класса, т.е. конкретного объекта, созданного на основе этого шаблона. Таким образом, объект является конкретной реализацией класса, которая имеет свое состояние и поведение. Каждый атрибут получает свое значение из аргументов, переданных в метод __init__().
Конструктор инициализации класса — это специальный метод __init__(), который вызывается при создании объекта этого класса и инициализирует его атрибуты: все или часть из них. Например, при создании нового Товара совсем не обязательно добавлять к нему составные части, т.е. ссылки на другие товары, если их нет. Поэтому в параметрах метода __init__() класса Product не перечислен атрибут elements.
Значения атрибутов сохраняются в экземпляре класса с помощью синтаксиса self.атрибут = значение. Метод add_element() добавляет другой товар со ссылкой на другой объект класса Товар, если добавляемый товар представляет собой комбинацию нескольких частей, например, как брючный костюм состоит из брюк и жакета, причем каждый из них является отдельным, самостоятельным товаром.
DDD, ООП и UML для аналитика
Код курса
BUML
Ближайшая дата курса
31 марта, 2025
Продолжительность
22 ак.часов
Стоимость обучения
48 000 руб.
Python-код для определения класса Заказ (Order) будет следующий:
class Order: def __init__(self, number: int, timestamp: date, customer: Customer): self.number = number self.timestamp = timestamp self.products = [] self.customer = customer self.sum = 0 def add_product(self, product: Product, quantity: int): self.products.append({ "product": product, "quantity": quantity }) self.sum += product.price * quantity
Метод add_product() добавляет товар к заказу и его количество в список товаров, при этом каждый товар представлен в виде словаря, содержащего название товара и его количество в заказе. Также в методе add_product() вычисляется общая стоимость добавляемого товара как произведение его атрибута цены на количество, что потом добавляется к атрибуту суммы заказа self.sum. Так сумма заказа рассчитывается автоматически по мере добавления товаров, а не задается вручную в инициализаторе класса.
Также создадим класс, определяющий поставщика (Provider) с набором полей, в которых будут храниться название компании, ИНН, email, телефон и адрес.
class Provider: def __init__(self, company: str, inn: str, email: str, phone: str, address: str): self. company = company self.inn = inn self.email = email self.phone = phone self.address = address
У покупателя тоже есть email, телефон и адрес, а также имя вместо названия. Код, определяющий сущность Покупатель (Customer), будет следующим:
class Customer: def __init__(self, name: str, email: str, phone: str, address: str): self.name = name self.email = email self.phone = phone self.address = address
Как видно из рассмотренных определений классов, все ассоциативные связи в коде реализуются через указание внешнего (по отношению к определяемому) класса как типа данных для конкретного атрибута. Причем, разницы между простой или направленной ассоциацией, агрегацией или композицией в коде нет, хотя на UML-диаграмме классов она присутствует.
Опытный разработчик отметит, что определять атрибуты классов публичными (без использования префикса __), считается плохим тоном, поскольку так они могут быть доступны для чтения и записи за пределами их классов. Обычно программист не позволяет себе такие вольности, делая атрибуты приватными, т.е. ограничивая доступ к ним за пределами класса. Это значит, что их можно изменить или получить значения только через методы доступа, которые обычно называются set() и get() и реализуются разработчиком вручную. Приватные атрибуты позволяют управлять доступом к данным в классе, предотвращая случайные изменения или ошибки, которые могут привести к непредсказуемому поведению программы. Впрочем, такие тонкости следует знать, прежде всего, разработчику, а не аналитику.
Тем не менее, аналитику полезно уметь читать код, даже если он его не пишет. Поэтому далее рассмотрим, как будет выглядеть Python-код для создания объектов этих классов.
DDD, ООП и UML для аналитика
Код курса
BUML
Ближайшая дата курса
31 марта, 2025
Продолжительность
22 ак.часов
Стоимость обучения
48 000 руб.
Создание объектов: код на Python
Создадим несколько товаров категории женская одежда (ladieswear) от производителя СуперШмот (SuperShmot) под названиями жакет (jacket), брюки (slacks) и брючный костюм (pantsuit):
jacket = Product(name='jacket', category='ladieswear', provider=SuperShmot, price=3.00) slacks = Product(name='slacks', category='ladieswear', provider=SuperShmot, price=5.00) pantsuit = Product(name='pantsuit', category='ladieswear', provider=SuperShmot, price=7.00)
Покажем, что брючный костюм состоит из жакета и брюк, вызвав метод add_element () у объекта pantsuit класса Product:
pantsuit.add_element(jacket) pantsuit.add_element(slacks)
Также создадим несколько объектов класса Заказ:
Order_1 = Order(number=12345, timestamp = date(2023, 20, 7), customer=customer_1) Order_2 = Order(number=6790, timestamp = date(2023, 30, 7), customer=customer_2)
Добавление товаров в заказы реализуется через вызовы метода add_product() у объектов класса Заказ, например, один покупатель добавил себе в заказ 1 пиджак и 1 брюки, а второй покупатель выбрал 3 костюма:
Order_1.add_product(jacket, 1) Order_1.add_product(slacks, 1) Order_2.add_product(pantsuit, 3)
Разумеется, чтобы этот код со ссылками на поставщиков и покупателей, работал, необходимо создать объекты классов Provider и Customer. Создадим поставщика с названием компании СуперШмот, которая находится в Москве с ИНН 1234567890, телефоном +79999999999 и адресом электронной почты sales@super.shmot:
SuperShmot = Provider( company='СуперШмот', inn='1234567890', email='sales@super.shmot', phone='+79999999999', address='г. Москва, ул. Проспект, д.1' )
Наконец, создадим пару клиентов customer_1 и customer_2, чтобы ссылки на них в ранее представленном коде работали корректно:
customer_1 = Customer( name='Маша', email='masha@example.com', phone='+7123456789', address='г. Москва, ул. Красная, д.1' ) customer_2 = Customer( name='Катя', email='katya@example.com', phone='+7987654321', address='г. Москва, ул. Ленина, д.10' )
Визуализируем созданные объекты на UML-диаграмме объектов, указав кратности связей там, где это имеет смысл. В частности, покажем, что в заказ Order_2, сделанный покупателем по имени Katya (customer_2) входит 3 костюма. И в состав товара костюм (pantsuit) входят одни брюки и 1 жакет, которые также сами являются товарами.
Эта UML-диаграмма объектов получена с помощью следующего скрипта PlantUML:
@startuml object customer_1 { name = "Маша" email = "masha@example.com" phone = "+7123456789" address = "г. Москва, ул. Красная, д.1" } object customer_2 { name = "Катя" email = "katya@example.com" phone = "+7987654321" address = "г. Москва, ул. Ленина, д.10" } object SuperShmot { company = "СуперШмот" inn = "1234567890" email = "sales@super.shmot" phone = "+79999999999" address = "г. Москва, ул. Проспект, д.1" } object jacket { name = "jacket" category = "ladieswear" provider = SuperShmot price = 3.00 } object slacks { name = "slacks" category = "ladieswear" provider = SuperShmot price = 5.00 } object pantsuit { name = "pantsuit" category = "ladieswear" provider = SuperShmot elements = [jacket, slacks] price = 7.00 } object Order_1 { number = 12345 timestamp = date(2023, 20, 7) customer = customer_1 products = [(jacket, 1), (slacks,1)] } object Order_2 { number = 6790 timestamp = date(2023, 30, 7) customer = customer_2 products = [(pantsuit,3)] } jacket --> SuperShmot slacks --> SuperShmot pantsuit --> SuperShmot pantsuit "1" *--> "1" jacket pantsuit "1" *--> "1" slacks Order_1 --> customer_1 Order_2 --> customer_2 Order_2 "1" o-> "3" pantsuit Order_1 "1" o--> "1" slacks Order_1 "1" o--> "1" jacket @enduml
В заключение подчеркну еще раз, что все ассоциативные связи в коде реализуются через указание внешнего (по отношению к определяемому) класса как типа данных для конкретного атрибута, без учета того, как это было обозначено (простая/направленная ассоциация, агрегация или композиция) на UML-диаграмме классов.
DDD, ООП и UML для аналитика
Код курса
BUML
Ближайшая дата курса
31 марта, 2025
Продолжительность
22 ак.часов
Стоимость обучения
48 000 руб.
Проверить свои знания UML вы можете прямо на нашем сайте, выполнив бесплатный интерактивный тест.
Научитесь самостоятельно разрабатывать эти и другие UML-диаграммы на специализированном курсе «UML для бизнес-аналитиков». Только практика на реальных примерах. После 8-часового интенсива вы сможете дополнять моделировать программную систему, чтобы наглядно объяснить разработчикам, из чего она должна состоять и что делать.