Система бронирования кинобилетов
Рассмотрение требований
- Мы хотим реализовать онлайн-систему бронирования кинобилетов.
- Фильм представляет собой базовую информацию о фильме.
- Название, время показа, информация о цене и т.д.
- Показ представляет собой фактическое событие, когда зрители смотрят фильм.
- Дата показа, время, номер сеанса и т.д.
- Люди бронируют фильмы, но на самом деле правильнее сказать, что они бронируют конкретный сеансфильма.
- Условия скидки
- Наличие скидки на цену
- Условие по порядку: определение наличия скидки с использованием номера сеанса
- Условие по периоду: определение наличия скидки с использованием времени начала показа фильма
- Политика скидок
- Определение скидочной цены
- Политика скидки в денежном выражении: скидка на определенную сумму от стоимости бронирования
- Политика процентной скидки: скидка в процентах от полной цены
- Для каждого фильма можно назначить одну политику скидок или не назначать вообще, а условия скидки можно комбинировать.
- Если политика скидок применяется, но условия скидки не выполнены, или если политика скидок не применяется, то скидка не предоставляется.
На пути к объектно-ориентированному программированию
Взаимодействие, объекты, классы
- Объектно-ориентированное программирование ориентировано на объекты.
- Не следует определять классы, а затем думать о том, какие атрибуты и методы им необходимы.
- Необходимо определить, какие объекты обладают какими состояниями и поведением.
- Объекты следует рассматривать не как независимые сущности, а как часть взаимодействующего сообщества.
Структура программы, соответствующая структуре предметной области
- Предметная область — это область, в которой пользователь использует программу для решения задачи.
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fe7d22a03-4a24-40e9-8068-bd03b9fd816b%2FUntitled.png?table=block&id=3f1931fc-8ef9-470a-8189-3727146638f6&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/438;" width="2000" height="438"></span>
- Как правило, имена классов должны совпадать или, по крайней мере, быть похожими на имена соответствующих понятий предметной области.
- Связи между классами также должны максимально соответствовать связям между понятиями предметной области, чтобы сделать структуру программы понятной и предсказуемой.
Реализация класса
- Класс разделен на внутреннюю и внешнюю части, и для разработки качественного класса необходимо определить, какие части будут открыты снаружи, а какие скрыты.
- Атрибуты объекта скрыты с помощью модификатора private, а необходимые для изменения внутреннего состояния методы открыты с помощью модификатора public.
- Разделение внутреннего и внешнего пространства класса гарантирует автономность объекта, предоставляя программисту свободу реализации.
Автономные объекты
- Объект должен быть автономным, обладать состоянием и поведением.
- Объединение данных и функций внутри объекта называется инкапсуляцией.
- Контроль доступа позволяет уменьшить внешнее вмешательство, что позволяет объекту самостоятельно принимать решения о своих действиях.
- Принцип разделения интерфейса и реализации является одним из основных принципов, которым необходимо следовать при объектно-ориентированном программировании.
- Публичный интерфейс: доступная внешним пользователям часть
- Реализация: доступная только внутренней части
Свобода программиста
- Роль программиста делится на разработчика класса и клиента-программиста.
- Разработчик класса добавляет новые типы данных
- Клиент-программист использует типы данных, добавленные разработчиком класса
- Скрытие реализации
- Разработчик класса может скрыть внутреннюю реализацию, предоставив клиенту-программисту только необходимые части.
- Клиенту-программисту достаточно знать только интерфейс, что позволяет уменьшить объем знаний.
Сообщество взаимодействующих объектов
- При представлении денег лучше обернуть их в объект, например, Money, вместо простого объявления переменной типа Long. Использование объектов позволяет лучше передавать смысл и обрабатывать повторяющиеся вычисления в одном месте.
- Взаимодействие между объектами для реализации какой-либо функции системы называется взаимодействием.
Краткая история взаимодействия
- Единственный способ взаимодействия объекта с другими объектами — отправка или прием сообщений.
- Свой собственный способ обработки принятого сообщения называется методом.
- Важно различать сообщение и метод, именно отсюда начинается понятие полиморфизма.
Вычисление скидочной стоимости
Начало взаимодействия для расчета скидочной стоимости
- В классе Movie нет подробной логики, связанной с политикой скидок, она делегирована интерфейсу DiscountPolicy. Использование наследования, полиморфизма и абстракции действительно важно.
Политика скидок и условия скидки
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F7b9b27a5-0dac-4ba7-9552-05b7f8fbdecd%2FUntitled.png?table=block&id=3264f189-6e12-4a55-94d2-75b851320c7a&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/535;" width="2000" height="535"></span>
Наследование и полиморфизм
Зависимость во время компиляции и во время выполнения
- Зависимость кода и зависимость во время выполнения могут различаться.
- Чем больше различаются эти две зависимости, тем сложнее понять код, но тем гибче и расширяемее он становится.
Программирование на основе различий
- Наследование позволяет легко и быстро добавлять новые классы на основе существующих классов, а также повторно использовать реализацию родительского класса.
- Способ создания нового класса путем добавления только отличающихся частей от родительского класса называется программированием на основе различий.
Наследование и интерфейсы
- Наследование позволяет дочернему классу наследовать все интерфейсы, предоставляемые родительским классом.
- Интерфейс определяет список сообщений, которые может понимать объект.
- Movie отправляет DiscountPolicy сообщение calculateDiscountAmount. Movie не важно, экземпляр какого класса отвечает, главное, чтобы ответ был успешным.
- Таким образом, как AmountDiscountPolicy, так и PercentDiscountPolicy могут взаимодействовать с Movie вместо DiscountPolicy.
- Такое замещение дочернего класса родительским называется апкастингом (upcasting). Это связано с тем, что дочерний класс автоматически преобразуется в родительский класс.
Полиморфизм
- Полиморфизм — это способность объекта по-разному реагировать на одно и то же сообщение в зависимости от типа объекта.
- Movie отправляет одно и то же сообщение, но метод, который будет фактически выполнен, зависит от класса объекта, принимающего сообщение.
- Полиморфизм основан на том факте, что зависимость кода во время компиляции и во время выполнения может различаться.
- Поскольку полиморфизм определяет выполняемый метод во время выполнения, его называют поздним связыванием или динамическим связыванием.
Интерфейсы и полиморфизм
- Если нет необходимости совместно использовать реализацию, а нужно совместно использовать только интерфейс, то вместо абстрактного класса можно использовать интерфейс.
Абстракция и гибкость
Сила абстракции
- Преимущества абстракции
- Если рассматривать только иерархию абстракции, то политику требований можно описать на высоком уровне.
- Дизайн становится более гибким.
Гибкий дизайн
- Абстракция предотвращает привязку дизайна к конкретным ситуациям, поэтому позволяет создавать гибкий дизайн.
Компромисс между абстрактным классом и интерфейсом
- В настоящее время NoneDiscountPolicy на самом деле не нужен, поскольку метод calculateDiscountAmount() класса DiscountPolicy возвращает 0, если нет условий скидки, поэтому проблем нет. Однако с точки зрения пользователя необходимо передать в Movie политику None, поэтому он был добавлен.
- Поэтому класс NoneDiscountPolicy выглядит немного запутанно, и если изменить его следующим образом, то он станет более понятным.
- Другими словами, мы абстрагируем DiscountPolicy как интерфейс и делим его на реализации — NoneDiscountPolicy и DefaultDiscountPolicy, которая представляет собой общую политику скидок.
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F712983a9-3937-4265-82fe-66a144c44a0f%2FUntitled.png?table=block&id=f5ca651c-d03c-4a6c-97e9-7443ee5649a0&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/886;" width="2000" height="886"></span>
- Можно подумать, что добавление интерфейса только для NoneDiscountPolicy снижает эффективность по сравнению с увеличенной сложностью.
- Важно понимать, что все, что связано с реализацией, может быть предметом компромисса, и проектирование должно учитывать это. То есть для каждого написанного фрагмента кода должна быть веская причина.
Повторное использование кода
- Наследование можно использовать для повторного использования кода.
- Однако для повторного использования кода лучше использовать композицию, чем наследование.
- В текущем коде Movie использует код DiscountPolicy именно с помощью композиции.
- Если мы возьмем Movie в качестве родительского класса и разделим его на AmountDiscountMovie и PercentDiscountMovie, то это будет использование наследования.
Наследование
- Недостатки наследования
- Нарушает инкапсуляцию.
- Необходимо хорошо знать внутреннюю структуру родительского класса.
- При изменении родительского класса велика вероятность изменения дочернего класса.
- Делает дизайн менее гибким.
- Связь между родительским и дочерним классами определяется во время компиляции.
- Невозможно изменить тип объекта во время выполнения.
- Однако при композиции можно заменить экземпляр во время выполнения с помощью метода changeDiscountPolicy() и т.п.
- Нарушает инкапсуляцию.
Композиция
- Способ повторного использования кода только через сообщения, определенные в интерфейсе, называется композицией.
- Позволяет реализовать инкапсуляцию и поддерживать слабую связь.
- При использовании интерфейсов для полиморфизма необходимо использовать сочетание наследования и композиции.
- Например, для реализации интерфейса DiscountPolicy приходится использовать наследование.
Источники
- Объект
Комментарии0