Объектно-ориентированный подход. Объектно_ориентированный_подход. Объектно ориентированный подход Мэтт Вайсфельд 5е международное издание ббк 32. 973. 2018
Скачать 5.43 Mb.
|
65 Обеспечение.минимального.интерфейса.пользователя. . Рис. 2.5. Не.такой.абстрактный.интерфейс ее снова: «Отвезите меня в аэропорт» или «Поверните направо, затем направо, затем налево, затем направо, затем налево»? Очевидно, что первая фраза явля- ется более подходящей. Вы можете сказать ее в любом городе всякий раз, когда садитесь в такси и хотите добраться до аэропорта. Вторая фраза подойдет толь- ко в отдельных случаях. Таким образом, абстрактный интерфейс «Отвезите меня в аэропорт» в целом является отличным вариантом грамотного подхода к объектно-ориентированному проектированию, результат которого окажется пригоден для повторного использования, а его реализация будет разной в Чи- каго, Нью-Йорке и Кливленде. Обеспечение минимального интерфейса пользователя При проектировании класса общее правило заключается в том, чтобы всегда обеспечивать для пользователей как можно меньше информации о внутреннем устройстве этого класса. Чтобы сделать это, придерживайтесь следующих про- стых правил. Предоставляйте пользователям только то, что им обязательно потребуется. По сути, это означает, что у класса должно быть как можно меньше интер- фейсов. Приступая к проектированию класса, начинайте с минимального интерфейса. Проектирование класса итеративно, поэтому вскоре вы обнару- жите, что минимального набора интерфейсов недостаточно. И это будет нормально. Глава.2..Как.мыслить.объектно 66 Лучше в дальнейшем добавить интерфейсы из-за того, что они окажутся действительно нужны пользователям, чем сразу давать этим людям больше интерфейсов, нежели им требуется. Иногда доступ конкретного пользовате- ля к определенным интерфейсам создает проблемы. Например, вам не пона- добится интерфейс, предоставляющий информацию о заработной плате всем пользователям, — такие сведения должны быть доступны только тем, кому их положено знать. Сейчас рассмотрим наш программный пример. Представьте себе пользова- теля, несущего системный блок персонального компьютера без монитора или клавиатуры. Очевидно, что от этого персонального компьютера будет мало толку. Здесь для пользователя просто предусматривается минимальный набор интерфейсов, относящийся к персональному компьютеру. Однако этого минимального набора недостаточно, и сразу же возникает необходи- мость добавить другие интерфейсы. Открытые интерфейсы определяют, что у пользователей имеется доступ. Если вы изначально скроете весь класс от пользователей, сделав интерфей- сы закрытыми, то когда программисты начнут применять этот класс, вам придется сделать определенные методы открытыми, и они, таким образом, станут частью открытого интерфейса. Жизненно важно проектировать классы с точки зрения пользователя, а не с позиции информационных систем. Слишком часто бывает так, что про- ектировщики классов (не говоря уже о программном обеспечении всех про- чих видов) создают тот или иной класс таким образом, чтобы он вписывался в конкретную технологическую модель. Даже если проектировщик станет все делать с точки зрения пользователя, то такой пользователь все равно окажется скорее техническим специалистом, а класс будет проектироваться с таким расчетом, чтобы он работал с технологической точки зрения, а не был удобным в применении пользователями. Занимаясь проектированием класса, перечитывайте соответствующие тре- бования и проектируйте его, общаясь с людьми, которые станут использовать этот класс, а не только с разработчиками. Класс, скорее всего, будет эволю- ционировать, и его потребуется обновить при создании прототипа системы. Определение пользователей Снова обратимся к примеру с такси. Мы уже решили, что пользователи в данном случае — это те люди, которые фактически будут применять соответствующую систему. При этом сам собой напрашивается вопрос: что это за люди? Первым порывом будет сказать, что ими окажутся клиенты. Однако это окажет- ся правильным только примерно наполовину. Хотя клиенты, конечно же, явля- ются пользователями, таксист должен быть способен успешно оказать клиентам 67 Обеспечение.минимального.интерфейса.пользователя. . соответствующую услугу. Другими словами, обеспечение интерфейса, который, несомненно, понравился бы клиенту, например «Отвезите меня в аэропорт бес- платно», не устроит таксиста. Таким образом, для того чтобы создать реалистич- ный и пригодный к использованию интерфейс, следует считать пользователями как клиента, так и таксиста. Коротко говоря, любой объект, который отправляет сообщение другому объ- екту, представляющему такси, считается пользователем (и да, пользовате- ли тоже являются объектами). На рис. 2.6 показано, как таксист оказывает услугу. Рис. 2.6. Оказание.услуги ЗАБЕГАЯ ВПЕРЕД _____________________________________________________________________ Таксист,.скорее.всего,.тоже.будет.объектом. Поведения объектов Определение пользователей — это лишь часть того, что нужно сделать. После того как пользователи будет определены, вам потребуется определить пове- дения объектов. Исходя из точки зрения всех пользователей, начинайте опре- деление назначения каждого объекта и того, что он должен делать, чтобы ра- ботать должным образом. Следует отметить, что многое из того, что будет выбрано первоначально, не приживется в окончательном варианте открытого интерфейса. Нужно определяться с выбором во время сбора требований с при- менением различных методик, например на основе вариантов использова- ния UML. Ограничения, налагаемые средой В книге «Объектно-ориентированное проектирование на Java» Гилберт и Мак- карти отмечают, что среда часто налагает ограничения на возможности объекта. Фактически почти всегда имеют место ограничения, налагаемые средой. Глава.2..Как.мыслить.объектно 68 Компьютерное аппаратное обеспечение может ограничивать функциональность программного обеспечения. Например, система может быть не подключена к сети или в компании может использоваться принтер специфического типа. В примере с такси оно не сможет продолжить путь по дороге, если в соответ- ствующем месте не окажется моста, даже если это будет более короткий путь к аэропорту. Определение открытых интерфейсов После того как будет собрана вся информация о пользователях, поведениях объектов и среде, вам потребуется определить открытые интерфейсы для каж- дого пользовательского объекта. Поэтому подумайте о том, как бы вы исполь- зовали такой объект, как такси. Сесть в такси. Сказать таксисту о том, куда вас отвезти. Заплатить таксисту. Дать таксисту «на чай». Выйти из такси. Что необходимо для того, чтобы использовать такой объект, как такси? Располагать местом, до которого нужно добраться. Подозвать такси. Заплатить таксисту. Сначала вы задумаетесь о том, как объект используется, а не о том, как он соз- дается. Вы, возможно, обнаружите, что объекту требуется больше интерфейсов, например «Положить чемодан в багажник» или «Вступить в пустой разговор с таксистом». На рис. 2.7 показана диаграмма класса, отражающая возможные методы для класса Cabbie Рис. 2.7. Методы.в.классе.Cabbie 69 Обеспечение.минимального.интерфейса.пользователя. . Как и всегда, «шлифовка» финального интерфейса — итеративный процесс. Для каждого интерфейса вам потребуется определить, вносит ли он свой вклад в экс- плуатацию объекта. Если нет, то, возможно, он не нужен. Во многих учебниках по объектно-ориентированному программированию рекомендуется, чтобы каждый интерфейс моделировал только одно поведение. Это возвращает нас к вопросу о том, какого абстрагирования мы хотим добиться при проектирова- нии. Если у нас будет интерфейс с именем enterTaxi() , то, безусловно, нам не потребуется, чтобы в нем содержалась логика «заплатить таксисту». Если все так и будет, то наша конструкция окажется несколько нелогичной. Кроме того, фактически пользователи класса никак не смогут узнать, что нужно сделать для того, чтобы «заплатить таксисту». Определение реализации Выбрав открытые интерфейсы, вы должны будете определить реализацию. По- сле того как вы спроектируете класс, а все методы, необходимые для эксплуа- тации класса, окажутся на своих местах, потребуется заставить этот класс ра- ботать. Технически все, что не является открытым интерфейсом, можно считать реа- лизацией. Это означает, что пользователи никогда не увидят каких-либо мето- дов, считающихся частью реализации, в том числе подпись конкретного метода (которая включает имя метода и список параметров), а также фактический код внутри этого метода. Можно располагать закрытым методом, который применяется внутри класса. Любой закрытый метод считается частью реализации при условии, что пользо- ватели никогда не увидят его и, таким образом, не будут иметь к нему доступ. Например, в классе может содержаться метод changePassword() ; однако этот же класс может включать закрытый метод для шифрования пароля. Этот метод был бы скрыт от пользователей и вызывался бы только изнутри метода changePassword() Реализация полностью скрыта от пользователей. Код внутри открытых методов является частью реализации, поскольку пользователи не могут увидеть его (они должны видеть только вызывающую структуру интерфейса, а не код, что на- ходится в нем). Это означает, что теоретически все считающееся реализацией может изменять- ся, не влияя на то, как пользователи взаимодействуют с классом. При этом, естественно, предполагается, что реализация дает ответы, ожидаемые пользо- вателями. Хотя интерфейс представляет то, как пользователи видят определенный объект, в действительности реализация — это основная составляющая этого объекта. Реализация содержит код, который описывает состояние объекта. Глава.2..Как.мыслить.объектно 70 Резюме В этой главе мы исследовали три области, которые помогут вам начать мыслить объектно-ориентированным образом. Помните, что не существует какого-либо постоянного списка вопросов, касающихся объектно-ориентированного мыш- ления. Работа в объектно-ориентированном стиле больше представляет собой искусство, нежели науку. Попробуйте сами придумать, как можно было бы описать объектно-ориентированное мышление. В главе 3 мы поговорим о жизненном цикле объектов: как они «рождаются», «живут» и «умирают». Пока объект «жив», он может переходить из одного со- стояния в другое, и этих состояний много. Например, объект DataBaseReader будет пребывать в одном состоянии, если база данных окажется открыта, и в дру- гом состоянии — если будет закрыта. Способ, которым все это будет представ- лено, зависит от того, как окажется спроектирован соответствующий класс. Ссылки Мартин Фаулер, «UML. Основы» (UML Distilled). — 3-е изд. — Бостон, штат Массачусетс: Addison-Wesley Professional, 2003. Стивен Гилберт и Билл Маккарти, «Объектно-ориентированное проектиро- вание на Java» (Object-Oriented Design in Java). — Беркли, штат Калифорния: The Waite Group Press (Pearson Education), 1998. Скотт Майерс, «Эффективное использование C++» (Effective C++). — 3-е изд. Addison-Wesley Professional, 2005. Глава 3 ПРОЧИЕ ОБЪЕКТНО- ОРИЕНТИРОВАННЫЕ КОНЦЕПЦИИ В главах 1 и 2 мы рассмотрели основы объектно-ориентированных концепций. Прежде чем мы приступим к исследованию более деликатных задач проекти- рования, касающихся создания объектно-ориентированных систем, нам необ- ходимо рассмотреть такие более продвинутые объектно-ориентированные концепции, как конструкторы, перегрузка операторов и множественное насле- дование. Мы также поговорим о методиках обработки ошибок и важности знания того, как область видимости применима в сфере объектно-ориентиро- ванного проектирования. Некоторые из этих концепций могут не быть жизненно важными для понимания объектно-ориентированного проектирования на более высоком уровне, однако они необходимы любому, кто занят в проектировании и реализации той или иной объектно-ориентированной системы. Конструкторы Конструкторы, возможно, не станут новинкой для тех, кто занимается струк- турным программированием. Хотя конструкторы обычно не используются в не объектно-ориентированных языках, таких как COBOL, C и Basic, структура, которая является частью C/C ++, действительно включает конструкторы. В пер- вых двух главах мы упоминали об этих специальных методах, которые исполь- зуются для конструирования объектов. В некоторых объектно-ориентированных языках, таких как Java и C #, конструкторы являются методами, которые имеют то же имя, что и класс. В Visual Basic .NET используется обозначение New , а в Swift — init . Как обычно, мы сосредоточимся на концепциях конструкторов, а не на конкретном синтаксисе всех языков. Давайте посмотрим на некоторый код Java, который реализует конструктор. Глава.3..Прочие.объектно-ориентированные.концепции 72 Например, конструктор для класса Cabbie , рассмотренного нами в главе 2, вы- глядел бы так: public Cabbie(){ /* код для конструирования объекта */ } Компилятор увидит, что имя метода идентично имени класса, и будет считать этот метод конструктором. ПРЕДОСТЕРЕЖЕНИЕ __________________________________________________________________ Следует.отметить,.что.в.этом.Java-коде.(как.и.в.написанном.на.C#.и.C++).у.конструк- тора.нет.возвращаемого.значения..Если.вы.предусмотрите.возвращаемое.значение,. то.компилятор.не.будет.считать.метод.конструктором. Например, если вы включите приведенный далее код в класс, то компилятор не будет считать метод конструктором, поскольку у того есть возвращаемое зна- чение, которым в данном случае является целочисленная величина: public int Cabbie(){ /* код для конструирования объекта */ } Это синтаксическое требование может создавать проблемы, поскольку код пройдет компиляцию, но не будет вести себя так, как это ожидается. Когда осуществляется вызов конструктора? При создании нового объекта в первую очередь выполняется вызов конструк- тора. Взгляните на приведенный далее код: Cabbie myCabbie = new Cabbie(); Ключевое слово new обеспечит создание класса Cabbie и таким образом приведет к выделению требуемой памяти. Затем произойдет вызов конструктора как такового с передачей аргументов в списке параметров. Конструктор обеспечи- вает для разработчиков возможность следить за соответствующей инициализа- цией. Таким образом, код new Cabbie() создаст экземпляр объекта Cabbie и вызовет метод Cabbie , который является конструктором. Что находится внутри конструктора? Пожалуй, наиболее важной функцией конструктора является инициализация выделенной памяти при обнаружении ключевого слова new . Коротко говоря, 73 Конструкторы. . код, заключенный внутри конструктора, должен задать для нового созданного объекта его начальное, стабильное, надежное состояние. Например, если у вас есть объект Counter с атрибутом count , то вам потребует- ся задать для count значение 0 в конструкторе: count = 0; ИНИЦИАЛИЗАЦИЯ АТРИБУТОВ _______________________________________________________ Функция,.называемая.служебной.(или.инициализацией),.часто.используется.в.целях. инициализации.при.структурном.программировании..Инициализация.атрибутов. представляет.собой.общую.операцию,.выполняемую.в.конструкторе..Но.все.же.не. стоит.полагаться.на.системные.настройки.по.умолчанию. Конструктор по умолчанию Если вы напишете класс и не добавите в него конструктор, то этот класс все равно пройдет компиляцию и вы сможете его использовать. Если в классе не предусмотрено явного конструктора, то будет обеспечен конструктор по умол- чанию. Важно понимать, что в наличии всегда будет как минимум один кон- структор вне зависимости от того, напишете ли вы его сами или нет. Если вы не предусмотрите конструктор, то система обеспечит за вас конструктор по умол- чанию. Помимо создания объекта как такового, единственное действие, предприни- маемое конструктором по умолчанию, — это вызов конструктора его супер- класса. Во многих случаях суперкласс будет частью фреймворка языка, как класс Object в Java. Например, если окажется, что для класса Cabbie не пред- усмотрено конструктора, то будет добавлен приведенный далее конструктор по умолчанию: public Cabbie(){ super(); } Если бы потребовалось декомпилировать байт-код, выдаваемый компилятором, то вы увидели бы этот код. Его в действительности вставляет компилятор. В данном случае если Cabbie не наследует явным образом от другого класса, то класс Object будет родительским. Пожалуй, в некоторых ситуациях конструк- тора по умолчанию окажется достаточно; однако в большинстве случаев по- требуется инициализация памяти. Независимо от ситуации, правильная мето- дика программирования — всегда включать в класс минимум один конструктор. Если в классе содержатся атрибуты, то желательна их инициализация. Кроме того, инициализация переменных всегда будет правильной методикой при на- писании кода, будь он объектно-ориентированным или нет. Глава.3..Прочие.объектно-ориентированные.концепции 74 ОБЕСПЕЧЕНИЕ КОНСТРУКТОРА _______________________________________________________ Общее.правило.заключается.в.том,.что.вы.должны.всегда.обеспечивать.конструктор,. даже.если.не.планируете.что-либо.делать.внутри.него..Вы.можете.предусмотреть. конструктор,.в.котором.ничего.нет,.а.затем.добавить.в.него.что-то..Хотя.технически. ничего.плохого.в.использовании.конструктора.по.умолчанию,.обеспечиваемого. компилятором,.нет,.в.целях.документирования.и.сопровождения.никогда.не.будет. лишним.знать,.как.именно.выглядит.код. Неудивительно, что сопровождение становится здесь проблемой. Если вы за- висите от конструктора по умолчанию, а при последующем сопровождении будет добавлен еще один конструктор, то конструктор по умолчанию больше не будет обеспечиваться. Это может действительно привести к необходимости правки кода, который предусматривал наличие конструктора по умолчанию. Нужно навсегда запомнить, что конструктор по умолчанию добавляется, толь- ко если вы сами не включите никаких конструкторов. Как только вы предусмо- трите хотя бы один конструктор, конструктор по умолчанию больше не будет применяться. |