Как классы и связи между ними с 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
Ближайшая дата курса
9 декабря, 2024
Продолжительность
16 ак.часов
Стоимость обучения
36 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
Ближайшая дата курса
9 декабря, 2024
Продолжительность
16 ак.часов
Стоимость обучения
36 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
Ближайшая дата курса
9 декабря, 2024
Продолжительность
16 ак.часов
Стоимость обучения
36 000 руб.
Проверить свои знания UML вы можете прямо на нашем сайте, выполнив бесплатный интерактивный тест.
Научитесь самостоятельно разрабатывать эти и другие UML-диаграммы на специализированном курсе «UML для бизнес-аналитиков». Только практика на реальных примерах. После 8-часового интенсива вы сможете дополнять моделировать программную систему, чтобы наглядно объяснить разработчикам, из чего она должна состоять и что делать.