Объектно-ориентированный подход. Объектно_ориентированный_подход. Объектно ориентированный подход Мэтт Вайсфельд 5е международное издание ббк 32. 973. 2018
Скачать 5.43 Mb.
|
107 Моделирование.реальных.систем. . Рис. 5.1. Cabbie.и.Cab.—.реальные.объекты Когда объектно-ориентированная разработка только набирала популярность, многие структурные программисты испытывали затруднения при ее примене- нии. Структурные программисты делали одну и ту же ошибку — создавали классы с поведениями, но без данных, в итоге получив набор из функций и под- программ по аналогии со структурной моделью. Это нам не нужно, поскольку в таком случае не используются преимущества инкапсуляции. В наши дни это справедливо лишь отчасти, потому что часто разработка ведет- ся с применением модели слабых доменов (anemic domain models), также из- вестных как объекты передачи данных (DTO) и модели представления (view models), в которых содержится достаточно данных для заполнения представ- ления или точно необходимое количество данных, требуемых потребителю. Гораздо больше внимания уделяется поведениям и методам обращения с дан- ными и тому, что обрабатывается интерфейсами. Инкапсуляция поведений в интерфейсы, спроектированные по принципу единственной ответственности, и программирование интерфейсов позволяют сохранять гибкость и модульность, а также облегчают сопровождение. ПРИМЕЧАНИЕ _________________________________________________________________________ Одной.из.моих.любимых.книг,.содержащих.указания.и.рекомендации.по.проектиро- ванию,.является.«Эффективное.использование.C++:.50.рекомендаций.по.улучшению. ваших.программ.и.проектов».Скотта.Майерса.(Effective.C++:.50.Specific.Ways.to. Improve.Your.Programs.and.Designs)..Важная.информация.о.проектировании.программ. преподносится.очень.лаконично. Одна из причин, по которым книга «Эффективное использование C++» так сильно интересует меня, заключается в том, что поскольку C++ обратно со- вместим с языком программирования C, компилятор позволит вам писать структурированный код на C++ без применения принципов объектно-ориен- тированного проектирования. Как я уже отмечал ранее, на собеседовании некоторые люди утверждают, что занимаются объектно-ориентированным программированием, просто потому, что они пишут код на C++. Это свидетель- ствует о полном непонимании того, что такое объектно-ориентированное про- ектирование. Таким образом, возможно, придется уделять больше внимания Глава.5..Руководство.по.проектированию.классов 108 вопросам проектирования ОО в таких языках, как C ++, в отличие от Java, Swift или .NET. Определение открытых интерфейсов К настоящему времени должно быть понятно, что, пожалуй, наиболее важная задача при проектировании класса — обеспечение минимального открытого интерфейса. Создание класса полностью сосредоточено на обеспечении чего-то полезного и компактного. В книге «Объектно-ориентированное проектирование на Java» Гилберт и Маккарти пишут, что «интерфейс хорошо спроектирован- ного объекта описывает услуги, оказание которых требуется клиенту». Если класс не будет предоставлять полезных услуг пользователям, то его вообще не следует создавать. Минимальный открытый интерфейс Обеспечение минимального открытого интерфейса позволяет сделать класс как можно более компактным. Цель состоит в том, чтобы предоставить пользовате- лю именно тот интерфейс, который даст ему возможность правильно выполнить соответствующую работу. Если открытый интерфейс окажется неполным (то есть будет отсутствовать поведение), то пользователь не сможет сделать всю работу. Если для открытого интерфейса не будет предусмотрено соответству- ющих ограничений (то есть пользователю предоставят доступ к поведению, что будет излишним или даже опасным), то в результате может возникнуть необ- ходимость отладки или, возможно, даже появятся проблемы с целостностью и защитой системы. Создание класса — серьезная задача, и, как и на всех этапах процесса проекти- рования, очень важно, чтобы пользователи были вовлечены в него с самого начала и на всем протяжении стадии тестирования. Таким образом, это позволит создать полезный класс, а также обеспечить надлежащие интерфейсы. РАСШИРЕНИЕ ИНТЕРФЕЙСА __________________________________________________________ Даже.если.открытого.интерфейса.класса.окажется.недостаточно.для.определенно- го.приложения,.объектная.технология.с.легкостью.позволит.расширить.и.адаптиро- вать.этот.интерфейс..Коротко.говоря,.если.спроектировать.новый.класс.с.учетом. наследования.и.композиции,.то.он.сможет.использовать.существующий.класс. и.создать.новый.класс.с.расширенным.интерфейсом. Чтобы проиллюстрировать это, снова обратимся к примеру с Cabbie . Если дру- гим объектам в системе потребуется извлечь значение name объекта Cabbie , то класс Cabbie должен будет обеспечивать открытый интерфейс для возврата этого значения; им будет метод getName() . Таким образом, если объекту 109 Определение.открытых.интерфейсов. . Supervisor понадобится извлечь значение name объекта Cabbie , то ему придется вызвать метод getName() из объекта Cabbie . Фактически Supervisor будет за- прашивать у Cabbie значение его атрибута name (рис. 5.2). Рис. 5.2. Открытый.интерфейс.определяет,.как.объекты.взаимодействуют Пользователи вашего кода не должны ничего знать о его внутренней работе. Им нужно знать лишь то, как создавать экземпляры и использовать объекты. Ины- ми словами, обеспечивайте для пользователей способ выполнять требуемые им действия, но скрывайте детали. Сокрытие реализации Необходимость скрывать реализацию уже рассматривалась очень подробно. Хотя определение открытого интерфейса — это задача проектирования, «вра- щающаяся» вокруг пользователей класса, реализация не должна их вообще касаться. Реализация будет предоставлять услуги, необходимые пользователям, однако способ их предоставления не следует делать очевидным для пользова- телей. По своей сути изменение реализации не должно неизбежно повлечь за собой изменение в пользовательском программном коде. Опять же, лучший способ обеспечить изменение поведений — посредством интерфейсов и компо- зиции. КЛИЕНТ В ПРОТИВОПОСТАВЛЕНИИ С ПОЛЬЗОВАТЕЛЕМ _____________________________ Иногда.я.использую.термин.«клиент».вместо.«пользователь»,.когда.говорю.о.людях,. которые.в.действительности.будут.использовать.программное.обеспечение..По- скольку.я.консультант,.то.пользователи.системы.фактически.будут.клиентами..В.том. же.духе.пользователи,.относящиеся.к.вашей.организации,.будут.называться.вну- тренними.клиентами..Это.может.показаться.тривиальным,.но.я.думаю,.что.важно. считать.всех.конечных.пользователей.фактическими.клиентами,.—.и.вы.должны. удовлетворить.их.требования. В примере с Cabbie класс с аналогичным названием мог содержать поведение, касающееся того, как таксист завтракает. Однако объекту Supervisor не нужно Глава.5..Руководство.по.проектированию.классов 110 знать, что таксист, представляемый объектом Cabbie , ел на завтрак. Таким об- разом, это поведение является частью реализации объекта Cabbie и не должно быть доступно другим объектам в этой системе (рис. 5.3). Гилберт и Маккарти пишут, что основная директива инкапсуляции заключается в том, что «все поля будут закрытыми». Таким образом, ни одно из полей в классе не будет доступ- но из других объектов. Рис. 5.3. Объектам.не.нужно.знать.некоторые.детали.реализации Проектирование надежных конструкторов (и, возможно, деструкторов) При проектировании класса одна из самых важных соответствующих задач — принять решение о том, как этот класс будет сконструирован. Конструкторы были рассмотрены в главе 3 «Прочие объектно-ориентированные концепции. Загляните в нее снова, если вам потребуется освежить свои знания насчет ос- новных принципов проектирования конструкторов. Прежде всего конструктор должен задать для объекта его начальное, надежное состояние. Сюда входит выполнение таких задач, как инициализация атрибутов и управление памятью. Вам также потребуется убедиться в том, что объект должным образом сконструирован в состоянии по умолчанию. Как вариант, можно обеспечить конструктор для обработки этой стандартной ситуации. ВНЕДРЕНИЕ КЛАССОВ КОНСТРУКТОРОМ _____________________________________________ А.вот.здесь.удобно.будет.представить.концепцию.внедрения.классов.конструктором,. когда.служебные.классы.внедряются.при.создании.объекта.(с.помощью.конструк- тора).вместо.внедрения.внутрь.класса.(с.применением.нового.ключевого.слова).. Например,.таксист.может.получить.объект.водительского.удостоверения,.объект. сведений.для.радиосвязи.(частота,.позывной.и.т..д.),.а.ключ,.который.запускает.его. такси,.передается.в.объект.через.конструктор. При использовании языков программирования, в которых имеются деструкто- ры, жизненно важно, чтобы эти деструкторы включали соответствующие функ- 111 Внедрение.обработки.ошибок.в.класс. . ции очистки. В большинстве случаев такая очистка связана с высвобождением системной памяти, полученной объектом в какой-то момент. Java и .NET авто- матически регенерируют память с помощью механизма сборки мусора. При использовании языков программирования вроде C++ разработчик должен включать код в деструктор для надлежащего высвобождения памяти, которую объект занимал во время своего существования. Если проигнорировать эту функцию, то в результате произойдет утечка памяти. УТЕЧКИ ПАМЯТИ _______________________________________________________________________ Когда.объект.не.высвобождает.надлежащим.образом.память,.которую.занимал.во. время.своего.жизненного.цикла,.она.оказывается.утраченной.для.всей.операционной. системы.до.тех.пор,.пока.выполняется.приложение,.создавшее.этот.объект..Допустим,. множественные.объекты.одного.и.того.же.класса.создаются,.а.затем.уничтожаются,. возможно,.в.рамках.некоторого.цикла..Если.эти.объекты.не.высвободят.свою.память,. когда.окажутся.вне.области.видимости,.то.соответствующая.утечка.памяти.приведет. к.исчерпанию.доступного.пула.системной.памяти..В.какой-то.момент.может.оказать- ся.израсходовано.столько.памяти,.что.у.системы.не.останется.свободного.ее.объема. для.выделения..Это.означает,.что.любое.приложение,.выполняющееся.в.системе,.не. сумеет.получить.хоть.сколько-нибудь.памяти..Это.может.ввергнуть.приложение.в.не- стабильное.состояние.и.даже.привести.к.блокировке.системы. Внедрение обработки ошибок в класс Как и при проектировании конструкторов, жизненно важно продумать, как класс будет обрабатывать ошибки. Обработка ошибок была подробно рассмотрена в главе 3. Почти наверняка можно сказать, что каждая система будет сталкиваться с не- предвиденными проблемами. Поэтому не стоит игнорировать потенциальные ошибки. Разработчик хорошего класса (или любого кода, если на то пошло) предвидит потенциальные ошибки и предусматривает код для обработки таких ситуаций, когда они имеют место. Согласно общему правилу, приложение никогда не должно завершаться ава- рийно. При обнаружении ошибки система должна либо «починить» себя и про- должить функционировать, либо корректно завершить свою работу без потери каких-либо данных, важных для пользователя. Документирование класса и использование комментариев Тема комментариев и документирования поднимается в каждой книге и статье по программированию, при каждой проверке кода, в каждой дискуссии насчет Глава.5..Руководство.по.проектированию.классов 112 грамотного подхода к проектированию, в которой вы участвуете. К сожалению, комментарии и надлежащее документирование зачастую не принимаются все- рьез или, что еще хуже, игнорируются. Большинству разработчиков известно, что следует тщательно документировать свой код, однако обычно они не желают тратить на это время. Но грамотный подход к проектированию практически невозможен без правильных методик документирования. На уровне класса область видимости может оказаться до- статочно небольшой для того, чтобы разработчик смог отделаться низкосортной документацией. Однако когда класс передается кому-то другому для расшире- ния и/или сопровождения либо становится частью более крупной системы (что и должно случиться), отсутствие надлежащей документации и комментариев может подорвать всю систему. Многие люди уже говорили все это раньше. Один из самых важных аспектов грамотного подхода к проектированию, будь то проектирование класса или чего-то другого, — тщательное документирование процесса. Такие реализации, как Java и .NET, предусматривают специальный синтаксис комментариев для облегчения процесса документирования. Загляните в главу 4, чтобы увидеть соответствующий синтаксис. СЛИШКОМ БОЛЬШОЕ КОЛИЧЕСТВО ДОКУМЕНТАЦИИ ________________________________ Имейте.в.виду,.что.чрезмерное.комментирование.тоже.может.привести.к.проблемам.. Слишком.большое.количество.документации.и/или.комментариев.может.мешать. и.вообще.действовать.во.вред.целям.документирования..Как.и.при.грамотном.под- ходе.к.проектированию.классов,.делайте.так,.чтобы.документация.и.комментарии. были.понятными.и.все.было.по.существу..Грамотно.написанный.код.—.сам.по.себе. хорошая.документация. Создание объектов с прицелом на взаимодействие Мы можем с уверенностью сказать, что почти ни один класс не существует в изоляции. В большинстве случаев нет никаких причин создавать класс, если он не будет взаимодействовать с другими классами. Это факт в «жизни» класса. Класс станет оказывать услуги другим классам, запрашивать услуги других классов либо делать и то и другое. В последующих главах мы рассмотрим раз- личные способы, посредством которых классы могут взаимодействовать друг с другом. В приводившемся ранее примере Cabbie и Supervisor не являются автоном- ными сущностями; они взаимодействуют друг с другом на разных уровнях (рис. 5.4). При проектировании класса убедитесь, что вы отдаете себе отчет в том, как другие объекты будут взаимодействовать с ним. 113 Проектирование.с.учетом.расширяемости. . Рис. 5.4. Объекты.должны.запрашивать.информацию Проектирование с учетом повторного использования Объекты могут повторно использоваться в разных системах, а код следует писать с учетом такого повторного использования. Например, когда класс Cabbie раз- работан и протестирован, его можно применять везде, где требуется такой класс. Чтобы сделать класс пригодным к использованию в разных системах, его нуж- но проектировать с учетом повторного использования. Именно здесь потребу- ется хорошо поразмыслить в процессе проектирования. Попытка предсказать все возможные сценарии, при которых объект Cabbie должен будет работать, — непростая задача. Более того, это фактически невозможно сделать. Проектирование с учетом расширяемости Добавление новых функций в класс может быть таким же простым, как рас- ширение существующего класса, добавление нескольких новых методов и мо- дификация поведений других. Нет надобности все переписывать. Именно здесь в дело вступает наследование. Если вы только что написали класс Person , то должны принимать во внимание тот факт, что позднее вам, возможно, потребу- ется написать класс Employee или Vendor . Поэтому лучше всего сделать так, чтобы Employee наследовал от Person ; в этом случае класс Person будет назы- ваться расширяемым. Вы не захотите проектировать класс Person таким образом, чтобы он содержал поведение, которое будет препятствовать его расширяемости другими классами, например Employee или Vendor (предполагается, что при проектировании вы будете действительно нацелены на то, чтобы другие классы расширяли Person ). Например, вы не захотели бы включать в класс Employee Глава.5..Руководство.по.проектированию.классов 114 функциональность, характерную для супервизорных функций. Если бы вы все же решились на это, а класс, которому не требуется такая функциональность, наследовал бы от Employee , то у вас возникла бы проблема. Этот аспект затрагивает рекомендации по абстрагированию, о которых шла речь ранее. Person должен содержать только данные и поведения, специфичные для него. Другие классы тогда смогут быть его подклассами и наследовать соответ- ствующие данные и поведения. Поскольку мы будем обсуждать принципы SOLID в главе 11 «Избегание за- висимостей и тесно связанных классов», а также в главе 12 «Принципы объ- ектно-ориентированного проектирования SOLID», забегая вперед, расскажу, что классы должны быть расширяемыми, но не модифицируемыми. С приме- нением интерфейсов и их программированием у вас появится возможность использования любых паттернов, например декоратора, для расширения воз- можностей класса без затрагивания проверенного кода, который стал его важной частью. КАКИЕ АТРИБУТЫ И МЕТОДЫ МОГУТ БЫТЬ СТАТИЧЕСКИМИ? _________________________ Если.применяются.статические.методы,.может.происходить.тесное.связывание. классов..Нельзя.абстрагировать.статический.метод..Нельзя.мокировать.статический. метод.или.класс..Нельзя.предоставить.статический.интерфейс..Единственный.слу- чай,.когда.оправданно.применение.статических.классов.(во.время.разработки.при- ложения,.разработка.фреймворка.несколько.отличается),.—.это.когда.вы.имеете. дело.с.каким-нибудь.вспомогательным.классом.или.методом.расширения,.который. не.приведет.к.нежелательным.побочным.эффектам..Например,.статический.класс. подойдет.для.сложения.чисел..Но.статический.класс.плох.для.обращения.к.базам. данных.или.веб-сервисам. Делаем имена описательными Ранее мы рассмотрели использование надлежащей документации и коммента- риев. Следование соглашению об именовании классов, атрибутов и методов является схожей темой. Существует большое количество соглашений об име- новании, а то, какое именно из них вы выберете, не так важно, как просто выбор одного соглашения и следование ему. Однако при выборе соглашения убедитесь в том, что, придумывая имена для классов, атрибутов и методов, вы не только следуете соответствующему соглашению, но и делаете эти имена описательны- ми. Когда кто-нибудь прочтет одно из этих имен, он должен понимать по имени объекта, что тот собой представляет. Какие конкретно соглашения об именова- нии будут использоваться, в различных организациях зачастую обусловлива- ется стандартами программирования. Делая имена описательными, вы будете придерживаться правильной методики разработки, которая выходит за пределы различных парадигм разработки. |