Главная страница
Навигация по странице:

  • 1.4. Способ видения мира

  • 1.4.1 Агенты, обязанности, сообщения и методы

  • 1.4.2. Обязанности и ответственности

  • 1.4.3. Классы и экземпляры

  • 1.4.4. Иерархии классов и наследование

  • 1.4.5. Связывание и переопределение методов

  • 1.4.6. Краткое изложение принципов

  • 1.5. Вычисление и моделирование

  • 1.5.2. Как избежать бесконечной регрессии

  • 1.6.1. Нелинейное увеличение сложности

  • 1.6.2. Механизмы абстрагирования

  • Область видимости для блоков

  • Абстрактные типы данных

  • Объекты: сообщения, наследование и полиморфизм

  • 1.7. Многократно используемое программное обеспечение

  • Объектно-ориентированное программирование. Объектно-ориентированное программирование в действии. Объектноориентированное программирование


    Скачать 5.29 Mb.
    НазваниеОбъектноориентированное программирование
    АнкорОбъектно-ориентированное программирование
    Дата05.09.2022
    Размер5.29 Mb.
    Формат файлаpdf
    Имя файлаОбъектно-ориентированное программирование в действии.pdf
    ТипДокументы
    #662573
    страница2 из 23
    1   2   3   4   5   6   7   8   9   ...   23
    1.3. Новая парадигма
    Объектно-ориентированное программирование часто называют новой парадигмой программирования. Другие парадигмы программирования: директивное (языки типа
    Pascal или C), логическое (языки типа Prolog) и функциональное (языки типа Lisp, FP или
    Haskell) программирование.
    Интересно исследовать слово «парадигма». Следующий фрагмент взят из толкового словаря American Heritage Dictionary of the English Language:
    par-a-digm (сущ.) 1. Список всех вариантов окончаний слова, рассматриваемый как иллюстративный пример того, к какому спряжению или склонению оно относится. 2.
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    Любой пример или модель (от латинского paradigma и греческого paradeigma — модель, от paradeiknunai — сравнивать, выставлять).
    На первый взгляд, склонение и спряжение слов (например, латинских) имеет мало общего с компьютерными языками. Чтобы понять связь, мы должны заметить, что слово
    «парадигма» пришло в программирование из оказавшей большое влияние книги
    «Структура научных революций», написанной историком науки Томасом Куном
    [Kuhn 1970]. Кун использовал этот термин во втором значении, чтобы описывать набор теорий, стандартов и методов, которые совместно представляют собой способ организации научного знания — иными словами, способ видения мира. Основное положение Куна состоит в том, что революции в науке происходят, когда старая парадигма пересматривается, отвергается и заменяется новой.
    Именно в этом смысле — как модель или пример, а также как организующий подход —
    это слово использовал Роберт Флойд, лауреат премии Тьюринга 1979 года, в лекции
    «Парадигмы программирования» [Floyd 1979]. Парадигмы в программировании — это способ концептуализации, который определяет, как проводить вычисления и как работа, выполняемая компьютером, должна быть структурирована и организована.
    Хотя сердцевина объектно-ориентированного программирования — техника организации вычислений и данных является новой, ее зарождение можно отнести по крайней мере к временам Линнея (1707–1778), если не Платона. Парадоксально, но стиль решения задач, воплощенный в объектно-ориентированной технике, нередко используется в повседневной жизни. Тем самым новички в информатике часто способны воспринять основные идеи объектно-ориентированного программирования сравнительно легко, в то время как люди, более осведомленные в информатике, зачастую становятся в тупик из-за своих представлений. К примеру, Алан Кей обнаружил, что легче обучать языку Smalltalk детей, чем профессиональных программистов [Kay 1977].
    При попытках понять, что же в точности имеется в виду под термином объектно-
    ориентированное программирование, полезно посмотреть на ООП с разных точек зрения.
    В нескольких следующих разделах кратко очерчиваются три разных аспекта объектно- ориентированного программирования. Каждый из них по-своему объясняет, чем замечательна эта идея.
    1.4. Способ видения мира
    Чтобы проиллюстрировать некоторые основные идеи объектно-ориентированного программирования, рассмотрим ситуацию из обыденной жизни, а затем подумаем, как можно заставить компьютер наиболее близко смоделировать найденное решение.
    Предположим, я хочу послать цветы своей бабушке (которую зовут Элси) в ее день рождения. Она живет в городе, расположенном за много миль от меня, так что вариант, когда я сам срываю цветы и кладу их к ее порогу, не подлежит обсуждению. Тем не менее послать ей цветы — это достаточно простая задача: я иду в ближайший цветочный магазин, хозяйку которого (какое совпадение) зовут Фло (florist — цветочница), называю ей тип и количество цветов, которые бы я хотел послать моей бабушке, и (за приемлемую цену) я могу быть уверен, что цветы будут доставлены в срок, по нужному адресу.
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    1.4.1 Агенты, обязанности, сообщения и методы
    Рискуя быть обвиненным в тавтологии, все-таки хочу подчеркнуть, что механизм, который я использовал для решения этой проблемы, состоял в поиске подходящего агента
    (а именно, Фло) и передаче ей сообщения, содержащего мой запрос. Обязанностью Фло является удовлетворение моего запроса. Имеется некоторый метод — то есть алгоритм, или последовательность операций, который используется Фло для выполнения запроса.
    Мне не надо знать, какой конкретный метод она использует для выполнения моего запроса, и в действительности зачастую я и не хочу это знать. Все дальнейшее обычно скрыто от моего взгляда.
    Однако если бы я исследовал этот вопрос, я, возможно, обнаружил бы, что Фло пошлет свое сообщение хозяину цветочного магазина в городе, где живет моя бабушка. Хозяин цветочного магазина в свою очередь примет необходимые меры и подготовит распоряжение (сообщение) для человека, ответственного за доставку цветов, и т. д. Тем самым мой запрос в конечном счете будет удовлетворен через последовательность запросов, пересылаемых от одного агента к другому.
    Итак, первым принципом объектно-ориентированного подхода к решению задач является способ задания действий.
    Действие в объектно-ориентированном программировании инициируется посредством передачи сообщений агенту (объекту), ответственному за действие. Сообщение содержит запрос на осуществление действия и сопровождается дополнительной информацией
    (аргументами), необходимой для его выполнения. Получатель (receiver) — это агент, которому посылается сообщение. Если он принимает сообщение, то на него автоматически возлагается ответственность за выполнение указанного действия. В качестве реакции на сообщение получатель запустит некоторый метод, чтобы удовлетворить принятый запрос.
    Мы заметили, что существует важный принцип маскировки информации в отношении пересылки сообщений. А именно: клиенту, посылающему запрос, не требуется знать о фактических средствах, с помощью которых его запрос будет удовлетворен. Существует и другой принцип, также вполне человеческий, который мы видели в неявной форме при пересылке сообщений. Если имеется работа, которую нужно выполнить, то первая мысль клиента — найти кого-либо еще, кому можно было бы ее поручить. Такая вполне нормальная реакция почти полностью атрофировалась у программиста, имеющего большой опыт в традиционном программировании. Ему трудно представить, что он (или она) не должен все полностью программировать сам, а может обратиться к услугам других. Важная часть объектно-ориентированного программирования — разработка повторно используемых компонент, и первым шагом в этом направлении является желание попробовать этот путь.
    Скрытие информации является важным принципом и в традиционных языках программирования. Тогда в чем пересылка сообщений отличается от обычного вызова процедуры? В обоих случаях имеется последовательность точно определенных действий, которые будут инициированы в ответ на запрос. Однако имеются два существенных отличия.
    Первое из них состоит в том, что у сообщения имеется вполне конкретный получатель — агент, которому послано сообщение. При вызове процедуры нет столь явно выделенного получателя. (Хотя, конечно, мы можем принять соглашение, согласно которому
    PDF created with pdfFactory Pro trial version www.pdffactory.com
    получателем сообщения является первый аргумент в вызове процедуры — примерно так и реализуются получатели сообщений).
    Второе отличие состоит в том, что интерпретация сообщения (а именно метод, вызываемый после приема сообщения) зависит от получателя и является различной для различных получателей. Я могу передать мое сообщение, к примеру, моей жене Бет, и она его поймет, и как результат действие будет выполнено (а именно цветы будут доставлены бабушке). Однако метод, который использует Бет для выполнения запроса (весьма вероятно, просто переадресовав его хозяйке цветочного магазина Фло), будет иным, чем тот, который применит Фло в ответ на тот же самый запрос. Если я попрошу о том же
    Кена, моего зубного врача, у него может не оказаться подходящего метода для решения поставленной задачи. Если предположить, что Кен вообще воспримет этот запрос, то он с большой вероятностью выдаст надлежащее диагностическое сообщение об ошибке.
    Вернемся в нашем обсуждении на уровень компьютеров и программ. Различие между вызовом процедуры и пересылкой сообщения состоит в том, что в последнем случае существует определенный получатель и интерпретация (то есть выбор подходящего метода, который запускается в ответ на сообщение) может быть различной для разных получателей. Обычно конкретный получатель неизвестен вплоть до выполнения программы, так что определить, какой метод будет вызван, заранее невозможно. В таком случае говорят, что имеет место позднее связывание между сообщением (именем процедуры или функции) и фрагментом кода (методом), используемым в ответ на сообщение. Эта ситуация противопоставляется раннему связыванию (на этапе компилирования или компоновки программы) имени с фрагментом кода, что происходит при традиционных вызовах процедур.
    1.4.2. Обязанности и ответственности
    Фундаментальной концепцией в объектно-ориентированном программировании является понятие обязанности или ответственности за выполнение действия. Мой запрос выражает только стремление получить желаемый результат (а именно доставить цветы бабушке).
    Хозяйка цветочного магазина свободна в выборе способа, который приведет к желаемому результату, и не испытывает препятствий с моей стороны в этом аспекте.
    Обсуждая проблему в терминах обязанностей, мы увеличиваем уровень абстрагирования.
    Это позволяет иметь большую независимость между агентами — критический фактор при решении сложных задач. В главе 2 мы будем подробно исследовать, как можно использовать обязанности в разработке программного обеспечения. Полный набор обязанностей, связанных с определенным объектом, часто определяется с помощью термина протокол.
    Различие между взглядом на программное обеспечение со стороны традиционного, структурного подхода и объектно-ориентированной точкой зрения на него может быть выражено в форме пародии на хорошо известную цитату:
    Задавайтесь вопросом не о том, что вы можете сделать для своих структур данных, а о том, что структуры данных могут сделать для вас.
    1.4.3. Классы и экземпляры
    Хотя я имел дело с Фло лишь несколько раз, у меня имеется примерное представление о ее реакции на мой запрос. Я могу сделать определенные предположения, поскольку имею
    PDF created with pdfFactory Pro trial version www.pdffactory.com
    общую информацию о людях, занимающихся разведением цветов, и ожидаю, что Фло, будучи представителем этой категории, в общих чертах будет соответствовать шаблону.
    Мы можем использовать термин Florist для описания категории (или класса) всех людей, занимающихся цветоводством, собрав в нее (категорию) все то общее, что им свойственно. Эта операция является вторым принципом объектно-ориентированного программирования:
    Все объекты являются представителями, или экземплярами, классов. Метод, активизируемый объектом в ответ на сообщение, определяется классом, к которому принадлежит получатель сообщения. Все объекты одного класса используют одни и те же методы в ответ на одинаковые сообщения.
    Проблема сообщества объектно-ориентированных программистов заключается в распространенности различных терминов для обозначения сходных идей. Так, в языке
    Object Pascal класс называется «объектом» (тип данных object), а надклассы (которые вкратце будут описаны ниже) известны как родительский класс, класс-предок и т. д.
    Словарь-глоссарий в конце этой книги поможет вам разобраться с нестандартными терминами. Мы будем использовать соглашение, общее для объектно-ориентированных языков программирования: всегда обозначать классы идентификаторами, начинающимися с заглавной буквы. Несмотря на свою распространенность, данное соглашение не является обязательным для большинства языков программирования.
    1.4.4. Иерархии классов и наследование
    О Фло у меня имеется больше информации, чем содержится в категории Florist. Я знаю, что она разбирается в цветах и является владелицей магазина (shopkeeper). Я догадываюсь, что, вероятно, меня спросят о деньгах в процессе обработки моего запроса и что после оплаты мне будет выдана квитанция. Все вышеперечисленное справедливо также для зеленщиков, киоскеров, продавцов магазинов и т. д. Поскольку категория Florist является более узкой, чем Shopkeeper, то любое знание, которым я обладаю о категории
    Shopkeeper, справедливо также и для Florist, и, в частности, для Фло.
    Один из способов представить организацию моего знания о Фло — это иерархия категорий (рис. 1.1). Фло принадлежит к категории Florist; Florist является подкатегорией категории Shopkeeper. Далее, представитель Shopkeeper заведомо является человеком, то есть принадлежит к категории Human — тем самым я знаю, что Фло с большой вероятностью является двуногим существом. Далее, категория Human включена в категорию млекопитающих (Mammal), которые кормят своих детенышей молоком, а млекопитающие являются подкатегорией животных (Animal) и, следовательно, дышат кислородом. В свою очередь животные являются материальными объектамидуумов с различными линиями наследования. Классы представляются в виде иерархической древовидной структуры, в которой более абстрактные классы (такие, как Material Object или Animal) располагаются в корне дерева, а более специализированные классы и в конечном итоге индивидуумы располагаются на его конце, в ветвях. Рисунок 1.2 показывает такую иерархию классов для Фло. Эта же самая иерархия включ ает в себя мою жену Бет, собаку Флеш, Фила — утконоса, живущего в зоопарке, а также цветы, которые я послал своей бабушке.
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    Рис. 1.2. Иерархическое дерево классов, представляющих различные материальные объекты
    Поскольку Фло — человек, та информация о ней, которой я обладаю, применима также, к примеру, к моей жене Бет. Те данные, которыми я располагаю в силу принадлежности последней к классу млекопитающих, имеют также отношение к моей собаке Флеш.
    Информация об объектах как о вещах материальных имеет смысл в отношени Фло, и ее цветов. Мы выражаем все это в виде идеи наследования:
    Классы могут быть организованы в иерархическую структуру с наследованием свойств.
    Дочерний класс (или подкласс) наследует атрибуты родительского класса (или надкласса), расположенного выше в иерархическом дереве
    1
    . Абстрактный родительский класс — это класс, не имеющий экземпляров (его примером может служить Mammal на рис. 1.2).
    Он используется только для порождения подклассов.
    1.4.5. Связывание и переопределение методов
    Утконос Фил представляет собой проблему для нашей простой структуры. Я знаю, что млекопитающие являются живородящими, но Фил определенно является млекопитающим, хотя он (точнее, его подруга Филлис) кладет яйца. Чтобы включить его в нашу схему, мы должны найти технику для представления исключений из общего правила.
    Мы сделаем это, допустив правило, что информация, содержащаяся в подклассе, может -
    1
    Здесь придется попросить читателя вернуться к рис. 1.2 и обратить внимание на то, что согласно принятой схеме дерево растет сверху вниз. — Примеч. ред.
    PDF created with pdfFactory Pro trial version www.pdffactory.com
    переопределять информацию, наследуемую из родительского класса. Очень часто при реализация такого подхода метод, соответствующий подклассу, имеет то же имя, что и соответствующий метод в родительском классе. При этом для поиска метода, подходящего для обработки сообщения, используется следующее правило: Поиск метода, который вызывается в ответ на определенное сообщение, начинается с методов, принадлежащих классу получателя. Если подходящий метод не найден, то поиск продолжается для родительского класса. Поиск продвигается вверх по цепочке родительских классов до тех пор, пока не будет найден нужный метод или пока не будет исчерпана последовательность родительских классов. В первом случае выполняется найденный метод, во втором — выдается сообщение об ошибке. Если выше в иерархии классов существуют методы с тем же именем, что и текущий, то говорят, что данный метод переопределяет наследуемое поведение.
    Даже если компилятор не может определить, какой именно метод будет вызываться во время выполнения программы, то во многих языках программирования уже на этапе компилирования, а не при выполнении программы можно определить, что подходящего метода нет вообще, и выдать сообщение об ошибке. Мы будем обсуждать реализацию механизма переопределения в различных языках программирования в главе 11.
    Тот факт, что моя жена Бет и хозяйка цветочного магазина Фло будут реагировать на мое сообщение с применением различных методов, является одним из примеров полиморфизма. Мы будем обсуждать эту важную составную часть объектно- ориентированного программирования в главе 14. То, что я, как уже говорилось, не знаю и не хочу знать, какой именно метод будет использован Фло для выполнения моего запроса, является примером маскировки информации, которая анализируется в главе 17.
    1.4.6. Краткое изложение принципов
    Алан Кей, которого кое-кто называет отцом объектно-ориентированного программирования, считает следующие положения фундаментальными характеристиками
    ООП [Kay 1993]:
    1. Все является объектом.
    2. Вычисления осуществляются путем взаимодействия (обмена данными) между объектами, при котором один объект требует, чтобы другой объект выполнил некое действие. Объекты взаимодействуют, посылая и получая сообщения.
    Сообщение — это запрос на выполнение действия, дополненный набором аргументов, которые могут понадобиться при выполнении действия.
    3. Каждый объект имеет независимую память, которая состоит из других объектов.
    4. Каждый объект является представителем класса, который выражает общие свойства объектов (таких, как целые числа или списки).
    5. В классе задается поведение (функциональность) объекта. Тем самым все объекты, которые являются экземплярами одного класса, могут выполнять одни и те же действия.
    6. Классы организованы в единую древовидную структуру с общим корнем, называемую иерархией наследования. Память и поведение, связанное с экземплярами определенного класса, автоматически доступны любому классу, расположенному ниже в иерархическом дереве.
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    1.5. Вычисление и моделирование
    Взгляд на программирование, проиллюстрированный на примере с цветами, весьма отличается от привычного понимания того, что такое компьютер. Традиционная модель, описывающая выполнение программы на компьютере, базируется на дуализме процесс состояние. С этой точки зрения компьютер является администратором данных, следующим некоторому набору инструкций. Он странствует по пространству памяти, изымает значения из ее ячеек (адресов памяти), некоторым образом преобразует полученные величины, а затем помещает их в другие ячейки (рис. 1.3). Проверяя значения, находящиеся в различных ячейках, мы определяем состояние машины или же результат вычислений. Хотя эта модель и может рассматриваться как более или менее точный образхранения, почтовых ящиках или ячейках памяти, содержащих значения, мало что из житейского опыта может подсказать, как следует структурировать задачу.
    Хотя антропоморфные описания, подобные тем, что цитировались выше в тексте Ингалса, могут шокировать людей, фактически они являются отражением огромной выразительной силы метафор. Журналисты используют метафоры каждый день, подобно тому, как это сделано в нижеследующем фрагменте из NewsWeek: В отличие от обычного метода программирования — то есть написания программы строчка за строчкой, — «объектно- ориентированная» система компьютеров NeXT предлагает строительные блоки большего размера, которые разработчик может быстро собирать воедино, подобно тому, как дети складывают мозаику.
    Возможно, именно это свойство — в большей степени, чем другие — вызывает часто наблюдаемый эффект, когда новичков от информатики легче учить понятиям объектно- ориентированного программирования, чем уже сложившихся профессионалов. Молодежь быстро адаптируется к соответствующим обыденной жизни метафорам, с которыми они себя чувствуют комфортно, в то время как «ветераны» обременены стремлением представить себе процесс вычислений, соответствующий традиционным взглядам на программирование.
    1.5.2. Как избежать бесконечной регрессии
    Конечно, объекты не могут во всех случаях реагировать на сообщение только тем, что вежливо обращаются к другим с просьбой выполнить некоторое действие. Это приведет к бесконечному циклу запросов, как если бы два джентльмена так и не вошли в дверь, уступая друг другу дорогу. На некоторой стадии по крайней мере некоторые объекты должны выполнять какую-то работу перед пересылкой запроса другим агентам. Эти действия выполняются по-разному в различных объектно-ориентированных языках программирования.
    В языках, где директивный и объектно-ориентированный подходы уживаются вместе
    (таких, как C++, Object Pascal и Objective-C), реальные действия выполняются методами, написанными на основном (не объектно-ориентированном) языке. В чисто объектно- ориентированных языках (таких, как Smalltalk и Java) это выполняется с помощью
    «примитивных» или «встроенных» операций, которые обеспечиваются исполнительной системой более низкого уровня.
    1.6. Барьер сложности
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    На заре информатики, большинство программ писалось на ассемблере. Они не соответствуют сегодняшним стандартам. По мере того как программы становились все сложнее, разработчики обнаружили, что они не в состоянии помнить всю информацию, необходимую для отладки и совершенствования их программного обеспечения. Какие значения находятся в регистрах? Вступает ли новый идентификатор в конфликт с определенными ранее? Какие переменные необходимо инициализировать перед тем, как передать управление следующему коду?
    Появление таких языков программирования высокого уровня, как Fortran, Cobol и Algol, разрешило некоторые проблемы (было введено автоматическое управление локальными переменными и неявное присваивание значений). Одновременно это возросла вера пользователей в возможности компьютера. По мере того как предпринимались попытки решить все более сложные проблемы с его использованием, возникали ситуации, когда даже лучшие программисты не могли удержать все в своей памяти. Привычными стали команды программистов, работающих совместно.
    1.6.1. Нелинейное увеличение сложности
    По мере того как программные проекты становились все сложнее, было замечено интересное явление. Задача, для решения которой одному программисту требовалось два месяца, не решалась двумя программистами за месяц. Согласно замечательной фразе
    Фреда Брукса, «рождение ребенка занимает девять месяцев независимо от того, сколько женщин занято этим» [Brooks 1975].
    Причиной такого нелинейного поведения является сложность. В частности, взаимосвязи между программными компонентами стали сложнее, и разработчики вынуждены были постоянно обмениваться между собой значительными объемами информации. Брукс также сказал:
    Поскольку конструирование программного обеспечения по своей внутренней природе есть задача системная (требует сложного взаимодействия участников), то расходы на обмен данными велики. Они быстро становятся доминирующими и нивелируют уменьшение индивидуальных затрат, достигаемое за счет разбиения задачи на фрагменты.
    Добавление новых людей удлиняет, а не сокращает расписание работ.
    Порождает сложность не просто большой объем рассматриваемых задач, а уникальное свойство программных систем, разработанных с использованием традиционных подходов, — большое число перекрестных ссылок между компонентами (именно это делает их одними из наиболее сложных людских творений). Перекрестные ссылки в данном случае обозначают зависимость одного фрагмента кода от другого.
    Действительно, каждый фрагмент программной системы должен выполнять некую реальную работу — в противном случае он был бы не нужен. Если эта деятельность оказывается необходимой для других частей программы, то должен присутствовать поток данных либо из, либо в рассматриваемую компоненту. По этой причине полное понимание фрагмента программы требует знаний как кода, который мы рассматриваем, так и кода, который пользуется этим фрагментом. Короче говоря, даже относительно независимый раздел кода нельзя полностью понять в изоляции от других.
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    1.6.2. Механизмы абстрагирования
    Программисты столкнулись с проблемой сложности уже давно. Чтобы полностью понять важность объектно-ориентированного подхода, нам следует рассмотреть разнообразные механизмы, которые использовались программистами для контроля над сложностью.
    Главный из них — это абстрагирование, то есть способность отделить логический смысл фрагмента программы от проблемы его реализации. В некотором смысле объектно- ориентированный подход вообще не является революционным и должен рассматриваться как естественный результат исторического развития: от процедур к модулям, далее к абстрактным типам данных и наконец к объектам.
    Процедуры
    Процедуры и функции были двумя первыми механизмами абстрагирования, примененными в языках программирования. Процедуры позволяют сконцентрировать в одном месте работу, которая выполняется многократно (возможно, с небольшими вариациями), и затем многократно использовать этот код, вместо того чтобы писать его снова и снова. Кроме всего прочего, процедуры впервые обеспечили возможность маскировки информации. Программист мог написать процедуру или набор процедур, которые потом использовались другими людьми. Последние не обязаны были знать детали использованного алгоритма — их интересовал только интерфейс программы. Но процедуры не решили всех проблем. В частности, они не обладали эффективным механизмом маскировки деталей организации данных и только отчасти снимали проблему использования разными программистами одинаковых имен.
    Пример: стек
    Чтобы проиллюстрировать эти проблемы, рассмотрим ситуацию, когда программисту нужно реализовать управление простым стеком. Следуя старым добрым принципам разработки программного обеспечения, наш программист прежде всего определяет внешний интерфейс — скажем, набор из четырех процедур init, push, pop и top. Затем он выбирает подходящий метод реализации. Здесь есть из чего выбрать: массив с указателем на вершину стека, связный список и т. д. Наш бесстрашный разработчик выбирает один из методов, а затем приступает к кодированию, как показано в листинге 1.1.
    Легко увидеть, что данные, образующие стек, не могут быть сделаны локальными для какой-то из четырех процедур, поскольку эти данные являются общими для всех из них.
    Но если у нас есть только локальные или глобальные переменные (как это имеет место для Fortran или было в C, до того как ввели модификатор static), то данные стека должны содержаться в глобальных переменных. Однако если переменные являются глобальными, то нет способа ограничить доступ к ним или их видимость. Например, если стек реализован как массив с именем datastack, то об этом должны знать все остальные программисты, поскольку они могут захотеть создать переменные с таким же именем, чего делать ни в коем случае нельзя. Запрет на использование имени datastack необходим, даже если сами данные важны только для подпрограмм работы со стеком и не будут использоваться за пределами этих четырех процедур. Аналогично имена init, push, pop и top являются теперь зарезервированными и не должны встречаться в других частях программы (разве что с целью вызова процедур), даже если эти части не имеют ничего общего с подпрограммами, обслуживающими стек.
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    Листинг 1.1. Процедуры не годятся для маскировки информации int datastack[100]; int datatop = 0; void init()
    { datatop = 0;
    } void push(int val)
    { if (datatop < 100) datastack [datatop++] = val;
    } int top()
    { if (datatop > 0) return datastack [datatop — 1]; return 0;
    } int pop()
    { if (datatop > 0) return datastack [--datatop]; return 0;
    }
    Область видимости для блоков
    Механизм видимости для блоков, использованный в языке Алгол и его преемниках (таких, как Pascal), предлагает чуть больший контроль над видимостью имен, чем просто различие между локальными и глобальными именами. Кажется, мы могли бы надеяться, что это решит проблему скрытия информации. К сожалению, проблема остается. В любой области, в которой разрешен доступ к именам четырех процедур, видны также и их общие данные. Чтобы решить эту дилемму, требуется разработать иной механизм структурирования. begin var datastack : array [1..100] of integer; datatop : integer; procedure init; . . . procedure push(val : integer); . . . function pop : integer; . . . end;
    Модули
    В некотором смысле модули можно рассматривать просто как улучшенный метод создания и управления совокупностями имен и связанными с ними значениями. Наш пример со стеком является типичным в том аспекте, что имеется определенная информация (интерфейсные процедуры), которую мы хотим сделать широко и открыто используемой, в то время как доступ к некоторым данным (собственно данные стека) должен быть ограничен. Если рассматривать модуль как абстрактную концепцию, сведенную к своей простейшей форме, то ее суть состоит в разбиении пространства имен на две части. Открытая (public) часть является доступной извне модуля, закрытая (private)
    PDF created with pdfFactory Pro trial version www.pdffactory.com
    часть доступна только внутри модуля. Типы, данные (переменные) и процедуры могут быть отнесены к любой из двух частей.
    Дэвид Парнас [Parnas 1972] популяризовал понятие модулей. Он сформулировал следующие два принципа их правильного использования:
    1. Пользователя, который намеревается использовать модуль, следует снабдить всей информацией, необходимой, чтобы делать это корректно, и не более того.
    2. Разработчика следует снабдить всей информацией, необходимой для создания модуля, и не более того.
    Эта философия в значительной мере напоминает военную доктрину «необходимого знания»: если вам не нужно знать определенную информацию, вы и не должны иметь к ней доступа. Это явное, намеренное и целенаправленное утаивание информации называется маскировкой информации (information hiding).
    Модули решают некоторые, но не все проблемы разработки программного обеспечения.
    Например, они позволяют нашему программисту скрыть детали реализации стека, но что делать, если другие пользователи захотят иметь два (или более) стека?
    В качестве более сложного примера предположим, что программист заявляет, что им разработан новый тип числовых объектов, названный Complex. Он определил арифметические операции для комплексных величин — сложение, вычитание, умножение и т. д. и ввел подпрограммы для преобразования обычных чисел в комплексные и обратно.
    Имеется лишь одна маленькая проблема: можно манипулировать только с одним комплексным числом.
    Комплексные числа вряд ли будут полезны при таком ограничении, но это именно та ситуация, в которой мы оказываемся в случае простых модулей. Последние, взятые сами по себе, обеспечивают эффективный механизм маскировки информации, но они не позволяют осуществлять размножение экземпляров, под которым мы понимаем возможность сделать много копий областей данных. Чтобы справиться с проблемой размножения, специалистам по информатике потребовалось разработать новую концепцию.
    Абстрактные типы данных
    Абстрактный тип данных задается программистом. С данными абстрактного типа можно манипулировать так же, как и с данными типов, встроенных в систему. Как и последним, абстрактному типу данных соответствует набор (возможно, бесконечный) допустимых значений и ряд элементарных операций, которые могут быть выполнены над данными.
    Пользователю разрешается создавать переменные, которые принимают значения из допустимого множества, и манипулировать ими, используя имеющиеся операции. К примеру, наш бесстрашный программист может определить свой стек как абстрактный тип данных и стековые операции как единственные действия, которые допускается производить над отдельными экземплярами стеков.
    Модули часто используются при реализации абстрактных типов данных.
    Непосредственной логической взаимосвязи между понятиями модуля и абстрактного типа данных нет. Эти две идеи близки, но не идентичны. Чтобы построить абстрактный тип данных, мы должны уметь:
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    1. Экспортировать определение типа данных.
    2. Делать доступным набор операций, использующихся для манипулирования экземплярами типа данных.
    3. Защищать данные, связанные с типом данных, чтобы с ними можно было работать только через указанные подпрограммы.
    4. Создавать несколько экземпляров абстрактного типа данных.
    В нашем определении модули служат только как механизм маскировки информации и тем самым непосредственно связаны только со свойствами 2 и 3 из нашего списка. Остальные свойства в принципе могут быть реализованы с использованием соответствующей техники программирования. Пакеты, которые встречаются в таких языках программирования, как CLU или Ada, тесно связаны с перечисленными выше требуемыми свойствами абстрактных типов данных.
    В определенном смысле объект — это просто абстрактный тип данных. Говорили, к примеру, что программисты на языке Smalltalk пишут наиболее «структурированные» программы, потому что они не имеют возможности написать что-либо кроме определений абстрактных типов данных. Истинная правда, что объект является абстрактным типом данных, но понятия объектно-ориентированного программирования, хотя и строятся на идеях абстрактных типов данных, добавляют к ним важные новшества по части разделения и совместного использования программного кода.
    Объекты: сообщения, наследование и полиморфизм
    Объектно-ориентированное программирование добавляет несколько новых важных идей к концепции абстрактных типов данных. Главная из них — пересылка сообщений. Действие инициируется по запросу, обращенному к конкретному объекту, а не через вызов функции. В значительной степени это просто смещение ударения: традиционная точка зрения делает основной упор на операции, в то время как ООП на первое место ставит собственно значение. (Вызываете ли вы подпрограмму push со стеком и значением в качестве аргументов, или же вы просите объект stack поместить нужное значение к нему внутрь?) Если бы это было все, что имеется в объектно-ориентированном программировании, эта техника не рассматривалась бы как принципиальное нововведение. Но к пересылке сообщений добавляются мощные механизмы переопределения имен и совместного/многократного использования программного кода.
    Неявной в идее пересылки сообщений является мысль о том, что интерпретация сообщения может меняться для различных объектов. А именно поведение и реакция, инициируемые сообщением, зависят от объекта, который получает сообщение. Тем самым push может означать одно действие для стека и совсем другое для блока управления механической рукой. Поскольку имена операций не обязаны быть уникальными, могут использоваться простые и явные формы команд. Это приводит к более читаемому и понятному коду.
    Наконец, объектно-ориентированное программирование добавляет механизмы наследования и полиморфизма. Наследование позволяет различным типам данных совместно использовать один и тот же код, приводя к уменьшению его размера и повышению функциональности. Полиморфизм перекраивает этот общий код так, чтобы удовлетворить конкретным особенностям отдельных типов данных. Упор на независимость индивидуальных компонент позволяет использовать процесс пошаговой сборки, при которой отдельные блоки программного обеспечения разрабатываются, программируются и отлаживаются до того, как они объединяются в большую систему.
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    Все эти идеи будут описаны более подробно в последующих главах.
    1.7. Многократно используемое программное обеспечение
    Десятилетиями люди спрашивали себя, почему создание программного обеспечения не может копировать процесс конструирования материальных объектов. К примеру, когда мы строим здание, автомобиль или электронное устройство, мы обычно соединяем вместе несколько готовых компонент вместо того, чтобы изготовлять каждый новый элемент с нуля. Можно ли конструировать программное обеспечение таким же образом?
    Многократное использование программного обеспечения — цель, к которой постоянно стремятся и редко достигают. Основная причина этого — значительная взаимозависимость большей части программного обеспечения, созданного традиционными способами. Как мы видели в предыдущих разделах, трудно извлечь из проекта фрагменты программного обеспечения, которые бы легко использовались в не имеющем к нему отношения новом программном продукте (каждая часть кода обычно связана с остальными фрагментами). Эти взаимозависимости могут быть результатом определения структуры данных или следствием особенностей функционирования.
    Например, организация записей в виде таблицы и осуществление операции ее индексированного просмотра являются обычным и в программировании. Тем не менее до сих пор подпрограммы поиска в таблицах зачастую пишутся «с нуля» для каждого нового приложения. Почему? Потому что в привычных языках программирования формат записи для элементов таблицы жестко связан с более общим кодом для вставки и просмотра.
    Трудно написать код, который бы работал для произвольной структуры данных и любого типа записей.
    Объектно-ориентированное программирование обеспечивает механизм для отделения существенной информации (занесение и получение записей) от специализированной
    (конкретный формат записей). Тем самым при использовании объектно-ориентированной техники мы можем создавать большие программные компоненты, пригодные для повторного использования. Многие коммерческие пакеты программных компонентов, пригодных для многократного использования, уже имеются, и разработка повторно используемых программных компонентов становится быстро развивающейся отраслью индустрии программного обеспечения.
    1.8. Резюме
    Объектно-ориентированное программирование — это не просто несколько новых свойств, добавленных в уже существующие языки. Скорее — это новый шаг в осмыслении процессов декомпозиции задач и разработки программного обеспечения.
    ООП рассматривает программы как совокупность свободно (гибко) связанных между собой агентов, называемых объектами. Каждый из них отвечает за конкретные задачи.
    Вычисление осуществляется посредством взаимодействия объектов. Следовательно, в определенном смысле программирование — это ни много ни мало, как моделирование мира.
    Объект получается в результате инкапсуляции состояния (данных) и поведения
    (операций). Тем самым объект во многих отношениях аналогичен модулю или абстрактному типу данных.
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    Поведение объекта диктуется его классом. Каждый объект является экземпляром некоторого класса. Все экземпляры одного класса будут вести себя одинаковым образом
    (то есть вызывать те же методы) в ответ на одинаковые запросы.
    Объект проявляет свое поведение путем вызова метода в ответ на сообщение.
    Интерпретация сообщения (то есть конкретный используемый метод) зависит от объекта и может быть различной для различных классов объектов.
    Объекты и классы расширяют понятие абстрактного типа данных путем введения наследования. Классы могут быть организованы в виде иерархического дерева наследования. Данные и поведение, связанные с классами, которые расположены выше в иерархическом дереве, доступны для нижележащих классов. Происходит наследование поведения от родительских классов.
    С помощью уменьшения взаимозависимости между компонентами программного обеспечения ООП позволяет разрабатывать системы, пригодные для многократного использования. Такие компоненты могут быть созданы и отлажены как независимые программные единицы, в изоляции от других частей прикладной программы.
    Многократно используемые программные компоненты позволяют разработчику иметь дело с проблемами на более высокой ступени абстрагирования. Мы можем определять и манипулировать объектами просто в терминах сообщений, которые они распознают, и работы, которую они выполняют, игнорируя детали реализации.
    Что читать дальше
    Я отметил ранее, что Алан Кей считается отцом объектно-ориентированного программирования. Подобно многим простым высказываниям, данное утверждение выдерживает критику лишь отчасти. Сам Кей [Kay 1993] считает, что его вклад состоит преимущественно в разработке языка Smalltalk на основе более раннего языка программирования Simula, созданного в Скандинавии в 60-х годах [Dahl 1966,
    Kirkerud 1989]. История свидетельствует, что большинство принципов объектно- ориентированного программирования было полностью разработано создателями языка
    Simula, но этот факт в значительной степени игнорировался профессионалами до тех пор, пока они (принципы) не были вновь открыты Кеем при разработке языка программирования Smalltalk. Пользующийся широкой популярностью журнал Byte в 1981 году сделал многое для популяризации концепций, разработанных Кеем и его командой из группы Xerox PARC.
    Термин «кризис программного обеспечения», по-видимому, был изобретен Дугом Мак-
    Илроем во время конференции НАТО 1968 года по программным технологиям. Забавно, что мы находимся в этом кризисе и сейчас, по прошествии половины срока существования информатики как независимой дисциплины. Несмотря на окончание холодной войны, выход из кризиса программного обеспечения не ближе к нам, чем это было в 1968 году — см., к примеру, статью Гиббса «Хронический кризис программного обеспечения» в сентябрьском выпуске Scientific American за 1994 год [Gibbs 1994].
    До некоторой степени кризис программного обеспечения — в значительной мере иллюзия. Например, задачи, рассматривавшиеся как чрезвычайно сложные пять лет назад, редко считаются таковыми сегодня. Проблемы, которые мы желаем решить сейчас, ранее считались непреодолимыми — по-видимому, это показывает, что разработка программного обеспечения год от года прогрессирует.
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    Цитата американского лингвиста Эдварда Сапира (стр. 21) взята из статьи «Связь поведения и мышления с языком», перепечатанной в сборнике «Мышление и реальность»
    [Whorf 1956]. В нем содержится несколько интересных работ по связям между языком и процессом мышления. Я настоятельно рекомендую каждому серьезному студенту, занимающемуся компьютерными языками, прочитать эти статьи. Некоторые из них имеют удивительно близкое отношение к искусственным языкам.
    Другая интересная книга — это «Эффект алфавита» Роберта Логана [Logan 1986], которая объясняет в лингвистических терминах, почему логика и наука были разработаны на
    Западе, в то время как в течение веков Китай имел опережающую технологию. В более современном исследовании о влиянии естественного языка на информатику Дж. Маршалл
    Унгер [Unger 1987] описывает влияние японского языка на известный проект Пятого поколения компьютеров.
    Всеми признанное наблюдение, что язык эскимосов имеет много слов для обозначения типов снега, было развенчано Джоффри Паллумом в его сборнике статей по лингвистике
    [Pullum 1991]. В статье в Atlantic Monthly «Похвала снегу» (январь 1995) Каллен Мерфи указывал, что набор слов, используемый для обсуждения «снежной» тематики людьми, говорящими по-английски, по крайней мере столь же разнообразен, как и термины эскимосов. При этом, естественно, имеются в виду люди, для которых различия в типах снега существенны (преимущественно это ученые, которые проводят исследования в данной области).
    В любом случае данное обстоятельство не имеет значения для нашего обсуждения.
    Определенно истинно, что группы индивидуумов с общими интересами стремятся разработать свой собственный специализированный словарь и, будучи однажды созданным, он имеет тенденцию направлять мысли своих творцов по пути, который не является естественным для людей за пределами группы. Именно такова ситуация с ООП.
    Хотя объектно-ориентированные идеи могут, при надлежащей дисциплине, быть использованы и без объектно-ориентированных языков, использование их терминов помогает направить ум программиста по пути, который не очевиден без терминологии
    ООП.
    Мой рассказ является слегка неточным в отношении принципа Чёрча и машин Тьюринга.
    Чёрч фактически делал свое утверждение относительно рекурсивных функций
    [Church 1936], которые впоследствии оказались эквивалентными вычислениям, проводимым с помощью машин Тьюринга [Turing 1936]. В той форме, в которой мы его формулируем здесь, этот принцип был описан Клини, и им же было дано то название, под которым принцип теперь известен. Роджерс приводит хорошую сводку аргументов в защиту эквивалентности различных моделей вычислений [Rogers 1967].
    Если вы помните, именно шведский ботаник Карл Линней разработал идеи родов, видов и т. д. Это является прототипом схемы иерархической организации, иллюстрирующей наследование, поскольку абстрактная классификация описывает характеристики, свойственные всем классификациям. Большинство иерархий наследования следуют модели Линнея.
    Критика процедур как методики абстрагирования (поскольку они не способны обеспечить надлежащий механизм маскировки данных) была впервые проведена Вилльямом Вульфом и Мери Шоу [Wulf 1973] при анализе многочисленных проблем, связанных с использованием глобальных переменных. Эта аргументация была впоследствии расширена Дэвидом Хансоном [Hanson 1981].
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    Подобно многим словам, которые нашли себе место в общепринятом жаргоне, термин
    «объектно-ориентированный» используется гораздо шире своего фактического значения.
    Тем самым на вопрос: «Что такое объектно-ориентированное программирование?» очень непросто ответить. Бьорн Страуструп [Stroustrup 1988] не без юмора заметил, что большинство аргументов сводится к следующему силлогизму:

    X — это хорошо.

    Объектная ориентированность — это хорошо.

    Следовательно, X является объектно-ориентированным.
    Роджер Кинг аргументированно настаивал, что его кот является объектно- ориентированным. Кроме прочих своих достоинств, кот демонстрирует характерное поведение, реагирует на сообщения, наделен унаследованными реакциями и управляет своим вполне независимым внутренним состоянием.
    Многие авторы пытались дать строгое определение тех свойств языка программирования, которыми он должен обладать, чтобы называться объектно-ориентированным, — см., к примеру, анализ, проведенный Джозефиной Микалеф [Micallef 1988] или Питером
    Вегнером [Wegner 1986].
    Вегнер, к примеру, различает языки, основанные на объектах, которые поддерживают только абстрагирование (такие, как Ada), и объектно-ориентированные языки, которые поддерживают наследование.
    Другие авторы — среди них наиболее заметен Брэд Кокс [Cox 1990] — определяют термин ООП значительно шире. Согласно Коксу объектно-ориентированное программирование представляет собой метод или цель (objective) программирования путем сборки приложений из уже имеющихся компонент, а не конкретную технологию, которую мы можем использовать, чтобы достичь этой цели. Вместо выпячивания различий между подходами мы должны объединить воедино любые средства, которые оказываются многообещающими на пути к новой Индустриальной Революции в программировании. Книга Кокса по ООП [Cox 1986], хотя и написана на заре развития объектно-ориентированного программирования, и в силу этого отчасти устаревшая в отношении деталей, тем не менее является одним из наиболее читаемых манифестов объектно-ориентированного движения.
    Упражнения
    1. В объектно-ориентированной иерархии наследования каждый следующий уровень является более специализированной формой предыдущего. Приведите пример иерархии из повседневной жизни с этим свойством. Некоторые из иерархий, обнаруживаемые в реальной жизни, не являются иерархиями наследования.
    Укажите пример иерархии без свойства наследования.
    2. Посмотрите значение слова парадигма по крайней мере в трех словарях.
    Соотнесите эти определения с языками программирования.
    3. Возьмите задачу из реального мира (аналогичную пересылке цветов, рассмотренной ранее) и опишите ее решение в терминах агентов (объектов) и обязанностей.
    4. Если вы знакомы с двумя (или более) различными языками программирования, приведите пример, когда один язык направляет мысль программиста к определенному решению, а другой — стимулирует альтернативное решение.
    PDF created with pdfFactory Pro trial version www.pdffactory.com

    5. Если вы знакомы с двумя (или более) естественными языками, опишите ситуацию, когда один язык направляет говорящего в одном направлении, в то время как другой язык приводит к иному ходу мысли.
    1   2   3   4   5   6   7   8   9   ...   23


    написать администратору сайта