Объектно-ориентированный подход. Объектно_ориентированный_подход. Объектно ориентированный подход Мэтт Вайсфельд 5е международное издание ббк 32. 973. 2018
Скачать 5.43 Mb.
|
Комментарии Независимо от используемого синтаксиса комментариев, они жизненно важны для понимания функции классов. В Java и прочих подобных ему языках рас- пространены комментарии двух типов. ДОПОЛНИТЕЛЬНЫЙ ТИП КОММЕНТАРИЕВ В JAVA И C# _______________________________ В.Java.и.C#.имеется.три.типа.комментариев..В.Java.третий.тип.комментариев. ( /** */ ).относится.к.форме.документирования,.предусматриваемой.этим.языком. программирования..Этот.тип.комментариев.не.рассматривается.в.данной.книге.. В.C#.синтаксис.для.комментария.в.документации.—. /// ,..почти.такой.же.применя- ется.в.документации.Javadoc.—. /** */ Первый комментарий оформлен в старомодном стиле языка программирования C и предполагает использование /* (слеш — звездочка) для открытия коммен- тария и */ (звездочка — слеш) для закрытия комментария. Комментарий этого типа может растягиваться более чем на одну строку, и важно не забывать ис- пользовать пару открывающих и закрывающих символов в каждом таком ком- ментарии. Если вы не укажете пару закрывающих символов комментария ( */ ), то какая-то часть вашего кода может оказаться помеченной как комментарий и игнорироваться компилятором. Вот пример комментария этого типа, исполь- зованного для класса Cabbie : Глава.4..Анатомия.класса 96 /* Этот класс определяет Cabbie и присваивает Cab */ Рис. 4.1. Класс,.используемый.в.качестве.примера 97 Атрибуты. . Второй тип комментария предполагает использование // (слеш — слеш). При этом все, что располагается после двойного слеша до конца строки, считается комментарием. Комментарий такого типа размещается только на одной строке, поэтому вам не нужно указывать закрывающий символ комментария. Вот при- мер комментария этого типа, использованного для класса Cabbie : // Атрибут name, относящийся к Cabbie Атрибуты Атрибуты представляют состояние определенного объекта, поскольку в них содержится информация об этом объекте. В нашем случае класс Cabbie вклю- чает атрибуты companyName и name , содержащие соответствующие значения, а также Cab , присвоенный Cabbie . Например, первый атрибут с именем companyName хранит следующее значение: private static String companyName = "Blue Cab Company"; Обратите внимание на два ключевых слова: private и static . Ключевое слово private означает, что доступ к методу или переменной возможен только внутри объявляемого объекта. СОКРЫТИЕ КАК МОЖНО БОЛЬШЕГО КОЛИЧЕСТВА ДАННЫХ __________________________ Все.атрибуты.в.этом.примере.являются.закрытыми..Это.соответствует.принципу. проектирования,.согласно.которому.интерфейс.следует.проектировать.таким.об- разом,.чтобы.он.получился.самым.минимальным.из.возможных..Единственный. способ.доступа.к.этим.атрибутам.—.воспользоваться.методом,.обеспечиваемым. интерфейсами.(на.эту.тему.мы.поговорим.позднее.в.текущей.главе). Ключевое слово static означает, что на все объекты, экземпляры которых будут созданы на основе соответствующего класса, будет приходиться только одна копия этого атрибута. По сути, это атрибут класса (см. главу 3, где атрибуты классов рассматриваются более подробно). Таким образом, даже если на осно- ве класса Cabbie будут созданы экземпляры 500 объектов, в памяти окажется только одна копия атрибута companyName (рис. 4.2). Второй атрибут с именем name представляет собой строку, содержащую соот- ветствующее значение Cabbie : private String name; Этот атрибут тоже является закрытым, поэтому у других объектов не будет прямого доступа к нему. Им потребуется использовать методы интерфейсов. Глава.4..Анатомия.класса 98 Рис. 4.2. Выделение.памяти.для.объектов Атрибут myCab — это ссылка на другой объект. Класс с именем Cab содержит информацию о такси вроде его регистрационного номера и журнала техниче- ского обслуживания: private Cab myCab; ПЕРЕДАЧА ССЫЛКИ ___________________________________________________________________ Объект.Cab.наверняка.был.создан.другим.объектом..Таким.образом,.объектная. ссылка.была.бы.передана.объекту.Cabbie..Однако.в.рассматриваемом.нами.при- мере.Cab.был.создан.внутри.объекта.Cabbie..В.результате.нас.не.очень.интересует. внутреннее.устройство.объекта.Cab. Следует отметить, что на этом этапе была создана только ссылка на объект Cab ; это определение не привело к выделению памяти. Конструкторы Класс Cabbie содержит два конструктора. Мы знаем, что это именно конструк- торы, поскольку у них то же имя, что и у соответствующего класса, — Cabbie Первый конструктор — это конструктор по умолчанию: public Cabbie() { name = null; 99 Конструкторы. . myCab = null; } Технически это не конструктор по умолчанию, обеспечиваемый системой. На- помним, что компилятор обеспечит конструктор по умолчанию, если вы не преду смотрите конструктор для класса. Здесь он называется конструктором по умолчанию потому, что является конструктором без аргументов, который в действительности переопределяет конструктор по умолчанию компилятора. Если вы предусмотрите конструктор с аргументами, то система не станет обе- спечивать конструктор по умолчанию. Хотя это может показаться запутанным, правило гласит, что конструктор по умолчанию компилятора добавляется, только если вы не предусмотрите в своем коде никаких конструкторов. БЕЗ КОНСТРУКТОРА __________________________________________________________________ Написание.кода.без.собственного.конструктора.вызовет.работу.конструктора.по. умолчанию..Использование.же.конструктора.по.умолчанию.чревато.головной.болью. в.дальнейшем.при.сопровождении.кода..Если.код.опирается.на.конструктор.по. умолчанию,.но.позже.такой.конструктор.будет.заменен,.нужного.конструктора.не. окажется. В этом конструкторе атрибутам name и myCab присвоено значение null : name = null; myCab = null; ПУСТОЕ ЗНАЧЕНИЕ NULL ______________________________________________________________ Во.многих.языках.программирования.значение. null .представляет.собой.пустое. значение..Это.может.показаться.эзотерическим,.однако.присвоение.атрибутам. значения. null —.рациональная.методика.программирования..Проверка.той.или. иной.переменной.на.предмет. null .позволяет.выяснить,.было.ли.значение.должным. образом.инициализировано..Например,.вам.может.понадобиться.объявить.атрибут,. который.позднее.потребует.от.пользователя.ввести.данные..Таким.образом,.вы. сможете.инициализировать.атрибут.значением. null .до.того,.как.пользователю. представится.возможность.ввести.данные..Присвоив.атрибуту. null .(что.является. допустимым.условием),.позднее.вы.сможете.проверить,.было.ли.задано.для.него. надлежащее.значение..Следует.отметить,.что.в.некоторых.языках.программирования. нельзя.присваивать.значение. null .атрибутам.и.переменным.типа. String ..Например,. при.использовании..NET.придется.указывать. name = string.empty ;. Как мы уже знаем, инициализация атрибутов в конструкторах — это всегда хорошая идея. В том же духе правильной методикой программирования будет последующая проверка значения атрибута с целью выяснить, равно ли оно null Благодаря этому вы сможете избежать головной боли в будущем, если для Глава.4..Анатомия.класса 100 атрибута или объекта будет задано ненадлежащее значение. Например, если вы используете ссылку myCab до того, как ей будет присвоен реальный объект, то, скорее всего, возникнет проблема. Если вы зададите для ссылки myCab значение null в конструкторе, то позднее, когда попытаетесь использовать ее, вы сможе- те проверить, по-прежнему ли она имеет значение null . Может быть сгенери- ровано исключение, если вы станете обращаться с неинициализированной ссылкой так, будто она была инициализирована надлежащим образом. Рассмотрим еще один пример: если у вас имеется класс Employee , содержащий атрибут spouse (супруг(а), возможно, для страховки), то вам лучше предусмо- треть все на случай, если тот или иной работник окажется не состоящим в бра- ке. Изначально присвоив атрибуту значение null , вы сможете позднее проверить соответствующий статус. Второй конструктор дает пользователю класса возможность инициализировать атрибуты name и myCab : public Cabbie(String iName, String serialNumber) { name = iName; myCab = new Cab(serialNumber); } В данном случае пользователь указал бы две строки в списке параметров кон- структора для того, чтобы инициализировать атрибуты надлежащим обра- зом. Обратите внимание, что в этом конструкторе создается экземпляр объ- екта myCab : myCab = new Cab(serialNumber); В результате выполнения этой строки кода для объекта Cab будет выделена память. На рис. 4.3 показано, как на новый экземпляр объекта Cab ссылается атрибут myCab . Благодаря применению двух конструкторов в этом примере де- монстрируется перегрузка методов. Обратите внимание, что все конструкторы определены как public . В этом есть смысл, поскольку в данном случае ясно, что конструкторы являются частью интерфейса класса. Если бы конструкторы были закрытыми, то другие объекты не имели бы к ним доступа и, таким образом, не смогли бы создавать экземпляры объекта Cab НЕСКОЛЬКО КОНСТРУКТОРОВ _______________________________________________________ Стоит.отметить,.что.в.наши.дни.задействование.более.одного.конструктора.счита- ется.дурной.практикой..Поскольку.преобладают.IoC-контейнеры.и.им.подобные,.оно. не.поощряется,.и.даже.не.поддерживается,.во.многих.фреймворках.без.особой. конфигурации. 101 Методы.доступа. . Рис. 4.3. Объект.Cabbie,.ссылающийся.на.объект.Cab Методы доступа В большинстве, если не во всех примерах, приведенных в этой книге, атрибуты определяются как private , из-за чего у любых других объектов нет прямого доступа к этим атрибутам. Было бы глупо создавать объект в изоляции, которая не позволит ему взаимодействовать с другими объектами, — мы ведь хотим, чтобы он мог делиться с ними соответствующей информацией. Есть ли необхо- димость инспектировать и иногда изменять значения атрибутов других классов? Ответом на этот вопрос будет, конечно же, «да». Бывает много ситуаций, когда тому или иному объекту требуется доступ к атрибутам другого объекта; однако это необязательно должен быть прямой доступ. Класс должен очень хорошо защищать свои атрибуты. Например, вы не за хотите, чтобы у объекта А была возможность инспектировать или изменять значения атрибутов объекта В, если объект В не сможет при этом все контролировать. На это есть несколько причин, и большинство из них сводится к целостности дан- ных и эффективной отладке. Предположим, что в классе Cab есть дефект. Вы отследили проблему, что при- вело вас к атрибуту name . Каким-то образом он перезаписывается, а при выпол- нении некоторых запросов имен появляется мусор. Если бы name был public и любой класс мог изменять его значение, то вам пришлось бы просмотреть весь возможный код в попытке найти фрагменты, которые ссылаются на name и из- меняют его значение. Однако если бы вы разрешили только объекту Cabbie изменять значение name , то вам пришлось бы выполнять поиск только в классе Cabbie . Такой доступ обеспечивается методом особого типа, называемым мето- дом доступа. Иногда методы доступа называются геттерами и сеттерами, а по- рой — просто get() и set() . По соглашению в этой книге мы указываем методы с префиксами set и get , как показано далее: Глава.4..Анатомия.класса 102 // Задание значения для name, относящегося к Cabbie public void setName(String iName) { name = iName; } // Извлечение значения name, относящегося к Cabbie public String getName() { return name; } В этом фрагменте кода объект Supervisor должен отправить запрос объекту Cabbie на возврат значения его атрибута name (рис. 4.4). Важно здесь то, что объект Supervisor сам по себе не сможет извлечь информацию; ему придется запросить сведения у объекта Cabbie . Эта концепция важна на многих уровнях. Например, у вас мог бы иметься метод setAge() , который проверяет, является ли введенное значение возраста 0 или меньшей величиной. Если значение воз- раста окажется меньше 0 , то метод setAge() может отказаться задавать это не- корректное значение. Обычно сеттеры применяются для обеспечения того или иного уровня целостности данных. Рис. 4.4. Запрос.информации Это также проблема безопасности. Например, у вас имеется требующая защиты информация: пароли или данные расчета заработной платы, доступ к которым вы хотите контролировать. Таким образом, доступ к данным с помощью геттеров и сеттеров обеспечивает возможность использования механизмов вроде про- верки паролей и прочих методик валидации. Это значительно укрепляет целост- ность данных. ОБЪЕКТЫ _____________________________________________________________________________ Вообще.говоря,.на.каждый.объект.не.приходится.по.одной.физической.копии.каж- дого.нестатического.метода..В.этом.случае.каждый.объект.указывал.бы.на.один.и.тот. же.машинный.код..Однако.на.концептуальном.уровне.вы.можете.представлять,.что. объекты.полностью.независимы.и.содержат.собственные.атрибуты.и.методы. 103 Методы.открытых.интерфейсов. . Приведенный далее фрагмент кода демонстрирует пример определения метода, а на рис. 4.5 показано, как на один и тот же код указывает несколько объектов. СТАТИЧЕСКИЕ АТРИБУТЫ _____________________________________________________________ Если.атрибут.является.статическим,.а.класс.обеспечивает.для.него.сеттер,.то.любой. объект,.вызывающий.этот.сеттер,.будет.изменять.единственную.копию..Таким.об- разом,.значение.этого.атрибута.изменится.для.всех.объектов. // Извлечение значения name, относящегося к Cabbie public static String getCompanyName() { return companyName; } Обратите внимание, что метод getCompanyName объявлен как static , как метод класса; методы классов подробнее рассматриваются в главе 3. Помните, что атрибут companyName тоже объявлен как static . Метод, как и атрибут, может быть объявлен как static для указания на то, что на весь соответствующий класс приходится только одна копия этого метода. Рис. 4.5. Выделение.памяти.в.случае.с.методами Методы открытых интерфейсов Как конструкторы, так и методы доступа объявляются как public и относятся к открытому интерфейсу. Они выделяются в силу своей особой важности для Глава.4..Анатомия.класса 104 конструкции класса. Однако значительная часть реальной работы выполняется в других методах. Как уже отмечалось в главе 2, методы открытых интерфейсов имеют тенденцию быть очень абстрактными, а реализация склонна быть более конкретной. Для нашего класса мы предусмотрим метод giveDestination , ко- торый будет частью открытого метода и позволит пользователю указать, куда он хочет «отправиться»: public void giveDestination (){ } На данный момент неважно, что содержится внутри этого метода. Главное, что он является открытым методом и частью открытого интерфейса для соответ- ствующего класса. Методы закрытых реализаций Несмотря на то что все методы, рассмотренные в этой главе, определяются как public , не все методы в классе являются частью открытого интерфейса. Методы в том или ином классе обычно скрыты от других классов и объявляются как private : private void turnRight(){ } private void turnLeft() { } Эти закрытые методы призваны быть частью реализации, а не открытого интер- фейса. Может возникнуть вопрос насчет того, кто будет вызывать данные ме- тоды, если этого не сможет сделать ни один другой класс. Ответ прост: вы, возможно, уже подозревали, что эти методы можно вызывать изнутри метода giveDestination : public void giveDestination (){ ... код turnRight(); turnLeft(); ... еще код } В качестве еще одного примера можно привести возможную ситуацию, когда у вас имеется внутренний метод, обеспечивающий шифрование, который вы 105 Ссылки. . будете вызывать изнутри самого класса. Коротко говоря, этот метод шифрова- ния нельзя вызвать извне созданного экземпляра объекта как такового. Главное здесь состоит в том, что закрытые методы являются строго частью реа- лизации и недоступны другим классам. Резюме В этой главе мы заглянули внутрь класса и рассмотрели фундаментальные концепции, необходимые для понимания принципов создания классов. Хотя в этой главе был использован скорее практический подход, в главе 5 классы будут рассмотрены с общей точки зрения проектировщика. Ссылки Мартин Фаулер, «UML. Основы» (UML Distilled). — 3-е изд. — Бостон, штат Массачусетс: Addison-Wesley Professional, 2003. Стивен Гилберт и Билл Маккарти, «Объектно-ориентированное проектиро- вание на Java» (Object-Oriented Design in Java). — Беркли, штат Калифорния: The Waite Group Press (Pearson Education), 1998. Пол Тима, Габриэл Торок и Трой Даунинг, «Учебник по Java для начинаю- щих» (Java Primer Plus). — Беркли, штат Калифорния: The Waite Group, 1996. Глава 5 РУКОВОДСТВО ПО ПРОЕКТИРОВАНИЮ КЛАССОВ Как уже отмечалось, объектно-ориентированное программирование поддержи- вает идею создания классов в виде полных пакетов, которые инкапсулируют данные и поведения единого целого. Таким образом, класс должен представлять логический компонент, например такси. В этой главе приведены рекомендации по проектированию качественных клас- сов. Ясно, что список вроде этого нельзя считать исчерпывающим. Вы, несо- мненно, добавите большое количество указаний в свой личный перечень, также включив в него полезные инструкции от других разработчиков. Моделирование реальных систем Одна из основных целей объектно-ориентированного программирования — моделирование реальных систем способами, которые схожи с фактическим образом мышления людей. Проектирование классов — это объектно-ориенти- рованный вариант создания таких моделей. Вместо использования структурно- го, или нисходящего, подхода, при котором данные и поведения — это логически раздельные сущности, объектно-ориентированный подход инкапсулирует данные и поведения в объектах, взаимодействующих друг с другом. Мы больше не представляем себе проблему как последовательность событий или программ, воздействующих на отдельные файлы данных. Элегантность этого подхода со- стоит в том, что классы буквально моделируют реальные объекты и их взаимо- действие с другими реальными объектами. Эти взаимодействия происходят почти так же, как взаимодействия между ре- альными объектами, такими, например, как люди. Поэтому при создании клас- сов вам следует проектировать их тем способом, который позволит представить истинное поведение объекта. Воспользуемся примером с Cabbie из предыдущих глав. Классы Cab и Cabbie моделируют реальную сущность. Как показано на рис. 5.1, объекты Cab и Cabbie инкапсулируют свои данные и поведения, а также взаимодействуют с помощью открытых интерфейсов друг друга. |