Объектно-ориентированный подход. Объектно_ориентированный_подход. Объектно ориентированный подход Мэтт Вайсфельд 5е международное издание ббк 32. 973. 2018
Скачать 5.43 Mb.
|
85 Важность.области.видимости. . public method2() { } } Метод method1 содержит локальную переменную с именем count . Эта целочис- ленная переменная доступна только внутри метода method1 . Метод method2 даже понятия не имеет, что целочисленная переменная count существует. На этом этапе мы познакомимся с очень важной концепцией — областью види- мости. Атрибуты (и методы) существуют в определенной области видимости. В данном случае целочисленная переменная count существует в области види- мости method1 . При использовании Java, C#, C++ и Objective-C область види- мости обозначается фигурными скобками ( {} ). В классе Number имеется не- сколько возможных областей видимости — начните сопоставлять их с соответствующими фигурными скобками. У класса как такового есть своя область видимости. Каждый экземпляр класса (то есть каждый объект) обладает собственной областью видимости. У методов method1 и method2 тоже имеются собственные области видимости. Поскольку целочисленная переменная count заключена в фигурные скобки method1 , при вызове этого метода создается ее копия. Когда выполнение method1 завершает- ся, копия count удаляется. Чтобы стало еще веселее, взгляните на этот код: public class Number { public method1() { int count; } public method2() { int count; } } В этом примере есть две копии целочисленной переменной count в соответству- ющем классе. Помните, что method1 и method2 обладают собственными областя- ми видимости. Таким образом, компилятор будет знать, к какой копии count нужно обращаться, просто выяснив, в каком методе она располагается. Вы мо- жете представлять себе это следующим образом: method1.count; method2.count; Глава.3..Прочие.объектно-ориентированные.концепции 86 Что касается компилятора, то различить два атрибута ему не составит труда, даже если у них одинаковые имена. Это почти аналогично ситуации, когда у двух человек одинаковая фамилия, но, зная их имена, вы понимаете, что это две от- дельные личности. Атрибуты объектов Во многих ситуациях при проектировании атрибут должен совместно исполь- зоваться несколькими методами в одном и том же объекте. На рис. 3.6, к при- меру, показано, что три объекта сконструированы на основе одного класса. Взгляните на приведенный далее код: public class Number { int count; // доступ к переменной имеется у обоих: method1 и method2 public method1() { count = 1; } public method2() { count = 2; } } Рис. 3.6. Атрибуты.объектов 87 Важность.области.видимости. . Обратите здесь внимание на то, что атрибут класса count объявлен вне области видимости как method1 , так и method2 . Однако он находится в области видимо- сти класса. Таким образом, атрибут count доступен для обоих method1 и method2 (по сути, у всех методов в классе имеется доступ к этому атрибуту). Следует отметить, что в коде, касающемся обоих методов, count присваивается опреде- ленное значение. На весь объект приходится только одна копия count , поэтому оба присваивания влияют на одну и ту же копию в памяти. Однако эта копия count не используется совместно разными объектами. В качестве наглядного примера создадим три копии класса Number : Number number1 = new Number(); Number number2 = new Number(); Number number3 = new Number(); Каждый из этих объектов — number1 , number2 и number3 — конструируется от- дельно, и ему выделяются его собственные ресурсы. Имеется три отдельных экземпляра целочисленной переменной count . Изменение значения атрибута count объекта number1 никоим образом не повлияет на копию count в объекте number2 или number3 . В данном случае целочисленная переменная count явля- ется атрибутом объекта. Вы можете поэкспериментировать с областью видимости. Взгляните на при- веденный далее код: public class Number { int count; public method1() { int count; } public method2() { int count; } } В данной ситуации в трех полностью отдельных областях памяти для каждого объекта имеется имя count . Объекту принадлежит одна копия, а у method1() и method2() тоже есть по соответствующей копии. Для доступа к объектной переменной изнутри одного из методов, например method1() , вы можете использовать указатель с именем this из основанных на C языков программирования: public method1() { int count; this.count = 1; } Глава.3..Прочие.объектно-ориентированные.концепции 88 Обратите внимание, что часть кода выглядит немного странно: this.count = 1; Выбор слова this в качестве ключевого, возможно, является неудачным. Одна- ко мы должны смириться с ним. Ключевое слово this нацеливает компилятор на доступ к объектной переменной count , а не к локальным переменным в теле методов. ПРИМЕЧАНИЕ _________________________________________________________________________ Ключевое.слово. this .—.это.ссылка.на.текущий.объект. Атрибуты классов Как уже отмечалось ранее, атрибуты могут совместно использоваться двумя и более объектами. При написании кода на Java, C#, C++ или Objective-C для этого потребуется сделать атрибут статическим: public class Number { static int count; public method1() { } } Из-за объявления атрибута count статическим ему выделяется один блок памя- ти для всех объектов, экземпляры которых будут созданы на основе соответ- ствующего класса. Таким образом, все объекты класса будут применять одну и ту же область памяти для count . По сути, у каждого класса будет единственная копия, совместно используемая всеми объектами этого класса (рис. 3.7). Это настолько близко к глобальным данным, насколько представляется возможным при объектно-ориентированном проектировании. Есть много допустимых вариантов использования атрибутов классов; тем не менее вам следует знать о возможных проблемах синхронизации. Создадим два экземпляра объекта Count : Count Count1 = new Count(); Count Count2 = new Count(); Теперь представим, что объект Count1 оживленно занимается своими делами, используя при этом count как средство для подсчета пикселов на мониторе компьютера. Это не будет проблемой до тех пор, пока объект Count2 не решит использовать атрибут count для подсчета овец. В тот момент, когда Count2 за- 89 Перегрузка.операторов. . пишет данные о первой овце, информация, сохраненная Count1 , будет потеряна. На практике статический метод не должен часто применяться. Только если вы уверены в том, что он необходим в проектировании. Рис. 3.7. Атрибуты.классов Перегрузка операторов Некоторые объектно-ориентированные языки программирования позволяют выполнять перегрузку операторов. Пример одного из таких языков программи- рования — C++. Перегрузка операторов дает возможность изменять их смысл. Например, когда большинство людей видят знак плюс, они предполагают, что он означает операцию сложения. Если вы увидите уравнение X = 5 + 6; то решите, что X будет содержать значение 11 . И в этом случае вы окажетесь правы. Однако иногда знак плюс может означать кое-что другое. Как, например, в сле- дующем коде: String firstName = "Joe", lastName = "Smith"; String Name = firstName + " " + lastName; Глава.3..Прочие.объектно-ориентированные.концепции 90 Вы ожидали бы, что Name будет содержать Joe Smith . Знак плюс здесь был пере- гружен для выполнения конкатенации строк. КОНКАТЕНАЦИЯ СТРОК _______________________________________________________________ Конкатенация строк.происходит,.когда.две.отдельные.строки.объединяют,.чтобы. создать.новую,.единую.строку. В контексте строк знак плюс означает не операцию сложения целых чисел или чисел с плавающей точкой, а конкатенацию строк. А как насчет сложения матриц? Мы могли бы написать такой код: Matrix a, b, c; c = a + b; Таким образом, знак плюс обеспечивает здесь сложение матриц, а не сложение целых чисел или чисел с плавающей точкой. Перегрузка — это мощный механизм. Однако он может откровенно сбивать с толку тех, кто читает и сопровождает код. Фактически разработчики могут сами себя запутать. Доводя это до крайности, отмечу, что можно было бы из- менить операцию сложения на операцию вычитания. А почему бы и нет? Пере- грузка операторов позволяет нам изменять их смысл. Таким образом, если внести изменение, благодаря которому знак плюс будет обеспечивать вычитание, то результатом выполнения приведенного далее кода станет значение X , рав- ное –1 : x = 5 + 6; Более поздние объектно-ориентированные языки программирования, например Java и .NET, не позволяют выполнять перегрузку операторов. Несмотря на это, они сами перегружают знак плюс для конкатенации строк, но дело этим и ограничивается. Люди, разрабатывавшие Java, должно быть, реши- ли, что перегрузка операторов — овчинка, не стоящая выделки. Если вам по- требуется выполнять перегрузку операторов при программировании на C++, то позаботьтесь о соответствующем документировании и комментировании, чтобы люди, которые будут использовать ваш класс, не запутались. Множественное наследование Намного подробнее о наследовании мы поговорим в главе 7. А эта глава хорошо подходит для того, чтобы начать рассмотрение множественного наследования, которое представляет собой один из наиболее важных и сложных аспектов про- ектирования классов. 91 Операции.с.объектами. . Как видно из названия, множественное наследование позволяет тому или иному классу наследовать более чем от одного класса. Это кажется отличной идеей. Объекты должны моделировать реальный мир, не так ли? При этом существу- ет много реальных примеров множественного наследования. Родители — хоро- ший пример такого наследования. У каждого ребенка есть два родителя — таков порядок. Поэтому ясно, что вы можете проектировать классы с применением множественного наследования. Для этого вы сможете использовать некоторые объектно-ориентированные языки программирования, например C++. Однако эта ситуация подпадает под категорию тех, что схожи с перегрузкой операторов. Множественное наследование — это очень мощная методика, и фак- тически некоторые проблемы довольно трудно решить без нее. Множественное наследование даже позволяет изящно решать отдельные проблемы. Но оно может значительно усложнить систему как для программистов, так и для раз- работчиков компиляторов. Современная концепция наследования предполагает наследование атрибутов от одного родительского класса (простое наследование). Хотя и возможно при- менение множественных интерфейсов или протоколов, это нельзя назвать под- линным множественным наследованием. ПОВЕДЕНЧЕСКОЕ НАСЛЕДОВАНИЕ И НАСЛЕДОВАНИЕ РЕАЛИЗАЦИИ _______________ Интерфейсы.—.механизм.для.поведенческого.наследования,.в.то.время.как.абстракт- ные.классы.используются.для.наследования.реализации..Основной.момент.заключа- ется.в.том,.что.языковые.конструкции.интерфейсов.обеспечивают.поведенческие. интерфейсы,.но.не.реализацию,.тогда.как.абстрактные.классы.могут.обеспечивать.как. интерфейсы,.так.и.реализацию..Более.подробно.эта.тема.рассматривается.в.главе.8. Операции с объектами Некоторые самые простые операции в программировании усложняются, когда вы имеете дело с комплексными структурами данных и объектами. Например, если вам потребуется скопировать или сравнить примитивные типы данных, то соответствующий процесс будет довольно простым. Однако копирование и срав- нение объектов окажется уже не настолько простым. В книге «Эффективное использование C++» (Effective C++) Скотт Майерс посвятил целый раздел копированию и присваиванию объектов. КЛАССЫ И ССЫЛКИ ___________________________________________________________________ Проблема.с.комплексными.структурами.данных.и.объектами.состоит.в.том,.что.они. могут.содержать.ссылки..Просто.скопировав.ссылку,.вы.не.скопируете.структуры. данных.или.объект,.к.которому.она.ведет..В.том.же.духе.при.сравнении.объектов,. просто.сопоставив.один.указатель.с.другим,.вы.сравните.только.ссылки,.а.не.то,.на. что.они.указывают. Глава.3..Прочие.объектно-ориентированные.концепции 92 Проблемы возникают при сравнении и копировании объектов. В частности, вопрос сводится к тому, станете ли вы следовать указателям. Независимо от этого должен быть способ скопировать объект. Опять-таки, эта операция не будет такой простой, какой она может показаться. Поскольку объекты могут содержать ссылки, потребуется придерживаться соответствующих деревьев ссылок для того, чтобы выполнить правильное копирование (если вам действи- тельно понадобится выполнить глубокое копирование). ГЛУБОКОЕ ИЛИ ПОВЕРХНОСТНОЕ КОПИРОВАНИЕ? __________________________________ Глубокое копирование.происходит,.когда.вы.следуете.всем.ссылкам,.а.новые.копии. создаются.для.всех.объектов,.на.которые.имеются.ссылки..В.глубокое.копирование. может.вовлекаться.много.уровней..Если.у.вас.есть.объекты.со.ссылками.на.множе- ство.объектов,.которые,.в.свою.очередь,.могут.содержать.ссылки.на.еще.большее. количество.объектов,.то.сама.копия.может.требовать.значительных.«накладных. расходов»..При.поверхностном.копировании.просто.будет.сделана.копия.ссылки.без. следования.уровням..Гилберт.и.Маккарти.хорошо.пояснили.то,.что.представляют. собой.поверхностная.и.глубокая.иерархии,.в.книге.«Объектно-ориентированное. проектирование.на.Java». В качестве наглядного примера взгляните на рис. 3.8, где показано, что если сделать простую копию объекта (называемую побитовой), то будут скопирова- ны только ссылки и ни один из фактических объектов. Таким образом, оба Рис. 3.8. Следование.объектным.ссылкам 93 Ссылки. . объекта (оригинал и копия) будут ссылаться (указывать) на одни и те же объ- екты. Чтобы выполнить полное копирование, при котором будут скопированы все ссылочные объекты, вам потребуется написать код для создания всех подобъе ктов. Подобная проблема также проявляется при сравнении объектов. Как и функция копирования, эта операция не будет такой простой, какой она может показать- ся. Поскольку объекты содержат ссылки, потребуется придерживаться соот- ветствующих деревьев ссылок для того, чтобы правильно сравнить эти объекты. В большинстве случаев языки программирования по умолчанию обеспечивают механизм для сравнения объектов. Но, как это обычно бывает, не следует по- лагаться на такой механизм. При проектировании класса вы должны рассмотреть возможность обеспечения в нем функции сравнения, которая будет работать так, как вам необходимо. Резюме В этой главе мы рассмотрели несколько продвинутых объектно-ориентирован- ных концепций, которые, возможно, и не имеют жизненно важного значения для общего понимания остальных объектно-ориентированных концепций, но крайне необходимы для решения объектно-ориентированных задач более вы- сокого уровня, таких, например, как проектирование классов. В главе 4 мы приступим к рассмотрению того, как проектируются и создаются классы. Ссылки Стивен Гилберт и Билл Маккарти, «Объектно-ориентированное проектиро- вание на Java» (Object-Oriented Design in Java). — Беркли, штат Калифорния: The Waite Group Press (Pearson Education), 1998. Скотт Майерс, «Эффективное использование C++» (Effective C++). — 3-е изд. — Бостон, штат Массачусетс: Addison-Wesley Professional, 2005. Пол Тима, Габриэл Торок и Трой Даунинг, «Учебник по Java для начинаю- щих» (Java Primer Plus). — Беркли, штат Калифорния: The Waite Group, 1996. Глава 4 АНАТОМИЯ КЛАССА В предыдущих главах мы рассмотрели основные объектно-ориентированные концепции и выяснили разницу между интерфейсом и реализацией. Неважно, насколько хорошо вы продумаете, что должно быть частью интерфейса, а что — частью реализации, самое важное всегда в итоге будет сводиться к тому, насколь- ко полезным является определенный класс и как он взаимодействует с другими классами. Никогда не проектируйте класс «в вакууме». Другими словами, один класс в поле не воин. При создании экземпляров объектов они почти всегда вза- имодействуют с другими объектами. Тот или иной объект также может быть ча- стью другого объекта либо частью иерархии наследования. В этой главе мы рассмотрим простой класс, разобрав его по частям. Кроме того, я буду приводить руководящие указания, которые вам следует принимать во внимание при проектировании классов. Мы продолжим использовать пример с таксистом, описанным в главе 2. В каждом из следующих разделов рассматривается определенная часть класса. Хотя не все компоненты необходимы в каждом классе, важно понимать то, как классы проектируются и конструируются. ПРИМЕЧАНИЕ _________________________________________________________________________ Рассматриваемый.здесь.класс.призван.послужить.лишь.наглядным.примером..Не- которые.методы.не.воплощены.в.жизнь.(то.есть.их.реализация.отсутствует).и.просто. представляют.интерфейс.—.главным.образом.с.целью.подчеркнуть.то,.что.интерфейс. является.«центром».исходной.конструкции. Имя класса Имя класса важно по нескольким причинам. Вполне понятная из них заключа- ется в том, что имя идентифицирует класс как таковой. Помимо того, чтобы просто идентифицировать класс, имя должно описательным. Выбор имени важен, ведь оно обеспечивает информацию о том, что класс делает и как он взаимодействует в рамках более крупных систем. 95 Комментарии. . Имя также важно, если принять во внимание ограничения используемого язы- ка программирования. Например, в Java имя открытого класса должно быть аналогично файловому имени. Если эти имена не совпадут, то приложение не получится скомпилировать. На рис. 4.1 показан класс, который мы будем исследовать. Имя этого класса, понятное и простое, звучит как «Cabbie» и указывается после ключевого слова class : public class Cabbie { } ИСПОЛЬЗОВАНИЕ JAVA-СИНТАКСИСА ________________________________________________ Помните,.что.в.этой.книге.мы.используем.Java-синтаксис..Он.будет.в.чем-то.похожим,. но.все.равно.в.разной.мере.отличаться.в.других.языках.объектно-ориентированно- го.программирования. Имя класса Cabbie будет использоваться каждый раз при создании экземпляра этого класса. |