Объектно-ориентированный подход. Объектно_ориентированный_подход. Объектно ориентированный подход Мэтт Вайсфельд 5е международное издание ббк 32. 973. 2018
Скачать 5.43 Mb.
|
Сообщения Сообщения — это механизм коммуникаций между объектами. Например, когда объект А вызывает метод объекта В, объект А отправляет сообщение объекту В. Ответ объекта В определяется его возвращаемым значением. Только открытые, а не закрытые методы объекта могут вызываться другим объектом. Приведенный далее код демонстрирует эту концепцию: public class Payroll{ String name; Person p = new Person(); p.setName("Joe"); ...код name = p.getName(); } В этом примере (предполагая, что был создан экземпляр объекта Payroll ) объект Payroll отправляет сообщение объекту Person с целью извлечения имени с помощью метода getName() . Опять-таки не стоит слишком беспоко- иться о фактическом коде, поскольку в действительности нас интересуют концепции. Мы подробно рассмотрим код по мере нашего продвижения по этой книге. Использование диаграмм классов в качестве визуального средства Со временем разрабатывается все больше средств и методик моделирования, призванных помочь в проектировании программных систем. Я с самого начала использовал UML-диаграммы классов как вспомогательный инструмент в об- разовательном процессе. Несмотря на то что подробное описание UML лежит вне рамок этой книги, мы будем использовать UML-диаграммы классов для иллюстрирования создаваемых классов. Фактически мы уже использовали 39 Инкапсуляция.и.сокрытие.данных. . диаграммы классов в этой главе. На рис. 1.11 показана диаграмма класса Person , о котором шла речь ранее в этой главе. Рис. 1.11. Диаграмма.класса.Person Обратите внимание, что атрибуты и методы разделены (атрибуты располагают- ся вверху, а методы — внизу). По мере того как мы будем сильнее углубляться в объектно-ориентированное проектирование, диаграммы классов будут стано- виться значительно сложнее и сообщать намного больше информации о том, как разные классы взаимодействуют друг с другом. Инкапсуляция и сокрытие данных Одно из основных преимуществ использования объектов заключается в том, что объекту не нужно показывать все свои атрибуты и поведения. При хорошем объектно-ориентированном проектировании (по крайней мере, при таком, ко- торое повсеместно считается хорошим) объект должен показывать только ин- терфейсы, необходимые другим объектам для взаимодействия с ним. Детали, не относящиеся к использованию объекта, должны быть скрыты от всех других объектов согласно принципу необходимого знания. Инкапсуляция определяется тем, что объекты содержат как атрибуты, так и по- ведения. Сокрытие данных является основной частью инкапсуляции. Например, объект, который применяется для вычисления квадратов чисел, должен обеспечивать интерфейс для получения результатов. Однако внутренние атри- буты и алгоритмы, используемые для вычисления квадратов чисел, не нужно делать доступными для запрашивающего объекта. Надежные классы проектиру- ются с учетом инкапсуляции. В последующих разделах мы рассмотрим концепции интерфейса и реализации, которые образуют основу инкапсуляции. Интерфейсы Мы уже видели, что интерфейс определяет основные средства коммуникации между объектами. При проектировании любого класса предусматриваются Глава.1..Введение.в.объектно-ориентированные.концепции 40 интерфейсы для надлежащего создания экземпляров и эксплуатации объектов. Любое поведение, которое обеспечивается объектом, должно вызываться через сообщение, отправляемое с использованием одного из предоставленных интер- фейсов. В случае с интерфейсом должно предусматриваться полное описание того, как пользователи соответствующего класса будут взаимодействовать с этим классом. В большинстве объектно-ориентированных языков программирования методы, являющиеся частью интерфейсов, определяются как public ЗАКРЫТЫЕ ДАННЫЕ __________________________________________________________________ Для.того.чтобы.сокрытие.данных.произошло,.все.атрибуты.должны.быть.объявлены. как. private ..Поэтому.атрибуты.никогда.не.являются.частью.интерфейсов..Частью. интерфейсов.классов.могут.быть.только.открытые.методы..Объявление.атрибута. как. public .нарушает.концепцию.сокрытия.данных. Взглянем на пример того, о чем совсем недавно шла речь: рассмотрим вычисле- ние квадратов чисел. В таком примере интерфейс включал бы две составляющие: способ создать экземпляр объекта Square ; способ отправить значение объекту и получить в ответ квадрат соответству- ющего числа. Как уже отмечалось ранее в этой главе, если пользователю потребуется доступ к атрибуту, то будет сгенерирован метод для возврата значения этого атрибута (геттер). Если затем пользователю понадобится получить значение атрибута, то будет вызван метод для возврата его значения. Таким образом, объект, со- держащий атрибут, будет управлять доступом к нему. Это жизненно важно, особенно в плане безопасности, тестирования и сопровождения. Если вы кон- тролируете доступ к атрибуту, то при возникновении проблемы не придется беспокоиться об отслеживании каждого фрагмента кода, который мог бы из- менить значение соответствующего атрибута — оно может быть изменено толь- ко в одном месте (с помощью сеттера). В целях безопасности не нужно, чтобы неконтролируемый код мог изменять или обращаться к закрытым данным. Например, во время использования бан- комата нужно ввести пин-код, чтобы получить доступ к данным. ПОДПИСИ: ИНТЕРФЕЙСЫ В СОПОСТАВЛЕНИИ С ИНТЕРФЕЙСАМИ ___________________ Не.стоит.путать.интерфейсы.для.расширения.классов.с.интерфейсами.классов..Мне. нравится.обобщать.интерфейсы,.представленные.методами,.словом.«подписи». Реализации Только открытые атрибуты и методы являются частью интерфейсов. Пользо- ватели не должны видеть какую-либо часть внутренней реализации и могут взаимодействовать с объектами исключительно через интерфейсы классов. 41 Инкапсуляция.и.сокрытие.данных. . Таким образом, все определенное как private окажется недоступно пользова- телям и будет считаться частью внутренней реализации классов. В приводившемся ранее примере с классом Employee были скрыты только атри- буты. Во многих ситуациях будут попадаться методы, которые также должны быть скрыты и, таким образом, не являться частью интерфейса. В продолжение примера из предыдущего раздела представим, что речь идет о вычислении ква- дратного корня, и отметим при этом, что пользователям будет все равно, как вычисляется квадратный корень, при условии, что ответ окажется правильным. Таким образом, реализация может меняться, однако она не повлияет на поль- зовательский код. Например, компания, которая производит калькуляторы, может заменить алгоритм (возможно, потому, что новый алгоритм оказался более эффективным), не повлияв при этом на результаты. Реальный пример парадигмы «интерфейс/реализация» На рис. 1.12 проиллюстрирована парадигма «интерфейс/реализация» с исполь- зованием реальных объектов, а не кода. Тостеру для работы требуется электри- чество. Чтобы обеспечить подачу электричества, нужно вставить вилку шнура тостера в электрическую розетку, которая является интерфейсом. Для того чтобы получить требуемое электричество, тостеру нужно лишь «реализовать» шнур, который соответствует техническим характеристикам электрической розетки; это и есть интерфейс между тостером и электроэнергетической компанией (в дей- ствительности — системой электроснабжения). Для тостера не имеет значения, что фактической реализацией является электростанция, работающая на угле. На самом деле электричество, которое для него важно, может вырабатываться как огромной атомной электростанцией, так и небольшим электрогенератором. При такой модели любой прибор сможет получить электричество, если он соответ- ствует спецификации интерфейса, как показано на рис. 1.12. Рис. 1.12. Пример.с.электростанцией Модель парадигмы «интерфейс/реализация» Подробнее разберем класс Square . Допустим, вы создаете класс для вычисления квадратов целых чисел. Вам потребуется обеспечить отдельный интерфейс Глава.1..Введение.в.объектно-ориентированные.концепции 42 и реализацию. Иначе говоря, вы должны будете предусмотреть для пользовате- лей способ вызова методов и получения квадратичных значений. Вам также потребуется обеспечить реализацию, которая вычисляет квадраты чисел; одна- ко пользователям не следует что-либо знать о конкретной реализации. На рис. 1.13 показан один из способов сделать это. Обратите внимание, что на диа- грамме класса знак плюс ( + ) обозначает public , а знак минус ( – ) указывает на private . Таким образом, вы сможете идентифицировать интерфейс по методам, в начале которых стоит плюс. Рис. 1.13. Класс.IntSquare Эта диаграмма класса соответствует следующему коду: public class IntSquare { // закрытый атрибут private int squareValue; // открытый интерфейс public int getSquare (int value) { SquareValue =calculateSquare(value); return squareValue; } // закрытая реализация private int calculateSquare (int value) { return value*value; } } Следует отметить, что единственной частью класса, доступной для пользовате- лей, является открытый метод getSquare , который относится к интерфейсу. Реализация алгоритма вычисления квадратов чисел заключена в закрытом методе calculateSquare . Обратите также внимание на то, что атрибут SquareValue является закрытым, поскольку пользователям не нужно знать о его наличии. Поэтому мы скрыли часть реализации: объект показывает только интерфейсы, 43 Наследование. . необходимые пользователям для взаимодействия с ним, а детали, не относящи- еся к использованию объекта, скрыты от других объектов. Если бы потребовалось сменить реализацию — допустим, вы захотели бы ис- пользовать встроенную квадратичную функцию соответствующего языка про- граммирования, — то вам не пришлось бы менять интерфейс. Вот код, исполь- зующий метод Math.pow из Java-библиотеки, который выполняет ту же функцию, однако обратите внимание, что calculateSquare по-прежнему является частью интерфейса: // закрытая реализация private int calculateSquare (int value) { return = Math.pow(value,2); } Пользователи получат ту же самую функциональность с применением того же самого интерфейса, однако реализация будет другой. Это очень важно при на- писании кода, который будет иметь дело с данными. Так, например, вы сможе- те перенести данные из файла в базу данных, не заставляя пользователя вносить изменения в какой-либо программный код. Наследование Наследование позволяет классу перенимать атрибуты и методы другого класса. Это дает возможность создавать новые классы абстрагированием из общих атрибутов и поведений других классов. Одна из основных задач проектирования при объектно-ориентированном про- граммировании заключается в выделении общности разнообразных классов. Допустим, у вас есть класс Dog и класс Cat , каждый из которых будет содержать атрибут eyeColor . При процедурной модели код как для Dog , так и для Cat вклю- чал бы этот атрибут. При объектно-ориентированном проектировании атрибут, связанный с цветом, можно перенести в класс с именем Mammal наряду со всеми прочими общими атрибутами и методами. В данном случае оба класса — Dog и Cat — будут наследовать от класса Mammal , как показано на рис. 1.14. Итак, оба класса наследуют от Mammal . Это означает, что в итоге класс Dog будет содержать следующие атрибуты: eyeColor // унаследован от Mammal barkFrequency // определен только для Dog В том же духе объект Dog будет содержать следующие методы: getEyeColor // унаследован от Mammal bark // определен только для Dog Глава.1..Введение.в.объектно-ориентированные.концепции 44 Рис. 1.14. Иерархия.классов.млекопитающих Создаваемый экземпляр объекта Dog или Cat будет содержать все, что есть в его собственном классе, а также все имеющееся в родительском классе. Таким об- разом, Dog будет включать все свойства своего определения класса, а также свойства, унаследованные от класса Mammal ПОВЕДЕНИЕ __________________________________________________________________________ Стоит.отметить,.что.сегодня.поведение,.как.правило,.описывается.в.интерфейсах. и.что.наследование.атрибутов.является.наиболее.распространенным.использова- нием.прямого.наследования..Таким.образом,.поведение.абстрагируется.от.своих. данных. Суперклассы и подклассы Суперкласс, или родительский класс (иногда называемый базовым), содержит все атрибуты и поведения, общие для классов, которые наследуют от него. На- пример, в случае с классом Mammal все классы млекопитающих содержат анало- гичные атрибуты, такие как eyeColor и hairColor , а также поведения вроде generateInternalHeat и growHair . Все классы млекопитающих включают эти атрибуты и поведения, поэтому нет необходимости дублировать их, спускаясь по дереву наследования, для каждого типа млекопитающих. Дублирование по- требует много дополнительной работы, и пожалуй, вызывает наибольшее бес- покойство — оно может привести к ошибкам и несоответствиям. Подкласс, или дочерний класс (иногда называемый производным), представ- ляет собой расширение суперкласса. Таким образом, классы Dog и Cat наследу- ют все общие атрибуты и поведения от класса Mammal . Класс Mammal считается суперклассом подклассов, или дочерних классов, Dog и Cat 45 Наследование. . Наследование обеспечивает большое количество преимуществ в плане проек- тирования. При проектировании класса Cat класс Mammal предоставляет значи- тельную часть требуемой функциональности. Наследуя от объекта Mammal , Cat уже содержит все атрибуты и поведения, которые делают его настоящим классом млекопитающих. Точнее говоря, являясь классом млекопитающих такого типа, как кошки, Cat должен включать любые атрибуты и поведения, которые свой- ственны исключительно кошкам. Абстрагирование Дерево наследования может разрастись довольно сильно. Когда классы Mammal и Cat будут готовы, добавить другие классы млекопитающих, например собак (или львов, тигров и медведей), не составит особого труда. Класс Cat также может выступать в роли суперкласса. Например, может потребоваться допол- нительно абстрагировать Cat , чтобы обеспечить классы для персидских, сиам- ских кошек и т. д. Точно так же, как и Cat , класс Dog может выступать в роли родительского класса для других классов, например GermanShepherd и Poodle (рис. 1.15). Мощь наследования заключается в его методиках абстрагирования и организации. Рис. 1.15. UML-диаграмма.классов.млекопитающих Такое большое количество уровней наследования является одной из причин, почему многие разработчики стараются в принципе не применять наследование. Как часто можно увидеть, непросто определить необходимую степень абстра- Глава.1..Введение.в.объектно-ориентированные.концепции 46 гирования. Например, представим, что есть классы penguin (пингвин) и hawk (ястреб). И пингвин, и ястреб — птицы, но должны ли они оба перенимать все признаки класса Bird (птица), в который заложен метод умения летать? В большинстве современных объектно-ориентированных языков программи- рования (например, Java, .NET и Swift) у класса может иметься только один родительский, но много дочерних классов. А в некоторых языках программи- рования, например C++, у одного класса может быть несколько родительских классов. В первом случае наследование называется простым, а во втором — множественным. МНОЖЕСТВЕННОЕ НАСЛЕДОВАНИЕ __________________________________________________ Представьте.себе.ребенка,.наследующего.черты.своих.родителей..Какого.цвета. у.него.будут.глаза?.То.же.самое.делает.написание.компиляторов.весьма.трудным.. Вот.C++.позволяет.применять.множественное.наследование,.а.многие.другие.язы- ки.—.нет. Обратите внимание, что оба класса, GermanShepherd и Poodle , наследуют от Dog — каждый содержит только один метод. Однако поскольку они наследуют от Dog , они также наследуют от Mammal . Таким образом, классы GermanShepherd и Poodle включают в себя все атрибуты и методы, содержащиеся в Dog и Mammal , а также свои собственные (рис. 1.16). Рис. 1.16. Иерархия.млекопитающих Отношения «является экземпляром» Рассмотрим пример, в котором Circle , Square и Star наследуют от Shape . Это отношение часто называется отношением «является экземпляром», поскольку круг — это форма, как и квадрат. Когда подкласс наследует от суперкласса, он 47 Полиморфизм. . получает все возможности, которыми обладает этот суперкласс. Таким образом, Circle , Square и Star являются расширениями Shape На рис. 1.17 имя каждого из объектов представляет метод Draw для Circle , Star и Square соответственно. При проектировании системы Shape очень полезно было бы стандартизировать то, как мы используем разнообразные формы. Так мы могли бы решить, что если нам потребуется нарисовать фигуру любой фор- мы, мы вызовем метод с именем Draw . Если мы станем придерживаться этого решения всякий раз, когда нам нужно будет нарисовать фигуру, то потребуется вызывать только метод Draw , независимо от того, какую форму она будет иметь. В этом заключается фундаментальная концепция полиморфизма — на индиви- дуальный объект, будь то Circle , Star или Square , возлагается обязанность по рисованию фигуры, которая ему соответствует. Это общая концепция во многих современных приложениях, например, предназначенных для рисования и об- работки текста. Рис. 1.17. Иерархия.Shape Полиморфизм Полиморфизм — это греческое слово, буквально означающее множественность форм. Несмотря на то что полиморфизм тесно связан с наследованием, он часто упоминается отдельно от него как одно из наиболее весомых преимуществ объ- ектно-ориентированных технологий. Если потребуется отправить сообщение объекту, он должен располагать методом, определенным для ответа на это со- общение. В иерархии наследования все подклассы наследуют от своих супер- классов. Однако поскольку каждый подкласс представляет собой отдельную сущность, каждому из них может потребоваться дать отдельный ответ на одно и то же сообщение. Возьмем, к примеру, класс Shape и поведение с именем Draw . Когда вы попро- сите кого-то нарисовать фигуру, первый вопрос вам будет звучать так: «Какой формы?» Никто не сможет нарисовать требуемую фигуру, не зная формы, ко- торая является абстрактной концепцией (кстати, метод Draw() в коде Shape не содержит реализации). Вы должны указать конкретную форму. Для этого по- требуется обеспечить фактическую реализацию в Circle . Несмотря на то что |