Введение
Роберт Л. Гласс утверждал, что практика важнее теории. Особенно в разработке программного обеспечения практика является типичной областью, опережающей теорию, и разработчики извлекают наибольшую пользу, когда «пачкают руки», работая с конкретным кодом. Поэтому мы отложим в сторону теорию и концепции и рассмотрим простую программу.
Реализация приложения для продажи билетов
- Мы планируем организовать небольшое мероприятие для продвижения небольшого театра.
- Содержание мероприятия: рассылка приглашений на бесплатный просмотр спектакля выбранным в результате лотереи зрителям.
- Необходимо разделить зрителей, выигравших в лотерее, и тех, кто не выиграл.
- Зрители, выигравшие в лотерее: обмен приглашения на билет.
- Зрители, не выигравшие в лотерее: покупка билета за деньги.
В чем проблема?
Роберт Мартин описывает три функции, которыми должен обладать программный модуль (независимо от размера, будь то класс, пакет или библиотека, любой элемент, составляющий программу).
- Работает правильно во время выполнения.
- Существует для изменений.
- Изменения должны быть возможны с минимальными усилиями.
- Общается с тем, кто читает код.
- Разработчику должно быть легко читать и понимать.
Приведенное выше приложение для продажи билетов удовлетворяет первому ограничению — правильному выполнению, но не соответствует целям простоты изменения и общения.
Код, не соответствующий ожиданиям
Давайте рассмотрим причину несоответствия цели общения.
- Что делает метод enter() класса Theater
- Театр проверяет сумку зрителя, чтобы узнать, есть ли в ней приглашение.
- Если в сумке есть приглашение, то театр приказывает кассиру переместить билет, хранящийся в кассе, в сумку зрителя.
- Если в сумке нет приглашения, то театр забирает из сумки зрителя деньги в размере стоимости билета, покупает билет и кладет его в сумку.
- С точки зрения зрителя, он вынужден наблюдать, как третья сторона, в данном случае театр, произвольно копается в его сумке, забирает деньги и кладет туда билет.
- С точки зрения кассира, он вынужден наблюдать, как третья сторона, в данном случае театр, без разрешения манипулирует билетами и деньгами в кассе.
- Понятный код — это код, действия которого не сильно расходятся с нашими ожиданиями, а действия театра не соответствуют нашим ожиданиям.
- Зритель должен самостоятельно достать деньги из сумки и заплатить кассиру за билет.
- Кассир должен самостоятельно достать билет из кассы, передать его зрителю и получить от него деньги, положив их в кассу.
- Кроме того, для понимания метода enter() необходимо помнить множество деталей.
- У Audience есть Bag.
- В Bag хранятся наличные и билет.
- TiketSellet продает билеты в TicketOffice, и в TicketOffice хранятся деньги и билеты.
Код, уязвимый к изменениям
Метод enter() предполагает два условия.
- Зритель всегда носит с собой сумку для хранения наличных и приглашения.
- Кассир продает билеты только в кассе.
А что, если ситуация изменится?
- У зрителя может не быть сумки.
- Зритель может использовать кредитную карту вместо наличных.
- Кассир может продавать билеты вне кассы.
Например, для удовлетворения первого требования необходимо удалить объект Bag класса Audience и изменить связанный с ним метод enter() класса Theater. Это связано с тем, что класс Theater чрезмерно полагается на слишком подробные сведения о том, что зритель носит сумку, а кассир продает билеты только в кассе. Если хотя бы один из этих подробных фактов изменится, придется изменить как этот класс, так и зависящие от него классы (например, Theater).
Это проблема, связанная с зависимостью между объектами, и зависимость подразумевает влияние на изменения. Однако объектно-ориентированный дизайн ставит целью построение сообщества объектов, взаимодействующих друг с другом, поэтому необходимо не просто устранять зависимости, а поддерживать минимальную зависимостьнеобходимую для реализации функций приложения, и удалять ненужные зависимости.
Ситуацию, когда зависимость между объектами слишком велика, называют высокой степенью связанностимежду двумя объектами. Чем выше степень связанности между двумя объектами, тем выше вероятность того, что они будут изменены вместе. Следовательно, целью проектирования становится снижение степени связанности между объектами для создания легко изменяемой конструкции.
Улучшение конструкции
Theater не нужно знать, что зритель носит сумку, и что кассир продает билеты в кассе. Theater просто хочет, чтобы зритель вошел в театр. Поэтому необходимо сделать зрителя и кассира автономными сущностями, чтобы они сами обрабатывали наличные и приглашения в сумке и билеты и стоимость продажи в кассе соответственно.
Повышение автономии
Объект, который выполняет только тесно связанные задачи и делегирует несвязанные задачи другим объектам, называют объектом с высокой степенью связности. Создание автономных объектов, которые сами обрабатывают свои данные, позволяет снизить степень связанности и повысить степень связности.
Процедурное и объектно-ориентированное программирование
- Процедурное программирование
- Метод enter() класса Theater — это процесс, а Audience, TicketSeller, Bag, TicketOffice — это данные.
- Способ размещения процессов и данных в отдельных модулях называется процедурным программированием.
- В нем много кода, противоречащего нашей интуиции. (Например, зритель сам управляет деньгами и приглашениями.)
- Трудно сузить влияние изменений данных.
- Управление ответственностью централизовано. (Theater управляет всем.)
- Объектно-ориентированное программирование
- Способ размещения данных и процессов внутри одного и того же модуля называется объектно-ориентированным программированием.
- Можно писать код, соответствующий нашей интуиции.
- Влияние изменений данных можно эффективно сузить с помощью инкапсуляции.
- Каждый объект несет ответственность за себя.
Можно улучшить еще больше.
- Класс Bag класса Audience по-прежнему является пассивной сущностью, управляемой классом Audience, поэтому необходимо сделать класс Bag автономным объектом.
- TicketOffice класса TicketSeller также управляется TicketSeller. Необходимо сделать TicketOffice автономным объектом.
- Однако после изменения между TicketOffice и Audience возникла дополнительная связанность.
- Таким образом, при проектировании необходимо тщательно взвешивать компромиссы. В этом случае можно договориться о том, чтобы сделать TicketOffice в некоторой степени пассивным объектом, чтобы снизить степень связанности с Audience.
Да, это ложь!
- В реальности, даже если что-то является пассивным, попав в мир объектно-ориентированного программирования, оно превращается в активную и автономную сущность.
- Полезно использовать антропоморфизм и представлять пассивные объекты как объекты, которые смеются, разговаривают и злятся.
Объектно-ориентированное проектирование
Зачем нужно проектирование?
- Проектирование — это размещение кода.
- Хорошее проектирование — это такое, которое полностью выполняет сегодняшние требования и позволяет легко адаптироваться к будущим изменениям.
Объектно-ориентированное проектирование
- Изменяемый код — это понятный код.
- Объектно-ориентированная парадигма помогает писать код так же, как мы видим мир.
- Объект — это автономная сущность, которая сама несет ответственность за свои данные.
- Отличное объектно-ориентированное проектирование — это проектирование, которое правильно управляет зависимостями между взаимодействующими объектами.
Источники
- Объекты
Комментарии0