Руководство по стилю программирования и конструированию по
Скачать 7.6 Mb.
|
Уровень 3: разделение подсистем на классы Этот уровень проектирования предполагает определение всех классов системы. Например, подсистема доступа к БД может быть далее разде- лена на классы доступа к данным и классы хранения данных, а также метаданные БД. На рис. 5-2 показано, как разделить на классы одну из подсистем уровня 2; конечно, три других подсистемы также следует разделить на классы. Перекрестная ссылка Об упро- щении бизнес-логики путем ее выражения в форме таблиц см. главу 18. Дополнительные сведения Про- ектирование БД хорошо рассмо- трено в книге «Agile Data base Techniques» (Ambler, 2003). ГЛАВА 5 Проектирование при конструировании 83 Кроме того, на этом уровне следует определить детали взаимодействия каждого класса с остальными элементами системы, особенно интерфейс класса. В целом сутью проектирования на данном уровне является декомпозиция подсистем до такого уровня детальности, который позволит реализовать части подсистем в форме отдельных классов. Разделение подсистем на классы обычно требуется во всех про- ектах, на реализацию которых уйдет более нескольких дней. В крупных проектах необходимы и второй, и третий уровни проектирования. При работе над совсем небольшим проектом второй уровень проектирования можно пропустить. Классы и объекты Один из важнейших аспектов объектно-ориентированного проектирования — различие между объектами и классами. Объект — это любая конкретная динами- ческая сущность, имеющая конкретные значения и атрибуты и существующая в период выполнения программы. Класс — это статическая сущность, с которой вы имеете дело, просматривая листинг программы. Например, вы можете объявить класс Person (человек), имеющий такие атрибуты, как фамилия, возраст, пол и т. д. В период выполнения вы будете работать с объектами nancy, hank, diane, tony и т. д. — иначе говоря, со специфическими экземплярами класса. Если вы знакомы с терминологией БД, различие между классом и объектом аналогично различию между «схемой» и «экземпляром». Класс можно рассматривать как форму для вы- печки булочек, а объекты — как сами булочки. В этой книге термины «класс» и «объект» используются неформально и более или менее взаимозаменяемо. Уровень 4: разделение классов на методы Данный уровень проектирования заключается в разделении каждого класса на методы. Некоторые методы уже будут определены на уровне 3, при проектирова- нии интерфейсов классов. На уровне 4 вы детально определите закрытые методы классов. При этом многие методы окажутся простыми, тогда как другие будут включать иерархии методов и потребуют дополнительного проектирования. Полное определение методов класса часто позволяет лучше понять его интерфейс, что может подтолкнуть к соответствующему изменению интерфейса, т. е. к воз- вращению на уровень 3. Четвертый уровень декомпозиции и проектирования часто оставляется отдельным программистам и необходим в любом проекте, требующем более нескольких часов. Формально выполнять этот этап не обязательно, но хотя бы про себя вы- полнить его нужно. Уровень 5: проектирование методов На этом уровне проектирование заключается в детальном определении функциональности отдельных методов, за что обычно отвечают отдельные программисты, работающие над конкретными методами. Данный уровень может включать такие действия, как написание псевдокода, поиск алгоритмов в книгах, размыш- ление над оптимальной организацией фрагментов метода и написание кода. Этот Перекрестная ссылка Характе- ристики высококачественных классов см. в главе 6. Перекрестная ссылка О созда- нии высококачественных мето- дов см. главы 7 и 8. 84 ЧАСТЬ II Высококачественный код уровень проектирования выполняется во всех случаях, но не всегда осознанно и качественно. На рис. 5-2 он отмечен цифрой 5. 5.3. Компоненты проектирования: эвристические принципы Разработчики ПО любят четкие и ясные правила: «Сделай A, B и C, и это обяза- тельно приведет к X, Y и Z». Мы испытываем гордость, когда находим тайные дей- ствия, приводящие к желаемым результатам, и сердимся, если команды работают не так, как описано. Стремление к детерминированному поведению прекрасно согласуется с детальным программированием, при котором строгое внимание к деталям может определить успех или провал программы. Однако проектирование ПО — совсем другая история. Так как проектирование не является детерминированным, главным аспектом про- ектирования качественного ПО становится умелое применение набора эффек- тивных эвристических принципов. Ниже мы рассмотрим ряд таких принципов — подходов, способных привести к удачным решениям. Можете считать эвристиче- ские принципы правилами выполнения проб при использовании метода проб и ошибок. Несомненно, некоторые из них вам уже известны. Каждый из эвристи- ческих принципов будет описан в контексте Главного Технического Императива Разработки ПО — управления сложностью. Определите объекты реального мира Первый, и самый популярный, подход к проектированию — «общепринятый» объектно-ориентированный подход — основан на определении объектов реального мира и ис- кусственных объектов. При проектировании с использованием объектов опреде- лите: 쐽 объекты и их атрибуты (методы и данные); 쐽 действия, которые могут быть выполнены над каждым объектом; 쐽 действия, которые каждый объект может выполнять над другими объектами; 쐽 части каждого объекта, видимые другим объектам, т. е. открытые и закрытые части; 쐽 открытый интерфейс каждого объекта. Эти часто повторяющиеся действия не обязательно выполнять в указанном по- рядке. Помните о важности итерации. Определите объекты и их атрибуты В основе создания программ обычно лежат сущности реального мира. Например, система расчета повременной оплаты может быть основана на таких сущностях, как сотрудники, клиенты, карты учета времени и счета (рис. 5-6). Прежде всего следует узнать, не что система выполняет, а над ЧЕМ она это выполняет! Бертран Мейер (Bertrand Meyer) Перекрестная ссылка О про- ектировании с использованием классов см. главу 6. ГЛАВА 5 Проектирование при конструировании 85 Рис. 5-6. Эта система расчета оплаты состоит из четырех основных объектов (пример упрощен) Определить атрибуты объектов не сложнее, чем сами объекты. Каждый объект имеет характеристики, релевантные для компьютерной программы. Скажем, в системе расчета повременной оплаты объект «сотрудник» обладал бы такими атрибутами, как имя/фамилия, должность и уровень оплаты. С объектом «счет» были бы связаны такие атрибуты, как сумма, имя/фамилия клиента, дата и т. д. Объектами системы GUI были бы разнообразные окна, кнопки, шрифты и ин- струменты рисования. При дальнейшем изучении проблемной области вы можете прийти к выводу, что установление однозначного соответствия между объектами программы и объектами реального мира — не самый лучший способ определения объектов, но для начала он тоже неплох. Определите действия, которые могут быть выполнены над каждым объ- ектом Объекты могут поддерживать самые разные операции. В нашей системе расчета оплаты объект «сотрудник» мог бы поддерживать изменение должности или уровня оплаты, объект «клиент» — изменение реквизитов счета и т. д. Определите действия, которые каждый объект может выполнять над другими объектами Суть этого этапа ясна из его названия. Двумя универсаль- ными действиями, которые объекты могут выполнять друг над другом, являются включение (containment) и наследование. Какие объекты могут включать другие (какие?) объекты? Какие объекты могут быть унаследованными от других (ка- ких?) объектов? На рис. 5-6 объект «карта учета времени» может включать объект «сотрудник» и объект «клиент», а объект «счет» может включать карты учета вре- мени. Кроме того, счет может сообщать, что клиент оплатил услуги, а клиент — оплачивать указанную в счете сумму. Более сложная система включала бы допол- нительные взаимодействия. Определите части каждого объекта, видимые другим объектам Один из главных аспектов проектирования — определение частей объекта, которые следует сделать открытыми, и частей, которые следует держать закрытыми. Этого решения требуют и данные, и методы. 86 ЧАСТЬ II Высококачественный код Определите интерфейс каждого объекта Для каждо- го объекта надо определить формальный синтаксический интерфейс на уровне языка программирования. Данные и методы, которые объект предоставляет в распоряжение остальным объектам, называются «открытым интерфейсом». Части объекта, доступные производным от него объектам, называются «защищенным интерфейсом» объекта. Проектируя программу, обду- майте интерфейсы обоих типов. Завершая проектирование высокоуровневой объектно-ориентированной органи- зации системы, вы будете использовать два вида итерации: высокоуровневую, на- правленную на улучшение организации классов, и итерацию на уровне каждого из определенных классов, направленную на детализацию проекта каждого класса. Определите согласованные абстракции Абстракция позволяет задействовать концепцию, игнорируя ее некоторые детали и работая с разными деталями на разных уровнях. Имея дело с составным объектом, вы имеете дело с абстракцией. Если вы рассматриваете объект как «дом», а не как комбинацию стекла, древесины и гвоздей, вы прибегаете к абстракции. Если вы рас- сматриваете множество домов как «город», вы прибегаете к другой абстракции. Базовые классы представляют собой абстракции, позволяющие концентрироваться на общих атрибутах производных классов и игнорировать детали конкретных классов при работе с базовым классом. Удачный интерфейс класса — это абстрак- ция, позволяющая сосредоточиться на интерфейсе, не беспокоясь о внутренних механизмах работы класса. Интерфейс грамотно спроектированного метода обеспечивает такую же выгоду на более низком уровне детальности, а интерфейс грамотно спроектированного пакета или подсистемы — на более высоком. С точки зрения сложности, главное достоинство абстракции в том, что она позво- ляет игнорировать нерелевантные детали. Большинство объектов реального мира уже является абстракциями некоторого рода. Как я только что сказал, дом — это абстракция окон, дверей, обшивки, электропроводки, водопроводных труб, изо- ляционных материалов и конкретного способа их организации. Дверь же — это абстракция особого вида организации прямоугольного фрагмента некоторого материала, петель и ручки. А дверную ручку можно считать абстракцией конкрет- ного способа упорядочения медных, никелевых или стальных деталей. Мы используем абстракции на каждом шагу. Если б, открывая или закрывая дверь, вы должны были иметь дело с отдельными волокнами древесины, молекулами лака и стали, вы вряд ли смогли бы войти в дом или выйти из него. Абстракция — один из главных способов борьбы со сложностью реального мира (рис. 5-7). Перекрестная ссылка О классах и сокрытии информации см. подраздел «Скрывайте секреты (к вопросу о сокрытии инфор- мации)» раздела 5.3. ГЛАВА 5 Проектирование при конструировании 87 Рис. 5-7. Абстракция позволяет представить сложную концепцию в более простой форме Разработчики ПО иногда создают системы на уровне волокон древесины и молекул лака и стали, из-за чего такие системы становятся слишком сложными и плохо поддаются осмысле- нию. Если программисты не создают более общие абстракции, разработка системы может завершиться неудачей. Благоразумные программисты создают абстракции на уровне интерфейсов ме- тодов, интерфейсов классов и интерфейсов пакетов (иначе говоря, на уровне дверной ручки, уровне двери и на уровне дома), что способствует более быстрому и безопасному программированию. Инкапсулируйте детали реализации Когда абстракция нас покидает, на помощь приходит инкапсуляция. Абстракция говорит: «Вы можете рассмотреть объект с общей точки зрения». Инкапсуляция добавляет: «Более того, вы не можете рассмотреть объект с иной точки зрения». Продолжим нашу аналогию: инкапсуляция позволяет вам смотреть на дом, но не дает подойти достаточно близко, чтобы узнать, из чего сделана дверь. Инкапсуля- ция позволяет вам знать о существовании двери, о том, открыта она или заперта, но при этом вы не можете узнать, из чего она сделана (из дерева, стекловолокна, стали или другого материала), и уж никак не сможете рассмотреть отдельные волокна древесины. Инкапсуляция помогает управлять сложностью, блокируя доступ к ней (рис. 5-8). В подразделе «Хорошая инкапсуляция» раздела 6.2 инкапсуляция рассматривается подробнее в контексте проектирования классов. Рис. 5-8. Инкапсуляция не только представляет сложную концепцию в более простой форме, но и не позволяет взглянуть на какие бы то ни было детали сложной концепции. Что видите, то и получите — и не более того! Перекрестная ссылка Об абст- ракции в контексте проектирова- ния классов см. подраздел «Хоро- шая абстракция» раздела 6.2. 88 ЧАСТЬ II Высококачественный код Используйте наследование, если оно упрощает проектирование При проектировании ПО часто выясняется, что одни объекты аналогичны другим за исключением нескольких различий. Так, при создании системы расчета зарплаты нужно учесть, что одни сотрудники работают полный день, а другие — неполный. В этом случае наборы данных, ассоциированные с сотрудниками обеих категорий, будут различаться лишь несколькими аспектами. Объектно-ориентированный подход позволяет создать общий тип «сотрудник» и определить сотрудников, ра- ботающих полный день, как сотрудников общего типа за исключением нескольких различий. Если операция над объектом «сотрудник» не зависит от его категории, она выполняется так, как если бы объект был сотрудником общего типа. Если же операция зависит от типа сотрудника, она выполняется разными способами. Определение сходств и различий между такими объектами называется «наследова- нием», потому что отдельные типы сотрудников, работающих полный и неполный день, наследуют свойства общего типа «сотрудник». Польза наследования в том, что оно дополняет идею абстракции. Абстракция позволяет представить объекты с разным уровнем детальности. Если помните, на одном уровне мы рассматривали дверь как набор определенных типов моле- кул, на втором — как набор волокон древесины, а на третьем — как что-то, что защищает нас от воров. Древесина имеет определенные свойства — скажем, вы можете распилить ее пилой или склеить столярным клеем, — при этом и плинтусы, и подоконники имеют общие свойства древесины, но вместе с тем и некоторые специфические свойства. Наследование упрощает программирование, позволяя создать универсальные методы для выполнения всего, что основано на общих свойствах дверей, и затем написать специфические методы для выполнения специфических операций над конкретными типами дверей. Некоторые операции, такие как Open() или Close(), будут универсальными для всех дверей: внутренних, входных, стеклянных, сталь- ных — каких угодно. Поддержка языком операций вроде Open() или Close() при отсутствии информации о конкретном типе двери вплоть до периода выполнения называется полиморфизмом. Объектно-ориентированные языки, такие как C++, Java и более поздние версии Microsoft Visual Basic, поддерживают и наследование, и полиморфизм. Наследование — одно из самых мощных средств объектно-ориентированного программирования. При правильном применении оно может принести большую пользу, однако в обратном случае и ущерб будет немалым. Подробнее см. подраз- дел «Наследование (отношение «является»)» раздела 6.3. ГЛАВА 5 Проектирование при конструировании 89 Скрывайте секреты (к вопросу о сокрытии информации) Сокрытие информации — один из основных принципов и структурного, и объек- тно-ориентированного проектирования. В первом случае сокрытие информации лежит в основе идеи «черных ящиков». Во втором оно дает начало концепциям инкапсуляции и модульности и связано с концепцией абстракции. Сокрытие ин- формации — одна из самых конструктивных идей в мире разработки ПО, и сейчас мы рассмотрим ее подробнее. Впервые сокрытие информации было представлено на суд общественности в 1972 г. Дэвидом Парнасом (David Parnas) в статье «On the Criteria to Be Used in Decomposing Systems Into Modules (О критериях, используемых при декомпозиции систем на модули)». С сокрытием информации тесно связана идея «секретов» — аспектов проектирования и реализации, которые разработчик ПО решает скрыть в каком-то месте от остальной части программы. В юбилейном 20-летнем издании книги «Мифический человеко-месяц» Фред Брукс пришел к выводу, что критика сокрытия информации была одной из ошибок, до- пущенных им в первом издании книги. «Парнас был прав в отношении сокрытия информации, а я ошибался», — признал он (Brooks, 1995). Барри Бом сообщил, что сокрытие информации — мощный метод избавления от повторной работы, и указал, что оно особенно эффективно в инкрементных средах с высоким уровнем изменений (Boehm, 1987). В контексте Главного Технического Императива Разработки ПО сокрытие инфор- мации оказывается особенно мощным эвристическим принципом, так как все его аспекты и даже само название подчеркивают сокрытие сложности. Секреты и право на личную жизнь При сокрытии информации каждый класс (пакет, метод) характеризуется аспектами проектирования или конструирования, которые он скрывает от остальных классов. Секретом может быть источник вероятных изменений, формат файла, реализация типа данных или область, изоляция которой требуется для сведения к минимуму вреда от возможных ошибок. Класс должен скрывать эту информацию и защищать свое право на «личную жизнь». Небольшие изменения системы могут влиять на не- сколько методов класса, но не должны распространяться за его интерфейс. Один из важнейших аспектов проектирования класса — при- нятие решения о том, какие свойства сделать доступными вне класса, а какие оставить секретными. Класс может включать 25 методов, предоставляя доступ только к пяти из них и ис- пользуя остальные 20 внутренне. Класс может использовать несколько типов данных, не раскрывая сведений о них. Этот аспект проектирования классов называют «видимостью», так как он определяет, какие свойства класса «видимы» или «доступны» извне. Интерфейс класса должен сообщать как можно меньше о внутренней работе класса. В этом смысле класс во многом похож на айсберг, большая часть которого скрыта под водой (рис. 5-9). Интерфейсы классов должны быть полными и минималь- ными. Скотт Мейерс (Scott Meyers) |