Thinking in Java, 2nd edition, Revision 11

©2000 by Bruce Eckel

[ Предыдущая глава ] [ Short TOC ] [ Содержание ] [ Индекс ] [ Следующая глава ]

1: Введение
в объекты

Развитие компьютерной революции идет из машины. Поэтому, развитие наших языков программирования также склоняется на сторону машины.

Но компьютеры не настолько машины, они понимаются как расширенные инструменты (“бациллы для ума”, как сказал Стив Добс (Steve Jobs)) и различные виды средств выражения. В результате инструмент, выглядит меньше всего как машина, а больше похож на часть нашего ума, а также на другие формы выражения, как письмо, рисование, скульптура, анимация и создание фильмов. Объектно-ориентированное программирование (ООП) - это часть движения в этом направлении использования компьютеров, как средства выражения.

Эта глава расскажет вам основные концепции ООП, включая обзор методов разработки. Эта глава и эта книга предполагает, что вы имеете опыт в процедурных языках программирования, но не обязательно в C. Если вы считает, что вам нужна большая подготовка в программировании и в синтаксисе C прежде, чем браться за эту книгу, вы должны поработать с книгой "Думаем на C: Основы для C++ и Java", которая есть на CD ROM, сопровождающим эту книгу, а также доступной на www.BruceEckel.com.

Эта глава является предпосылкой и дополнительным материалом. Многие люди не чувствуют себя комфортно в объектно-ориентированном программировании без первоначального понимания всей картины. Поэтому, существует много концепций, которые приведены здесь, чтобы дать вам целую картину ООП. Однако многие другие люди не понимают всей картины, пока они сначала не увидят некоторую механику; такие люди могут увязнуть и потеряться без некоторого кода, полученного своими руками. Если вы относитесь к этой последней группе и сначала хотите узнать спецификацию языка, можете свободно пропустить эту главу — пропуск с этого места не скажется на написании программ или на изучении языка. Однако со временем вы можете захотеть вернуться, чтобы пополнить свои знания, чтобы вы могли понимать, почему объекты так важны и как работать с ними.

Развитие абстракции

Все языки программирования обеспечивают абстракцию. Она может быть обсуждена как запутанная проблема, решаемая вами напрямую в зависимости от рода и качества абстракции. Под “родом” я понимаю “Что вы абстрагируете?” Сборный язык - это небольшая абстракция лежащей в основе машины. Многие созвучные “императивные” языки, которые сопровождаются (такие как Фортран, Бейсик и C) были абстракцией сборного языка. Эти языки являются большим улучшением собирающих языков, но их первичная абстракция остается необходима вам, чтобы думать в терминах структуры компьютера, а не в структуре проблемы, которую вы решаете. Программист должен установить ассоциацию между машинной моделью (в “области решения”, которая является местом, где вы моделируете проблему, как и компьютер) и моделью проблемы, которая действительно должна быть решена (в “пространстве проблемы”, где проблема существует). Усилие, необходимое для выполнения этой связи и факты, присущие языку программирования, производят программу, которая сложна для написания и дорога для сопровождения, а с другой стороны, создается эффект целой индустрии “методов программирования”.

Альтернативой к моделированию машины является моделирование проблемы, которую вы пробуете решить. Ранние языки программирования, такие как LISP и APL выбирают определенный взгляд на мир (“Все проблемы - это, в конечном счете, список” или “Все проблемы - это алгоритмы” соответственно). Пролог преобразует все проблемы в цепочку решений. Были созданы Языки для программирования ограниченной базы и для программирования манипуляций исключительно с графическими символами. (Позже стали тоже ограниченными.) Каждый из этих подходов - это хорошее решение для определенного класса проблем, которые они призваны решать, но когда вы выходите за пределы этой области, они становятся неудобными.

Объектно-ориентированным подход продвигается на шаг дальше, обеспечивая инструмент для программиста, представляющий элементы в пространстве проблемы. Это представление достаточно общее, чтобы программист не был скован определенным типом проблем. Мы ссылаемся на элементы в пространстве проблемы и на их представление в пространстве решения, как на “объект”. (Конечно, вам также необходимы другие объекты, которые не имеют аналогов в пространстве проблемы.) Идея в том, что программа позволяет адаптировать себя к языку проблемы путем добавления новых типов объектов, так что вы, читая код, описывающий решение, читаете слова, которые описывают проблему. Это более гибкая и мощная абстракция языка, чем те, что были ранее. Поэтому, ООП позволяет вам описать проблему в терминах проблемы, а не в терминах компьютера, где работает решение. Хотя здесь остается связь с компьютером. Каждый объект полностью выглядит как маленький компьютер, он имеет состояние и он может работать так, как вы скажете. Однако это не выглядит как плохая аналогия с объектом в реальном мире — они все имеют характеристики и характер поведения.

Некоторые разработчики языков решают, что объектно-ориентированное программирование само по себе не достаточно легко для решения всех проблем программирования и отстаивают комбинацию различных подходов в мультипарадигмовых языках программирования. [2]

Алан Кэй суммирует пять основных характеристик Смалтолка, первого удачного объектно-ориентированного языка, и одного из языков, основанного на Java. Эти характеристики представлены в чистых подходах к объектно-ориентированному программированию:

  1. Все есть объект. Думать об объектах, как об особенных переменных; они хранят данные, но вы можете “сделать запрос” к такому объекту, попросив его самого выполнить операцию. Теоретически вы можете взять любой умозрительный компонент в проблеме, которую вы пробуете решить (собак, дома, услугу и т.п.) и представить его как объект в вашей программе.
  2. Программа - это связка объектов, говорящих друг другу что делать, посылая сообщения. Чтобы сделать запрос к объекту, вы “посылаете сообщение” этому объекту. Правильнее вы можете думать о сообщении, как о запросе на вызов функции, которая принадлежит определенному объекту.
  3. Каждый объект имеет свою собственную память, отличную от других объектов. Говоря другими словами, вы создаете объект нового вида, создавая пакет, содержащий существующие объекты. Поэтому, вы можете построить сложные связи, пряча их позади простых объектов.
  4. Каждый объект имеет тип. Другими словами, каждый объект является экземпляром класса, где “класс” - это синоним “типа”. Большинство важных различий характеристик класса в том, “Какие сообщение можете вы посылать ему?”
  5. Все объекты определенного типа могут принимать одинаковые сообщения. Это действительно важное утверждение, как вы увидите позднее. Так как объект типа “круг” также является объектом типа “форма”, круг гарантированно примет сообщения формы. Это означает, что вы можете писать код, который говорит форме и автоматически управляет всем, что соответствует описанию формы. Это представляется одной из большинства полезных концепций ООП.

Объект имеет интерфейс

Аристотель, вероятно, был первым, кто начал старательно изучать концепцию типа; он говорил: “класс рыбы и класс птицы”. Идея, что все объекты, хотя являются уникальными, также являются частью класса объектов, которые имеют общие характеристики и характер поведения, что было использовано в первом объектно-ориентированном языке Симула-67 с этим основополагающим словом класс, которое ввело новый тип в программу.

Симула, как показывает его название, был создан для разработки симуляторов, таких как классическая “проблема банковского кассира”. В ней вы имеете группу кассиров, клиентов, счетов, переводов и денег — множество “объектов”. Объекты, которые идентичны, за исключением своих состояний во время исполнения программы, группируются вместе в “классы объектов”. Так и пришло ключевое слово класс. Создание абстрактных типов данных (классов) - это основополагающая концепция в объектно-ориентированном программировании. Абстрактные типы данных работают почти так же, как и встроенные типы: вы можете создавать переменные этого типа (называемые объектами или экземплярами, если говорить объектно-ориентированным языком) и манипулировать этими переменными (это называется посылка сообщений или запрос; вы посылаете сообщение и объект смотрит что нужно с ним делать). Члены (элементы) каждого класса распределяются с некоторой унифицированностью: каждый счет имеет баланс, каждый кассир может принимать депозит и т.п. В то же время, каждый член имеет свое собственное состояние, каждый счет имеет различный баланс, каждый кассир имеет имя. Поэтому, кассиры, клиенты, счета, переводы и т.п. могут быть представлены как уникальная сущность в компьютерной программе. Эта сущность и есть объект, а каждый объект принадлежит определенному классу, который определяет характеристики и черты поведения.

Так, несмотря на то, что мы реально делаем в объектно-ориентированном программировании - это создание новых типов, фактически все объектно-ориентированные языки используют ключевое слово “класс”. Когда вы видите слово “тип”, то думайте “класс” и наоборот. [3]

Так как класс описывает набор объектов, которые имеют идентичные характеристики (элементы данных) и черты поведения (функциональность), класс реально является типом данных, потому что, например, число с плавающей точкой также имеет набор характеристик и черт поведения. Отличия в том, что программист определяет класс исходя из проблемы, чтобы представить блок для хранения в машине. Вы расширяете язык программирования, добавляя спецификации новых типов данных, которые вам необходимы. Эта система программирования приветствует новые классы и заботится за ними всеми, выполняя проверку типа, как и для встроенных типов.

Объектно-ориентированный подход не ограничивается построением симуляторов. Независимо от того, согласны вы или нет, что любая разрабатываемая вами программа - это эмуляция системы, использование ООП техники может легко снизить большую часть проблем для упрощения решения.

Как только класс создан, вы можете создать столько объектов этого класса, сколько захотите, а затем манипулировать этими объектами так, как если бы они являлись элементами, которые существуют в проблеме, которую вы пробуете решить. Несомненно, одно из предназначений объектно-ориентированного программирования - это создание связей один-к-одному между элементами в пространстве проблемы и объектами в пространстве решения.

Но как заставить объект стать полезным для вас? Должен существовать способ сделать запрос к объекту, чтобы он что-то сделал, например, законченную транзакцию, что-то нарисовал на экране или включил переключатель. Каждый объект может удовлетворять только определенные запросы. Запросы, которые вы можете сделать к объекту, определяются его интерфейсом и типом, который определяет интерфейс. Простым примером может стать представление электрической лампочки:


Light lt = new Light();
lt.on();

Интерфейс определяет какой запрос вы можете выполнить для определенного объекта. Однако должен существовать определенный код, для удовлетворения этого запроса. Здесь, наряду со спрятанными данными, содержится реализация. С точки зрения процедурного программирования это не сложно. Тип имеет функциональные ассоциации для каждого возможного запроса и, когда вы делаете определенный запрос к объекту, вызывается такая функция. Этот процесс обычно суммируется и можно сказать, что вы “посылаете сообщение” (делаете запрос) объекту, а объект определяет, что он должен сделать с этим сообщением (он исполняет код).

В этом промере имя типа/класса - Light, имя этого обычного объекта Light - lt, а запросы, которые вы можете сделать для объекта Light - это включить его, выключить, сделать ярче или темнее. Вы создаете объект Light, определяя “ссылку” (lt) для объекта и вызываете new для запроса нового объекта этого типа. Для отправки сообщения объекту вы объявляете имя объекта и присоединяете его к сообщению запроса, разделив их (точкой). С точки зрения пользователя, предварительное определение класса - более красивый способ программирования с объектами.

Диаграмма, показанная выше, следует формату Унифицированного Языка Моделирования (Unified Modeling Language (UML). Каждый класс представляется ящиком, с именем типа в верхней части ящика и членами - данными, которые вы описываете в средней части ящика, а члены - функции (принадлежащие объекту функции, которые принимают сообщения, которые вы посылаете этому объекту) в нижней части ящика. Чаще всего только имя класса и публичные члены - функции показаны в диаграмме разработки UML, так что средняя часть не показывается. Если вы интересуетесь только именем класса, нижние части нет необходимости показывать.

Спрятанная реализация

Очень полезно разбивать участников на создателей класса (которые создают новые типы данных) и программисты-клиенты [4] (потребители классов, которые используют типы данных в своих приложениях). Задача программистов-клиентов - это сбор полного набор классов для использования в быстрой разработке приложений. Задача создателей классов - построить класс так, чтобы показать только то, что необходимо программисту-клиенту, а все остальное оставить спрятанным. Почему? Потому если он это спрячет, программист-клиент не сможет использовать это. Это означает, что создатель класса может изменить спрятанную часть, когда он захочет, не беспокоясь, что это повлияет на кого-то еще. Спрятанная часть обычно представляет хрупкую часть объекта, которая может быть легко повреждена неосторожным или неосведомленным программистом-клиентом, так что прятанье реализации снижает ошибки программы. Концепция спрятанной реализации не может быть переоценена.

Во всех отношениях важно иметь границы, которые соблюдаются всеми участвующими сторонами. Когда вы создаете библиотеку, вы создаете отношения с программистом-клиентом, который тоже является программистом, то таким, который собирает приложение, используя вашу библиотеку, может быть, строит большую библиотеку.

Если все члены класса доступны всем, то программист-клиент может сделать что-нибудь с таким классом и нет способа ввести ограничения. Даже если вы можете реально предоставить программисту-клиенту способ не прямого управления членами вашего класса, без доступа к содержимому, нет способа предотвратить это. Все в мире не защищено.

Так что первая причина для управления доступом - это держать руки программиста-клиента подальше от той части, которой он не должен касаться, которая необходима для внутреннего механизма типа данных, но не является необходимым пользователю интерфейсом для решения его обычных проблем. Это важное свойство для пользователей, поскольку они легко видят, что важно для них, а что они могут игнорировать.

Вторая причина для управления доступом - это позволить разработчику класса изменить внутреннюю работу класса, не заботясь о том, что это повлияет на программиста-клиента. Например, вы можете реализовать определенный класс простым способом для легкости разработки, а, затем, решите, что необходимо переписать его, чтобы он работал быстрее. Если интерфейс и реализация легко четко разделены и защищены, вы легко можете выполнить это.

Java использует три ясных ключевых слова для установки границ в классе: public, private и protected. Их использование и значение достаточно ясно. Эти спецификаторы доступа определяют кто может использовать приводимое далее определение. public означает, что приводимое далее определение доступно всем. Ключевое слово private в одних руках означает, что никто не может получить доступ к этому определению, за исключением вас, создателя типа внутри функций-членов этого типа. private - это каменная стена между вами и программистом-клиентом. Если кто-то попробует получить доступ к private члену, он получит ошибку времени компиляции. protected работает так же как и private с тем исключением, что наследующие классы имеют доступ к protected членам, то не к private членам. О наследовании будет сказано несколько слов.

Java также имеет идентификатор доступа “по умолчанию”, который вступает в игру, если вы не используете ни один из вышеупомянутых спецификаторов. Это иногда называется “дружественным” доступом, потому что классы могут получить доступ к дружественным членам этого же пакета, но вне пакета те же самые дружественные члены становятся private.

Повторное использование реализации

Как только класс был создан и протестирован, он должен представлять (в идеале) полезный блок кода. Как оказывается это повторное использование далеко не так просто, как многие могут надеяться, для этого необходим опыт и понимание, чтобы воспроизвести хороший дизайн. Но как только вы имеете дизайн, он может быть использован повторно. Повторное использование кода - это одно из великолепных преимуществ, которое обеспечивает объектно-ориентированное программирование.

Простой способ повторного использования класса - это прямое использование объекта этого класса, но вы можете также поместить объект этого класса внутрь нового класса. Мы называем это “создание объекта - члена класса”. Ваш новый класс может содержать любое число объектов любого типа в любой комбинации, которая вам необходимо для достижения той функциональности, которая вам нужна в вашем новом классе. Поскольку вы составляете новый класс из существующих классов, эта концепция называется композицией (composition) (или более обще: агрегирование (aggregation)). Композиция часто объясняет нам “существование” связей, как, например, в “автомобиле существует машина”.

(Приведенная выше UML диаграмма показывает композицию с закрашенным ромбом, который представлен рядом с автомобилем. Обычно я буду использовать упрощенную форму: просто линию без ромба, чтобы показать связь. [5])

Композиция придает значительную гибкость. Объекты-члены вашего нового класса обычно приватные, что делает их недоступными для программиста-клиента, который будет использовать класс. Это позволяет вам менять эти члены без перераспределения существующего клиентского кода. Вы можете также изменить член-объект во время выполнения, динамическое изменение остается позади вашей программы. Наследование, которое описывает следующий способ, не имеет этой гибкости, так как компилятор должен поместить ограничения времени компиляции на классы, создаваемые путем наследования.

Поскольку наследование важно в объектно-ориентированном программировании, на нем чаще акцентируют внимание, а программисты-новички могут заразиться идеей, что наследование должно использоваться везде. В результате может получиться неуклюжий и чрезмерно сложная разработка. Вместо этого вы должны сначала посмотреть на композицию при создании нового класса, так как это проще и гибче. Если вы выберите этот подход, ваш дизайн будет чище. Как только вы наберете определенный опыт, это будет очевидно для вас, когда вам нужно наследование.

Наследование:
повторное использование интерфейса

Сама по себе идея заключается в том, что объект является удобным инструментом. Он позволяет вам оформлять данные и функциональность вместе, согласно концепции, так что вы можете представить подходящую идею проблемной области раньше, чем начать усиленно использовать идиому лежащей в основе машины. Эта концепция является фундаментом при программировании с использованием ключевого слова class.

Однако это выглядит довольно жалко, решать все проблемы путем создания класса, а затем усиленно создавать качественно новый класс, который может иметь сходную функциональность. Гораздо лучше, если мы можем взять существующий класс, размножить его и создать дополнения и модификации клона. Это результат, который вы получаете при наследовании, за исключением того, что если класс-оригинал (называемый базовый или супер или родительский класс) меняется, модифицируется и “клон” (называемый производный или наследуемый или sub или наследник класс), отражая эти изменения.

Стрелка на приведенной выше UML диаграмме указывает от класса-наследника на базовый класс. Как вы видите, может быть более одного наследуемого класса.)

Тип - это больше чем описание ограничений для набора объектов. Это также взаимные отношения с другими типами. Два типа могут иметь общие характеристики и черты поведения, но один тип может содержать больше характеристик, чем другой, а может обрабатывать больше сообщений (или обрабатывать их иначе). Наследование выражает эту схожесть между типами, используя концепцию базового типа и наследуемого типа. Базовый тип содержит все характеристики и черты поведения, которые есть у всех типов, наследуемых от этого. Вы создаете базовый тип для образования ядра вашей идеи для некоторых объектов в вашей системе. От базового типа вы образуете другие типы для создания разных способов, которыми данное ядро может быть реализовано.

Например, машина по переработке мусора сортирует кусочки мусора. Базовый тип - “мусор”, а каждый кусочек мусора имеет вес, объем и так далее, и может быть разрезан, расплавлен или растворен. Для этих более специфичных типов мусора наследуются типы, которые могут иметь дополнительные характеристики (бутылки имеют цвет) или черты поведения (алюминий может быть раздавлен, а сталь может магнитится). Вдобавок, некоторые черты поведения могут отличаться (объем бумаги зависит от типа и состояния). Используя наследование, вы можете создать иерархические типы, которые выражают проблему, которую вы пробуете решить в терминах своих типов.

Второй пример - это классический пример с “формой”, возможно, используемый компьютерной системе разработки или в игровых симуляторах. Базовый тип - “форма”, а каждая форма имеет размер, цвет, положение и так далее. Каждая форма может быть нарисована, стерта, перемещена, окрашена и т.д. От нее образуются (наследуются) специфические типы: окружность, квадрат, треугольник и так далее, каждый из которых может иметь дополнительные характеристики и черты поведения. Определенные формы могут быть зеркально отражены, например. Некоторые черты поведения могут отличаться, так если вы хотите посчитать площадь формы. Формы иерархически объединяет и похожие и различные черты форм.

Поиск решения в терминах проблемы чрезвычайно полезно, так как у вас нет необходимости массы промежуточных моделей, чтобы перейти от описания проблемы к описанию решения. У объектов в первичной модели типы иерархические, так что вы прямо переходите от описания системы реального мира к описанию системы в кодах. Несомненно, одно из затруднений людей, работающих с объектно-ориентированной разработкой, в том, что слишком просто пройти от начала до конца. Разум, натренированный на поиск сложных решений часто сначала стопорится на такой простоте.

Когда вы наследуете от существующего типа, вы создаете новый тип. Этот новый тип содержит не все члены существующего типа (private члены спрятаны и недоступны), более важно, это дублирование интерфейсов базового класса. То есть, все сообщения, которые вы можете послать объекту базового класса, вы можете послать их объекту наследуемого класса. Так как мы знаем тип класса, которому мы посылаем сообщения, это означает, что наследуемый класс того же типа, что и базовый класс. В предыдущем примере “окружность - это форма”. Такая эквивалентность типов через наследование - это один из основных шлюзов для понимания значения объектно-ориентированного программирования.

Так как оба класса: базовый и наследованный имеют одинаковый интерфейс, должна быть реализация для работы с этим интерфейсом. Таким образом, должен быть определенный код, который выполняется, когда объект принимает определенное сообщение. Если вы просто наследуете класс и ничего больше не делаете, методы интерфейса базового класса переходят без изменения в наследованный класс. Это означает, что объект наследованного класса имеет не только тот же тип, он имеет такие же черты поведения, что обычно не интересно.

Вы имеете два способа сделать ваш новый класс отличным от оригинального базового класса. Первый достаточно прямой: вы просто добавляете несколько новый функций к наследуемому классу. Это означает, что базовый класс просто не делает столько, сколько вам нужно, так что вы добавляете больше функций. Это простое и примитивное использование наследования, одновременно является законченным решением вашей проблемы. Однако вы должны взглянуть более пристально на возможно, что вашему базовому классу может не хватать этих дополнительных функций. Этот процесс обнаружения и повторения при разработке встречается регулярно в объектно-ориентированном программировании.

Хотя наследование иногда может подразумевать (особенно в Java, где ключевое слово, означающее наследование, это extends), что вы хотите добавить новые функции к интерфейсу, это не всегда так. Второй, наиболее важный способ сделать ваш класс отличным, заключается в изменении поведения существующей функции базового класса. Это называется перегрузкой.

Для перегрузки функции вы просто создаете новое определение для функции в наследуемом классе. Вы говорите, “Я использую ту же функцию интерфейса, но я хочу делать что-то другое для моего нового типа.

Отношения ЯВЛЯЕТСЯ против ПОХОЖ НА

Здесь приведена некоторая дискуссия, которая может случиться по поводу наследования: Должно ли наследование только перегружать функции базового класса (и не добавлять новые функции-члены, которых нет в базовом классе)? Это означает, что наследуемый тип точно того же типа, что и базовый класс, так как он имеет точно такой же интерфейс. В результате вы можете заменить объект наследуемого класса на объект базового класса. Это может означать чистую замену и это часто называется принципиальной заменой. Это идеальный способ использования наследования. Мы часто ссылаемся на взаимосвязи между базовым классом и наследуемыми классами. В этом случае мы имеем взаимоотношение ЯВЛЯЕТСЯ, так как вы можете сказать, что “окружность является формой”. Проверьте наследование, чтобы определить, можете ли вы сказать о классе, что имеется взаимоотношение ЯВЛЯЕТСЯ.

Иногда, когда вы должны добавить к наследуемому типу новый элемент интерфейса, так что расширение интерфейса создает новый тип. Новый тип все равно может быть представлен базовым типом, но представление не точное, поскольку новые функции не доступны у базового типа. Это может быть описано как взаимоотношение ПОХОЖ НА[6]. Новый тип имеет интерфейс старого типа, а так же содержит другие функции, так что вы не можете реально сказать, что он такой же. Например, рассмотрим кондиционеры. Предполагая, что ваш дом имеет все регуляторы для охлаждения, так что вам нужен интерфейс, который позволит вам регулировать охлаждение. Вообразите, что кондиционер упал и разбился и вы заменили его на такой же, но с нагревающим вентилятором, который может производить и холод и тепло. Такой аппарат ПОХОЖ НА кондиционер, но он может делать больше. Поскольку система управления в вашем доме предназначена только для регулировки охлаждения, это ограничивает коммуникацию с охлаждающей частью нового объекта. Интерфейс нового объекта был расширен, а существующая система не знает ничего, за исключением оригинального интерфейса.


Конечно, как вы видите, становиться достаточно ясно, что базовый класс “система охлаждения” не достаточно общий, и должен быть переименован в “систему управления температурой”, чтобы он также мог включать нагреватели — после чего замена принципиально сможет работать. Однако приведенная выше диаграмма является примером того, что случается при разработке и в реальном мире.

Как вы видите, замену принципиальную легче почувствовать в этом подходе (чистая замена) - это только способ делать вещи, а фактически это лучшие, если вы делаете работу не этим способом. Но вы найдете, что существуют задачи, когда совершенно ясно, что вы должны добавить новые функции к интерфейсу наследуемого класса. При просмотре оба класса должны быть достаточно понятны.

Взаимозаменяемые объекты
с полиморфизмом

Когда работаете с иерархическими типами, вы часто хотите трактовать объект не как объект определенного типа, а как объект его базового типа. Это позволит вам написать код, который не зависит от определенного типа. В примере с формой: функции манипулируют общей формой, не заботясь о том, является ли она окружностью, треугольником или какой-то другой формой, которая еще не была определена. Все формы могут быть нарисованы, стерты и перемещены, так что эти функции просто посылают сообщения объекту формы. Они не беспокоятся о том, как объект обходится с сообщением.

Такой код не изменяется при добавлении новых типов, а добавление новых типов - это наиболее общий способ в объектно-ориентированной программе для расширения и получения новых структур. Например, вы можете наследовать новый подтип формы, называемый пятиугольник, не модифицируя функции, которые работают только с родительской формой. Эта способность расширения программы облегчает наследование новых подтипов и является важной, потому что это в общем случае существенно облегчает разработку, снижая стоимость поддержки программы.

Однако, существует проблема, когда пробуют трактовать унаследованный тип как объект базового типа (окружность - как форма, велосипед - как транспортное средство, баклана - как птицу и т.п.). Если функция предназначена для сообщения родительской форме о необходимости нарисовать себя или родительскому транспортному средству - управлять, или родительской птице - лететь, компилятор не может знать точно во время компиляции, какой кусок кода будет исполнен. Это то место когда сообщение послано, а программист не хочет знать, какой кусок кода будет исполнен. Функция рисования может быть одинаково применена к окружности, к квадрату или к треугольнику, и объект должен выполнять правильный код в зависимости определенного для него типа. Если вы не знаете какая часть кода будет выполнена, то когда вы добавляете новый подтип, то выполняемый код может отличаться, и это не потребует изменений при вызове функции. Поэтому, компилятор не может знать точно какая часть кода выполнилась, но что делает это? Например, в приведенной диаграмме КонтроллерПтицы объект работает только с родительским объектом Птица и не знает какой точно тип имеется. Это удобно с точки зрения КонроллераПтицы, так как нет необходимости писать специальный код для определения точного типа Птицы, с которой идет работа, или поведения Птицы. Если это так, то когда вызывается move( ) при игнорировании определенного типа Птицы, как воспроизведется правильное поведение (бег, полет или плаванье Гуся и бег или плаванье Пингвина)?

Ответ напрямую вытекает из объектно-ориентированного программирования: компилятор не может выполнить вызов функции в традиционном понимании. Вызов функции, генерируемый не ООП компилятором, становится причиной того, что вызывается ранее связывание, термин, который вы могли не слышать ранее, поскольку вы никогда не думали об этом иным способом. Это означает, что компилятор генерирует вызов, указывая имя функции, а линковщик транслирует этот вызов в абсолютные адреса кода выполнения. В ООП, программа не может определить адрес кода, пока не начнется время выполнения, так что необходимы другие схемы, когда сообщение посылается родительскому объекту.

Для решения проблемы объектно-ориентированные языки используют концепцию позднего связывания. Когда вы посылаете объекту сообщение, код, который будет вызван, не определяется, пока не начнется время выполнения. Компилятор не убеждается, что функция существует, а выполняет проверку типа аргумента и возвращаемого значения (языки, в которых это так, называются weakly typed), но он не знает точный код для выполнения.

Для выполнения позднего связывания Java использует специальный бит-код вместо абсолютных вызовов. Этот код рассчитывает адрес тела функции, используя информацию, хранимую в объекте (этот процесс более детально описан в Главе 7). Таким образом, каждый объект ведет себя различно, в соответствии с содержимым этого специального бит-кода. Когда вы посылаете объекту сообщение, объект фактически вычисляет что делать с этим сообщением.

В некоторых языках (обычно, в С++) вы должны явно указать, что вы хотите функцию, имеющую гибкость со свойствами позднего связывания. В таких языках, по умолчанию, функции-члены связываются не динамически. Это является причиной проблем, так что в Java динамическое связывание используется по умолчанию и вам нет необходимости вспоминать о добавлении дополнительных ключевых слов, чтобы получить полиморфизм.

Вернемся к примеру с формой. Дерево классов (все базируются на одном и том же интерфейсе) было показано ранее в этой главе. Для демонстрации полиморфизма мы хотим написать простой кусок кода, который игнорирует специфические детали типа и общается только с базовым классом. Такой код отделяется от информации определения типов и это проще для написания и легче в понимании. А если будет добавлен новый тип, например Шестиугольник, написанный вами код будет работать, как если бы новый типа был Форма, как это сделано для существующих типов. Таким образом, программа расширяема.

Если вы пишете метод в Java (скоро вы выучите как это делать):

void doStuff(Shape s) {
  s.erase();
  // ...
  s.draw();
}

Эта функция говорит любой Форме, так что это не зависит от специфического типа объекта, который рисуется и стирается. Если в некоторой части программы мы используем функцию doStuff( ) :

Circle c = new Circle();
Triangle t = new Triangle();
Line l = new Line();
doStuff(c);
doStuff(t);
doStuff(l);

Вызов doStuff( ) автоматически работает правильно, не зависимо от точного типа объекта.

Это, фактически, красивый и удивительный фокус. Рассмотри строку:

doStuff(c);

Что случится здесь, если в функцию будет передана Окружность, которая ожидает Форму. Так как Окружность является Формой, это можно трактовать, как передачу Формы в doStuff( ). Так что любое сообщение, которое может послать doStuff( ) Форме, Окружность может принять. Так что это полностью безопасно и самое логичное, что можно сделать.

Мы называем этот процесс, когда наследуемый тип трактуется как базовый, обратное преобразование (upcasting). Название преобразование (cast) использовалось в смысле преобразования к шаблону, а Обратное (up) исходит от способа построения диаграммы наследования, которая обычно упорядочена так: базовый тип вверху, а наследуемые классы развертываются вниз. Таким образом, преобразование к базовому типу - это перемещение вверх по диаграмме наследования: “обратное преобразование”.


Объектно-ориентированное программирование содержит кое-где обратное преобразование, поскольку, так как вы отделяете себя от знания точного типа, вы работаете с этим. Посмотрите на код doStuff( ):

  s.erase();
  // ...
  s.draw();

Заметьте, что это не значит сказать: “Если ты Окружность, сделай это, если ты Квадрат, сделай то и т.д.” Если вы пишете код такого рода, который проверяет все возможные типы, которыми может быть Форма, это грязный способ и вы должны менять его всякий раз, когда добавляете новый сорт Формы. Здесь вы просто говорите: “Ты - Форма, я знаю, что ты можешь стирать erase( ) и рисовать draw( ) сама. Сделай это и правильно позаботься о деталях.”

Что впечатляющего в коде doStuff( ), так это то, что все почему-то правильно происходит. Вызов draw( ) для Окружности становится причиной вызова другого кода, чем при вызове draw( ) для Квадрата или для Линии, но когда сообщение draw( ) посылается к анонимной Форме, происходит правильное поведение, основанное на действительном типе Формы. Это удивительно, поскольку, как упомянуто ранее, когда компилятор Java компилирует код для doStuff( ), он не может знать точный тип, с которым идет работа. Так что обычно вы не ожидаете этого до конца вызова версии erase( ) и draw( ) для базового класса Формы, и для определенного класса Окружности, Квадрата или Линии. Тем не менее, правильная работа происходит по причине полиморфизма. Компилятор и система времени выполнения управляет деталями. Все что вам нужно - это знать что случится, и что более важно, как с этим работать. Когда вы посылаете объекту сообщение, объект будет делать правильные вещи, даже если используется обратное преобразование.

Абстрактные базовые классы и интерфейсы

Часто при разработке вы хотите, чтобы базовый класс представлял только интерфейс для наследуемых классов. Это значит, что вы не хотите, чтобы кто-то реально создавал объект базового класса, а только выполнял обратное преобразование к нему, чтобы использовать интерфейс. Это достигается при создании абстрактного класса, используя ключевое слово abstract. Если кто-либо попробует создать объект абстрактного класса, компилятор предотвратит это. Это инструмент для навязывания определенного дизайна.

Вы также можете использовать ключевое слово abstract для описания методов, которые не будут реализованы сразу — как напоминание “это интерфейсная функция для всех типов, наследуемых от этого класса, но в этом месте она не имеет реализации”. Абстрактный метод может быть создан только внутри абстрактного класса. При наследовании такой метод должен быть реализован или наследуемый класс также станет абстрактным. Создание абстрактных методов позволяет вам помещать методы в интерфейс и не заботиться о возможности создания бессмысленного кода для тела этого метода.

Ключевое слово interface дает концепцию абстрактного класса одним шагом, предотвращая будущее определения функций. Интерфейс - очень удобный и часто используемый инструмент, который обеспечивает отличное разделение интерфейса и реализации. В дополнение вы можете комбинировать много интерфейсов вместе, если хотите, в то время как наследование от нескольких обычных или абстрактных классов не возможно.

Видимость и время жизни объектов

Технически, ООП - это просто абстрактные типы данных, наследование и полиморфизм, но другие свойства могут быть не менее важны. Оставшаяся часть раздела будет описывать эти особенности.

Один из большинства важных факторов - это способ создания и разрушения объектов. Где находятся данные объекта и как регулируется время жизни объекта? Существуют различные философии, работающие в этой области. C++ использует подход, который эффективен при управлении для большинства важных свойств, так что программист имеет выбор. Для максимальной скорости выполнения хранение и время жизни может определяться при написании программы, помещая объекты в стек (они иногда называется автоматические или контекстные переменные) или в области статического хранения. Это дает приоритет скорости резервирования и освобождения хранимого и управление этим может быть очень драгоценно в некоторых ситуациях. Однако вы приносите в жертву гибкость, поскольку вы должны знать точное количество, время жизни и тип объекта при написании программы. Если вы пробуете более общую проблему, такую как вспомогательный компьютерный дизайн, управление складом или управление воздушным движением, это большое ограничение.

Второй способ - создания объектов динамически в области памяти, называемой кучей. В этом способе вы не знаете до выполнения, сколько объектов необходимо, какого их время жизни или какой их точный тип. Это определяется в момент выполнения программы. Если вам необходим новый объект, вы просто создаете его в куче в тот момент, когда вам это необходимо. Поскольку хранилище управляется динамически во время выполнения, количество времени, необходимое для резервации места в куче значительно больше, чем при реализации хранения в стеке. (Создание хранилища в стеке часто - это простая инструкция перемещения указателя стека вниз, а другая инструкция - перемещение вверх.) Динамический способ создания делает общие логические присвоения, через которые выражается объект, так что чрезмерные затраты при нахождении хранилища и его освобождении не будет иметь значительное влияние на создание объекта. Вдобавок, большая гибкость существенна для решения общих проблем программирования.

Java использует исключительно второй способ [7]. Каждый раз, когда вы хотите создать объект, вы используете ключевое слово new для создания динамического экземпляра этого объекта.

Однако есть другая способность - это время жизни объекта. С языками, которые позволяют объектам создаваться в стеке, компилятор определяет, как велик объект и когда он может быть автоматически разрушен. Однако если вы создаете его в куче, компилятор не имеет знаний о его времени жизни. В таких языках, как C++, вы должны определить программированием, когда разрушать объект, который может вызвать утечку памяти, если вы некорректно сделаете это (и это общая проблема программ на C++). Java обеспечивает особенность, называемую сборщиком мусора, который автоматически определяет, когда объект более не используется и разрушает его. Сборщик мусора часто более пригодный, так как он уменьшает число проблем, которые вы должны отслеживать и упрощает код, который вы должны написать. Более важно то, что сборщик мусора обеспечивает достаточно высокий уровень страховки от сложной проблемы утечки памяти (которая заставляет тормозиться многие проекты на C++).

Остаток этого раздела выглядит как дополнительные факторы, относительно времени жизни и области видимости объектов.

Cборщики и итераторы

Если вы не знаете, сколько объектов вам будет необходимо для решения проблемы, или сколько долго они будут существовать, то вы также не знаете, как хранить эти объекты. Откуда вы можете знать, сколько пространства необходимо для этих объектов? Вы не можете, так как эта информация не известна, пока не настанет время выполнения.

Решения большинства проблем в объектно-ориентированном дизайне выглядит дерзко: вы создаете типы других объектов. Новый тип объектов, который решает эту обычную проблему хранения ссылок на другие объекты. Конечно, вы можете сделать то же самое с помощью массива, который поддерживают многие языки. Но это гораздо большее. Этот новый объект, обычно называемый контейнер (также называется коллекцией, но библиотека Java использует этот термин в другом смысле, так что эта книга будет использовать “контейнер”), будет расширять себя при необходимости, чтобы аккомодировать все, что вы поместите внутрь него. Так что вы не должны знать, сколько объектов вы положили храниться в контейнер. Просто создайте объект контейнера и дайте ему заботится о деталях.

К счастью, хорошие языки ООП пришли с набором контейнеров, как часть пакета. В C++ это часть Стандартной Библиотеки C++, часто называемой Библиотекой Стандартных Шаблонов [Standard Template Library (STL)]. Object Pascal имеет контейнеры в Библиотеке Визуальных Компонент [Visual Component Library (VCL)]. Smalltalk имеет более полный набор контейнеров. Java также имеет контейнеры в своей стандартной библиотеке. В некоторых библиотеках общие контейнеры достаточно хороши для всех надобностей, а в других (например, в Java) библиотека имеет различные типы контейнеров для разных надобностей: вектор (называемый ArrayList в Java) для последовательного доступа ко всем элементам, и связанный список для последовательной вставки в любое место, например, вы можете выбрать обычный тип, который удовлетворяет вашим требованиям. Библиотеки контейнеров могут также включать наборы, очереди, хэш-таблицы, деревья, стеки и т.п.

Все контейнеры имеют определенный способ для помещения вещей внутрь и получения вещей назад. Обычно это функции для добавления элемента в контейнер, а другая - для извлечения этого элемента. Но получение элемента может стать более пробламетично, поскольку функция простого выбора ограничена. Что если вы захотите манипулировать или сравнивать набор элементов в контейнере не по одному?

Решением этого является итератор, который является объектом, чья работа - это выбор элементов из контейнера и представление их пользователю итератора. Как класс, он также обеспечивает уровень абстракции. Эта абстракция может быть использована для разделения деталей контейнера от кода, который имеет доступ к контейнеру. Контейнер через итератор абстрагируется до простой последовательности. Итератор позволяет вам обработать эту последовательность, не заботясь о лежащей в основе структуре — является ли она ArrayList, LinkedList, Stack или чем-то еще. Это дает вам гибкость для легкой смены лежащей в основе структуры данных без переделки кода вашей программы. Java началась (в версиях 1.0 и 1.1) со стандартного итератора, называемого Enumeration, для всех контейнерных классов. В Java 2 добавлено много более сложных библиотек контейнеров, которые содержат итераторы, называемые Iterator, который делает много больше, чем старый Enumeration.

С точки зрения разработки, все что вам нужно - это последовательность, которой можно манипулировать для решения вашей проблемы. Если последовательность простых типов удовлетворяет всем вашим требованиям, то нет смысла иметь другие типы. Есть два довода, почему вам необходимы контейнеры. Во-первых, контейнеры обеспечивают различные типы интерфейсов и внешних воздействий. Стек имеет различные интерфейсы и поведение, в отличие от очереди, которая отличается от набора или списка. Один из них может обеспечивать более гибкое решение вашей проблемы, чем другие. Во-вторых, различные контейнеры имеют различную эффективность для определенных операций. Лучший пример - это ArrayList и LinkedList. Оба контейнера - это простая последовательность, которые могут иметь одинаковые интерфейсы и внешнее поведение. Но определенные операции могут иметь радикальное отличие в стоимости. Случайный доступ к элементам в ArrayList - это операция постоянная по времени. Она занимает примерно одно и то же время не зависимо от выбираемого вами элемента. Однако в LinkedList очень дорого перемещаться по списку элементов в случайном порядке, и занимает больше времени при нахождении элемента, чем при переборе их вниз по списку. С другой стороны, если вы хотите вставить элемент в середину последовательности, это дешевле в LinkedList, чем в ArrayList. Есть и другие операции, имеющие различия в эффективности, в зависимости от лежащей в основе структуры последовательности. В фазе разработки вы можете начать с LinkedList и, когда будете настраивать производительность, сменить на ArrayList. Поскольку есть абстракция через итераторы, вы можете сменить один на другой с минимальной переработкой вашего кода.

В завершение, помните, что контейнеры только хранилище для помещения объектов. Если такое хранилище решает все ваши проблемы, не имеет значение как оно реализовано (основная концепция для большинства типов объектов). Если вы работаете со средой программирования, имеющей встроенную возможность, обусловленную другими факторами, то стоимость различается для ArrayList и LinkedList может не иметь значения. Вам может понадобиться только один из типов последовательности. Вы можете вообразить “совершенный” абстрактный контейнер, который может автоматически изменять лежащую в основе реализацию в зависимости от способа его использования.

Простая корневая иерархия

Одна из проблем в ООП становиться особенно явной после введения как в C++, где все классы должны быть, в конце концов, наследованы от единственного базового класса. Java (как фактически и во всех ООП языках) отвечает “да” и имя этого единственного базового класса Object. Это позволяет свести иерархию к единому корню.

Все объекты в иерархии с единым корнем имеют общий интерфейс, так что они все приводятся к единому типу. Альтернатива (пришедшая из C++) в том, что вы не знаете, что все исходит из одного и того же фундаментального типа. С точки зрения обратной совместимости это лучше соответствует модели C и можно подумать, что ограничивает меньше, но когда вы хотите применить полное объектно-ориентированное программирование, вы должны построить вашу собственную иерархию, чтобы обеспечить такое же соглашение, которое применено в других языках ООП. А в любых новых библиотеках классов вы получаете, что используются некоторые другие несовместимые интерфейсы. Это требует введения (и возможности множественного наследования) работы с новыми интерфейсами в вашей разработке. Достигнута ли большая “гибкость” в C++? Если вам необходимо это — если вы имеете большие знания в C— это достаточно ценная вещь. Если вы начинаете с начала, другие альтернативы как Java могут часто быть более продуктивными.

Все объекты в иерархии с единым корнем (какую обеспечивает Java) могут гарантировать определенную функциональность. Вы знаете, что можете выполнить определенные базовые операции для каждого объекта вашей системы. Иерархия с единым корнем, наряду с созданием всех объектов в куче, сильно упрощает передачу аргументов (одна из наиболее сложных тем в C++).

Иерархия с единым корнем сильно облегчает реализацию сборщика мусора (который удобно встроен в Java). Необходимость поддержки может быть установлена в базовом классе, а сборщик мусора может посылать определенные сообщения каждому объекту в системе. Без иерархии с единым корнем и системой управления объектами через ссылки сложно реализовать сборщик мусора.

Так как информация о типе во время выполнения гарантирована для всех объектов, вы никогда не встретитесь с объектом, чей тип вы не можете определить. Это особенно важно с операциями системного уровня, такими как обработка исключений, и предоставляет большую гибкость в программировании.

Собрание библиотек и поддержка для облегчения использования собрания

Поскольку контейнер - это инструмент, который вы будете часто использовать, то есть смысл иметь библиотеку контейнеров, которая построена с возможностью повторного использования, так что вы можете взять один из экземпляров и встроить в свою программу. Java обеспечивает такую библиотеку, которая должна удовлетворять большинству требований.

Нисхождение против шаблонов/настроек

Чтобы сделать эти контейнеры пригодными для повторного использования, они хранят один универсальный тип для Java, который упоминался ранее: Object. Иерархия с единым корнем означает, что все является Object, так что контейнер, который хранит Object, может хранить все. Это легко создает для контейнеров возможность повторного использования.

Для использования таких контейнеров вы просто добавляете в него ссылку на объект, а позже просите ее назад. Но так как контейнер хранит только Object, когда вы добавляете в контейнер новую ссылку объекта, выполняется обратное преобразование к Object, здесь происходит потеря уникальности. Когда вы запрашиваете объект назад, вы получаете ссылку на Object, а не ссылку на тип, который вы положили в него. Как вы можете вернуть его назад к какому-то полезному интерфейсу объекта, который вы положили в контейнер?

Здесь снова используется преобразование, но здесь вы выполняете не обратное иерархическое преобразование к более общему классу, вы движетесь вниз по иерархии к более специфичному типу. Такое преобразование называется прямое преобразование. С обратным преобразованием, вы знаете, например, что Окружность - это тип Форма, так что такое преобразование безопасно, но вы не знаете, что Object - это обязательно Окружность или Форма, так что едва ли безопасно прямое преобразование, если вы не знаете с чем имеете дело.

Однако, это еще не все опасности, поскольку если вы выполняете прямое преобразование к неправильному типу, вы получаете ошибку времени выполнения, называемую исключение, которая скоро будет описана. Когда вы получаете ссылку на объект из контейнера, вы должны иметь способ для запоминания, что точно вы должны выполнить при прямом преобразовании.

Прямое преобразование и проверка во время выполнения требует дополнительного времени для выполнения программы и дополнительных усилий от программиста. Можно ли создать какой-либо способ создания контейнера так, чтобы знать тип того, что хранится, снижая необходимость прямого преобразования и возможность ошибки? Решение - это параметризованные типы, которые для классов компилятор может автоматически настраивать для работы со специфическими типами. Например, при программировании контейнера компилятор может настроить этот контейнер так, чтобы он принимал только Формы и возвращал только Формы.

Параметризированные типы - это важная часть C++, отчасти потому, что C++ не имеет иерархии с единым корнем. В C++ ключевое слово, реализующее параметризированные типы - это “шаблон”. Java в настоящее время не имеет параметризированных типов, так что есть возможность получить тип — однако неудобная — используя иерархию с единым корнем. Однако текущее предложение о параметризированных типах использует синтаксис, который сильно схож с шаблонами C++.

Дилемма домоводства: Кто должен убирать?

Каждый объект требует ресурсов для существования, большинство - много памяти. Когда объект более не нужен, он должен быть очищен так, чтобы его ресурсы были освобождены для повторного использования. В некоторых случаях программирования вопрос о том, как очищать объект не встает: вы создаете объект, используете его столько, сколько нужно, а затем он должен быть разрушен. Это не сложно, однако, встречаются задачи, в которых ситуация более сложная.

Предположим, например, вы разрабатываете систему для управления воздушным движением аэропорта. (Эта же модель может также работать для управления ящиками на складе или системой видео проката, или собачьим питомником.) На первый взгляд это выглядит просто: Создать контейнер для хранения аэропланов, затем создать новый объект аэроплана и поместите его в контейнер для каждого аэроплана, который входит в зону регулировки воздушного движения. Для очистки - просто удалите объект аэроплана, когда он оставляет зону.

Однако вы имеете несколько другую систему для записи данных об аэроплане; возможно, что данные не требуются такого же пристального внимания, как и главная функция управления. Возможно, эта запись о летящем самолете, одном из всех маленьких самолетов, вылетевших из аэропорта. Так что вы имеете второй контейнер маленьких самолетов и когда бы вы ни создали объект аэроплана, вы так же помещаете его во второй контейнер, если это маленький самолет. Затем некоторый фоновый процесс выполняет операцию над объектами этого контейнера в моменты минимальной занятости.

Теперь проблема более сложная: как вы можете знать, когда разрушать объект? Когда вы закончили работу с объектом, некоторые другие части системы могут еще работать с ним. Эта же проблема может возникнуть и в других ситуациях и в системах программирования (таких как C++) в которых вы должны явно удалять объект, когда вы закончили работу с ним и это будет достаточно сложно.

В Java сборщик мусора предназначен, чтобы позаботится о проблеме освобождения памяти (хотя это не включает другие аспекты очистки объекта). Сборщик мусора “знает”, когда объект более не используется, и он автоматически освобождает память этого объекта. Это (совместно с тем фактом, что все объекты наследуются от одного корневого класса Object, и то, что вы можете создать объект только одним способом, в куче) делает процесс программирования в Java проще, чем в C++. Вам нужно принимать гораздо меньше решений и преодолевать гораздо меньше препятствий.

Сборщик мусора против эффективности и гибкости

Если все это хорошая идея, почему не сделано то же самое в C++? Конечно - это цена, которую вы платите за все это соглашение о программировании и это дополнительные затраты во время выполнения. Как упоминалось ранее, в C++ вы можете создавать объекты в стеке, а в этом случае они очищаются автоматически (но вы не имеете гибкости при создании стольких объектов, сколько вам нужно во время выполнения). Создание объектов в стеке - это наиболее эффективный способ для резервации места хранения для объекта и для освобождения этого места. Создание объектов в куче может быть более дорого. Всегда наследование от базового класса и создание всех функций называется полиморфизмом также точно как и в small tollk. Но сборщик мусора - это обычная проблема, поскольку вы никогда точно не знаете, когда он начинает работать или как долго он работает. Это означает, что есть определенная несообразность при исполнении Java программы, так что вы не можете использовать его в определенных ситуациях, таких как при оценке равномерности выполнения программы. (Это обычно называется программа реального времени, хотя не все проблемы программирования реального времени здесь обязательны.)

Разработчики языка C++, привлекая C программистов (и часто удачно), не хотят добавлять какие-то особенности к языку, которые повлияют на скорость или использование C++ в любой ситуации, где программист может обратиться к C. Эта цель реализована, но ценой большей сложности при программировании в C++. Java проще C++, но цена - эффективность и иногда применимость. Для значительной части программируемых задач Java - предпочтительнее.

Обработка исключений: работа с ошибками

Всегда, начиная язык программирования, обработка ошибок бывает одним из наиболее сложных мест. Потому что так сложно создать при разработке хорошую схему обработки ошибок, многие языки просто игнорируют эту проблему, перекладывая ее решение на разработчика библиотеки, который придумывает на полпути меры, работающие во многих ситуациях, но могут быть легко обмануты, если просто проигнорировать их. Главная проблема с большинством схем обработки ошибок в том, что они опираются на внимание и согласие программиста, которое не навязывается языком. Если программист не внимателен — часто если он торопится — эти схемы могут легко быть забыты.

Обработка исключений связана с обработкой ошибок напрямую в языке программирования и иногда даже в операционной системе. Исключение - это объект, который “бросается” со стороны ошибки и может быть “пойман” подходящим обработчиком исключения, предназначенном для обработки определенного типа ошибки. Это как если исключение обрабатывается, выбирается другой, параллельный путь исполнения, которое может быть выбрано, когда что-то неправильно. И так как используется отличный путь исполнения, нет необходимости пересекаться с кодом нормального выполнения. Это делает такой код проще для написания, так как вам не нужно постоянно уделять внимания на проверку ошибок. В дополнение, выбрасывание исключений не похоже на код ошибки, который возвращается из функции, или на флаг, который устанавливается функцией, чтобы указать на состояние ошибки — он может быть проигнорирован. Исключение не может быть проигнорировано, так что это гарантированно будет замечено в некоторой точке. Наконец, исключение обеспечивает способ надежной защиты от плохой ситуации. Вместо простого выхода вы часто способны установить вещи правильно и восстановить исполнение программы, что делает большинство устойчивых программ.

Обработка ошибок в Java основывается на большинстве языков программирования, поскольку в Java обработка исключений была встроено с самого начала и вы вынуждены использовать это. Если вы не пишете свой код с правильной обработкой исключений, вы получите ошибку времени компиляции. Это гарантирует последовательность, делая обработку ошибок более легкой.

Стоит отметить, что обработка исключения - это не особенность объектно-ориентированного языка, хотя в объектно-ориентированных языках исключение обычно представляется объектом. Обработка исключений появилась раньше объектно-ориентированных языков.

Многопоточность

Фундаментальная концепция в компьютерном программировании - это идея обработки более одной задачи одновременно. Многи проблемы программирования требуют, чтобы программы были способны остановить свое выполнение, заняться какой-то другой проблемой, а затем вернуться к главному процессу. Решение может быть получено несколькими путями. Изначально, программисты со знаниями нижнего уровня машины писали процедуру обработки прерывания и приостановление главного процесса инициировалось аппаратным прерыванием. Хотя это работало хорошо, это было сложно не оперативно, так что это делало перенос программы на новый тип машин медленным и дорогим.

Иногда прерывания необходимы для обработки критичных ко времени задач, но есть большой класс проблем, в которых вы просто пробуете отделить часть проблемы в отдельно работающий процесс, так что вся программа станет легче отзываться. Внутри программы раздельно выполняющие части, называются потоками, а общая концепция называется mмногопоточностью. Общий пример многопоточности - это пользовательский интерфейс. При использовании потоков пользователь может нажать кнопку и получить быстрый ответ, не заставляя ждать, пока программа завершит свою текущую задачу.

Обычно, потоки - это просто способ выделить время у одного процессора. Но если операционная система поддерживает несколько процессоров, каждый поток может выполняться различными процессорами, и они действительно могут работать параллельно. Одно из удобств многопоточности на уровне языка - это то, что программист не должен беспокоится о том, есть ли в системе несколько процессоров или только один. Программа логически разделена на потоки и, если машина имеет более одного процессора, то программа работает быстрее без дополнительных регулировок.

Все это делает использование потоков приятно простым. В этом выгода: разделение ресурсов. Если вы имеете более одного запущенного процесса, которые ожидают доступа к одному и тому же ресурсу, вы имеете проблему. Например, два процесса не могут одновременно посылать информацию на принтер. Для решения проблемы ресурс, который может быть разделен, такой как принтер, должен быть заблокирован, пока он используется. Так процесс блокирует ресурс, завершает задачу, а затем освобождает блокировку, так что кто-то другой может использовать ресурс.

Потоки Java встроены в язык, что делает сложные предметы более простыми. Потоки поддерживается на уровне объектов, так что один исполняющийся поток представлен одним объектов. Java также обеспечивает ограниченное блокирование ресурса. Может быть заблокирована память любого объекта (который, помимо всего, один из видов разделяемых ресурсов), так что только один поток может использовать его одновременно. Это достигается с помощью ключевого слова synchronized. Другие типы ресурсов должны быть явно заблокированы программистом, обычно с помощью создания объекта для представления блокировки, который все потоки должны проверять прежде, чем обратится к ресурсу.

Живучесть

Когда вы создаете объект, он существует столько, сколько это необходимо, но после в нем нет необходимости, когда программа завершена. На первый взгляд это имеет смысл, но есть ситуации, в которых было бы невероятно полезно, если объект мог существовать и сохранил информацию даже после завершения работы программы. Затем, в следующий запуск программы объект будет уже здесь, и он будет обладать той же информацией, что и в предыдущей работающей программе. Конечно, вы можете получить сходный эффект при записи информации в файл или в базу данных, но в духе того, что все - это объекты было бы более последовательно иметь возможность объявить объект постоянным и взять заботу обо всех деталях на себя.

Java обеспечивает поддержку для “легковесной живучести”, которая означает, что вы можете легко сохранять объекты на диске, а позже восстанавливать их. Оправдание такой “легковесности” в том, что вы все еще ограничены явными вызовами, чтобы выполнить сохранение и восстановление. В дополнение JavaSpaces (описанное в Главе 15) обеспечивает вид постоянного хранения объектов. В некоторых будущих версиях может появиться более сложная поддержка для постоянства.

Java и Internet

Если Java, фактически, является еще одним языком программирования, вы можете спросить: почему он так важен и почему он преподносится, как революционный шаг в компьютерном программировании. Ответ не будет получен немедленно, если вы исходите из традиционного подхода к программированию. Хотя Java очень полезен для решения традиционных одиночных проблем программирования, он также важен, поскольку решает проблемы программирования в World Wide Web.

Что такое Web?

Сначала Web может показаться немного мистическим, со всеми этими разговорами о “серфинге”, “присутствии” и “домашних страницах”. Была даже нарастающая реакция против “Internet-омании”, подвергающая сомнению экономическую ценность и результативность такого широкого движения. Полезно вернуться назад и посмотреть, что есть реально, но чтобы сделать это вы должны понимать системы клиент/сервер и другие вычислительные аспекты, которые полны запутанных проблем.

Вычисления Клиент/Сервер

Главная идея системы клиент/сервер заключается в том, что вы имеете центральное хранилище информации — данные определенного рода, чаще всего в базе данных — которые вы хотите распределять по запросу определенному набору людей или машин. Ключевым моментом концепции клиент/сервер является то, что хранилище информации расположено так, что его местоположение может быть изменено и это изменение передастся потребителям информации. Общими словами, хранилище информации, программное обеспечение, которое распределяет информацию, и машина(ы), где расположены информация и программное обеспечение, называется сервером. Программное обеспечение, расположенное на удаленной машине, связывающейся с сервером, получающее информацию и обрабатывающее ее, а затем отображающее это на удаленной машине, называется клиентом.

Основная концепция клиент/серверных вычислений не сильно сложна. Проблемы возникает потому, что вы имеете один сервер, который пробует обслужить много клиентов одновременно. Обычно, система управления базой данных подразумевает, что разработчик “балансирует” расположение данных в таблицах для оптимального использования. В дополнение, системы часто позволяют клиенту помещать новую информацию на сервер. Это означает, что вы должны убедиться, что новые данные от одного клиента не перекрываются новыми данными другого, или что данные не потеряны в процессе их добавления в базу данных. (Это называется обработка с транзакциями.) Если программное обеспечение клиента изменится, оно должно быть построено, отлажено и установлено на клиентских машинах, что является более сложным и дорогим, как вы можете подумать. Это особая проблема - поддержка различных типов компьютеров и операционных систем. И, наконец, здесь всеобщая проблема производительности: вы можете иметь сотни клиентов, делающих запросы к вашему серверу одновременно, и любая маленькая задержка критична. Чтобы минимизировать время ожидания, программисты усердно работают над освобождением процессов обработки часто за счет клиентской машины, но иногда за счет другой машины на стороне сервера, используя так называемое middleware. (Middleware также используется для облегчения поддержки.)

Простая идея распределенной информации для людей имеет столько много уровней сложности в реализации, что вся проблема может показаться безнадежной. И это решающий момент: клиент/серверные вычисления составляют, грубо говоря, половину всех вычислений. Они ответственны за все, начиная от получения заказов и операций с кредитными картами, заканчивая распределением любого рода данных — склада магазина, науки, правительства, вы это знаете. Что мы придумывали в прошлом: индивидуальные решения индивидуальных проблем, изобретая новое решение каждый раз. Это было сложно в реализации и сложно в использовании, а пользователь должен был учить новый интерфейс каждый раз. Вся проблема клиент/сервера должна решить массу проблем.

Web - как гигантский сервер

Web - это действительно одна гигантская система клиент/сервер. Это немного хуже, так как все сервера и клиенты сосуществуют в одной сети одновременно. Но вам не надо знать это, так как вы заботитесь о соединении и взаимодействии с одновременно одним сервером (даже притом, что вы можете переключаться по всему миру в вашем поиске нужного сервера).

Изначально - это было простым односторонним процессом. Вы делаете запрос на сервер и он пересылает вам файл, который программа броузера на вашей машине (т.е. клиент) интерпретирует на вашей машине согласно формату. Но вскоре люди захотели делать больше, чем просто доставлять странички с сервера. Они захотели полную клиент/серверную совместимость, так чтобы клиент мог отправлять информацию обратно на сервер, например, выполнить поиск в базе данных на сервере, добавить новую информацию на сервере или поместить заказ (который требует большей безопасности, чем предоставляет обычная система). Поэтому, было замечено, что необходимы изменения для разработки в среде Web.

Просмотрщик Web был большим шагом вперед: основная идея в том, что одна порция информации должен отображаться на любом типе компьютера без изменений. Однако просмотрщики все еще оставались примитивными и постоянно отставали от требований, предъявляемых к ним. Они не были достаточно интерактивными, и имели тенденцию засорять и сервер, и Internet, поскольку, так как вам необходимо было что-то делать, это требовало от программы посылать информацию назад на сервер для обработки. Порой требовалось от нескольких секунд до минут, чтобы обнаружить орфографическую ошибку в вашем запросе. Поскольку броузеры были всего лишь просмотрщиками, они не могли выполнить даже простейшие задачи расчета. (Но с другой стороны, это было безопасно, так как они не могли выполнять программы на вашей локальной машине, что является источником помех и вирусов.)

Для решения этой проблемы был выбран другой подход. Сначала был развит графический стандарт, чтобы улучшить анимацию и видео внутри броузеров. Оставшиеся проблемы были решены только с помощью разработки возможности запускать программы на стороне клиента, под управлением броузера. Это называется программирование клиентской стороны.

Программирование клиентской стороны

Изначально Web разработка сервер-броузер разрабатывалась для интерактивной работы, но интерактивность полностью обеспечивалась сервером. Сервер поставлял статические страницы для броузера клиента, которые им просто интерпретировались и отображались. HTML основа содержит простой механизм для сбора данных: поля ввода текста, чекбоксы, радио группы, списки и выпадающие списки, так же кнопки, которые могут быть запрограммированы на сброс данных в форме или на “подтверждение” данных формы для отправки обратно на сервер. Это подтверждение проходило через Common Gateway Interface (CGI), обеспечиваемый всеми Web серверами. Текст внутри отправленного говорил CGI что нужно делать с данными. В Большинстве случаев - это запуск программы, расположенной пряма на сервере, которая обычно называется “cgi-bin”. (Если вы наблюдаете за окном адреса в верхней части вашего броузера при нажатии кнопки на Web страничке, вы можете иногда видеть “cgi-bin” внутри текста на специальном языке. ) Эти программы могут быть написаны на большинстве языков. Perl - это наиболее частый выбор, потому что он предназначен для манипуляций с текстом и его интерпретации, так что он может быть установлен только на сервере независимо от процессора или операционной системы.

Многие мощные Web сайты сегодня построены по структуре CGI, и вы можете фактически можете многое с использованием этого. Однако, Web сайты, построенные на CGI программах, могут часто становиться слишком трудными в поддержке, а также проблемой является время ответа. Ответ CGI программы зависит от того, как много данных должно быть послано, так как это загружает и сервер и Internet. (В вершине этого то, что запуск CGI программ происходит медленно.) Первые разработчики Web не предвидели, как часто пропускная способность будет исчерпана для такого рода приложений, разрабатываемых людьми. Например, любой сорт динамической графики часто нельзя применять последовательно, так как должен быть создан GIF файл и перемещен от сервера клиенту для каждой версии графики. И вы, без сомнения, имели прямой опыт с чем-то настолько простым, как проверка данных в форме ввода. Вы нажимаете кнопку подтверждения на странице; данные отправляются назад на сервер; сервер запускает CGI программу, которая обнаруживает ошибку, формирует HTML страницу, информирующую вас об ошибке, а затем посылает страницу вам; вы должны вернуться на предыдущую страницу и попробовать вновь. Это не только медленно, это не элегантно.

Решение - это программирование клиентской стороны. Большинство машин, запускающих Web броузеры, являются достаточно мощными и способны обширной работы, а с оригинальным подходом статического HTLM они простаивают, просто ожидая, когда сервер передаст следующую страницу. Программирование стороны клиента означает, что Web броузер используется для выполнения какой-нибудь работы, которую он может выполнить, а результат для пользователя - это скоростная и более интерактивная работа на вашем Web сайте.

Проблемы, обсуждаемые при программировании клиентской стороны, не отличаются от общих проблем обсуждаемых при программировании в целом. Параметры в большинстве те же, но платформы разные: Web броузер - это как ограниченная операционная система. И, в конце концов, вы должны программировать, и это становится причиной многих головокружительных проблем и решений, появляющихся при программировании на стороне клиента. Остальная часть главы приводит обзор способов и подходов в программировании стороны клиента.

Рlug-ins

Один из наиболее значимых шагов вперед в программировании на стороне клиента - это разработка встраиваемых модулей. Это способ программирования с добавлением броузеру новой функциональности при скачивании части кода, которая встраивает себя в соответствующее место броузера. Он говорит броузеру “с этого момента ты можешь выполнять новые действия”. (Вам необходимо загрузить встраиваемый модуль лишь однажды.) Некоторые быстрые и мощные особенности добавляются броузеру через встраиваемые модули, но написание этих модулей - это не тривиальная задача и вам не нужно это делать никогда, как часть процесса построения обычного сайта. Значение встраиваемых модулей для программирования стороны клиента в том, что он позволяет программисту-эксперту разработать новый язык и добавить этот язык в броузер, не обращаясь к разработчику броузера. Таким образом, встраиваемый модуль обеспечивает “заднюю дверь”, которая позволяет создание нового языка программирования стороны клиента (хотя не все языки реализуются как встраиваемые модули).

Языки сценариев

Встраиваемые модули стали результатом взрывного распространения языков сценария. У языков сценария вы встраиваете исходный код для вашей программы стороны клиента прямо в HTML страницу, а встраиваемый модуль, который интерпретирует этот язык, автоматически активируется при отображении HTML страницы. Языки сценариев достаточно легки для понимания и, потому что они являются простым текстом, как часть HTML страницы, они загружаются очень быстро, как часть одного щелчка, необходимого для производства страницы. Минус в том, что ваш код открыт каждому для просмотра (и воровства). Обычно, однако, вы не делаете удивительно сложные вещи с помощью языков сценария, так что это не встречает особых трудностей.

Это говорит о том, что языки сценариев, используемые внутри Web просмотрщиков, реально предназначены для решения специфических проблем, в первую очередь создание богатого и более интерактивного графического пользователя (GUI). Однако языки сценариев могут решить 80 процентов проблем, возникающих при программировании на стороне клиента. Ваши проблемы могут полностью попадать в эти 80 процентов, так как языки сценариев могут предоставить простоту и быстроту разработки, вам, вероятно, нужно рассмотреть язык сценариев, прежде чем рассматривать более сложные решения, такие как Java или ActiveX.

Наиболее часто обсуждаемые языки сценариев для броузеров - это: JavaScript (который не делает ничего , что может Java; его название - это просто способ отобрать часть рынка Java), VBScript (который выглядит как Visual Basic) и Tcl/Tk, который пришел из популярного кросс-платформенного языка GUI-разработки. Есть и другие, не редко более развитые.

JavaScript, вероятно, наиболее часто поддерживается. Он встроен и в Netscape Navigator и в Microsoft Internet Explorer (IE). В дополнение, вероятно, о JavaScript существует больше книг, чем о других языках броузера, а некоторые инструменты автоматически создают страницы, используя JavaScript. Однако если вы уже владеете Visual Basic или Tcl/Tk, для вас более продуктивным станет использование этих языков сценариев, чем учить новый. (У вас и без того будут проблемы с Web.)

Java

Если языки сценариев могут решить 80 процентов проблем программирования стороны клиента, что можно сказать об остальных 20 процентов “действительно сложных задач”? Наиболее популярным решением сегодня является Java. Не только потому, что это мощный язык программирования, построенный для безопасности, кросс-платформенности и интернациональности, но Java постоянно расширяется, чтобы обеспечить такие особенности языка и библиотеки, которые элегантно решают проблемы, которые сложны для традиционных языков программирования, такие как многопоточность, доступ к базам данных, сетевое программирование и распределенные вычисления. Java обеспечивает программирование на стороне клиента через апплет.

Апплет - это мини-программа, которая запускается только под управлением Web броузера. Апплет скачивается автоматически, как часть Web странички (как, например, графика скачивается автоматически). Когда активируется апплет, то выполняется программа. Это часть прекрасного — это обеспечивает вам способ автоматического распределения клиентского программного обеспечения с сервера в то время, когда это необходимо пользователю и не ранее. Пользователи получают последнюю версию клиентского программного обеспечения без ошибок и без сложных переинсталяций. Поэтому, в том способе, который разработан в Java, программисту необходимо создать только одну программу, а эта программа автоматически работает на всех компьютерах, которые имею броузеры со встроенным Java интерпретатором. (Это благополучно включают большинство машин.) Так как Java полноценный язык программирования, вы можете выполнить столько работы, сколько может клиент как перед, так и после выполнения запроса на сервер. Например, вы не хотите посылать запрос через Internet, чтобы узнать, что данные или какой-то параметр неверны, а ваш клиентский компьютер быстро выполнит работу по проверке данных, вместо ожидания от сервера проверки и передачи графического изображения к вам обратно. Вы не только получаете преимущество в скорости и отзывчивости, но это снизит сетевой трафик и в загрузку сервера, предотвращая от замедления весь Internet.

Java апплеты предпочтительнее других программ-сценариев, так как они имеют компилированную форму, так что исходный код не доступен для клиента. С другой стороны, Java апплет может быть декомпилирован без особых затруднений, но прятанье вашего кода чаще всего не самая важная задача. Два других фактора могут оказаться важнее, как вы увидите далее в этой книге, компилированные Java апплеты могут включать много модулей и занимать много отправок (обращений) сервера для скачивания. (В Java 1.1 и выше это минимизируется Java архивами, называемыми JAR файлами, что позволяет все требуемые модули паковать вместе и компрессировать для упрощения скачивания.) Программы-сценарии просто интерпретируются на Web странице как часть ее текста (и обычно маленькие и снижают обращения к серверу). Это важно для отзывчивости вашего Web сайта. Другой фактор - существенная кривая изучения. Независимо от того, что вы слышали, Java - это не простой язык для изучения. Если вы программируете на Visual Basic, переход к VBScript будет для вас более быстрым решением и, вероятно решит большинство типичных проблем клиент/сервер, которые вы можете с трудом преодолеть, изучая Java. Если вы имеете опыт в языках сценария, вам сначала полезнее будет взглянуть на JavaScript или VBScript, прежде чем переходить на Java, так как они могут легко удовлетворить вашим требованиям и ваша работа будет более продуктивной.

ActiveX

Для некоторых проблем соперником Java является Microsoft ActiveX, хотя он имеет полностью другой подход. ActiveX изначально было только решением для Windows, хотя сейчас это становится кросс-платформенной разработкой независимого консорциума. Действительно, ActiveX говорит: “если ваша программа подключается к окружению, то она может быть перенесена на Web страницу и работать под управлением броузера, который поддерживает ActiveX”. (IE напрямую поддерживает ActiveX, а Netscape использует подключаемые модули.) Таким образом, ActiveX не принуждает вас к специальному языку. Если, например, если вы имеете опыт программирования в Windows на таких языках как C++, Visual Basic или Delphi от Borland, вы можете создать компонент ActiveX, почти без изменений вашего знания языка. ActiveX также обеспечивает путь для использования правильного кода на вашей Web странице.

Безопасность

Автоматическая загрузка и запуск программ через Internet может показаться мечтой создателей вирусов. ActiveX особенно тернистый путь для безопасности в программировании на стороне клиента. Если вы кликаете на Web сайте, вы можете автоматически загрузить любое число вещей одновременно с HTML страницей: GIF файлы, код сценария, откомпилированный Java код и компоненты ActiveX. Некоторые из них доброкачественные; GIF файлы не могут причинить вред, а языки сценариев обычно ограничены в своих возможностях. Java также была разработана для запуска своих апплетов в “песочнице” безопасности, что предотвращает от записи на диск или доступ к памяти вне пределов песочницы.

ActiveX находится на противоположной стороне спектра. Программирование с ActiveX - это как программирование Windows — вы можете делать все, что захотите. Так что, если вы кликните на странице для закачки компонента ActiveX, этот компонент может стать причиной повреждений файлов на вашем диске. Конечно, программы, которые вы загрузили на ваш компьютер, которые не ограничены запуском внутри Web броузера, могут сделать то же самое. Вирусы, загруженные с Bulletin-Board Systems (BBS) долгое время являлись проблемой, но скорость Interneta увеличивает трудности.

Решением кажется “цифровая подпись”, в которой код проверяется, чтобы указать его автора. Это базируется на идее, что вирус работает потому, что его создатель может быть анонимным, так что если вы уберете анонимность, индивидуальности будут нести ответственность за свое авторство. Это выглядит как хороший план, потому что позволяет программам стать более функциональными, и, я подозреваю, снизит вред злоумышленников. Если, однако, программа имеет неумышленную деструктивную ошибку, это станет причиной проблем.

Подход Java исключает возникновение этих проблем через песочницу. Интерпретатор Java, который есть на вашем локальном Web броузере, проверяет апплет на наличие неблагоприятных инструкций, когда апплет загружается. Обычно, апплет не может писать файлы на диск или стирать файлы (главная опора вирусов). Апплеты, обычно, считаются безопасными и, так как существенно для надежности системы клиент/сервер, любые ошибки языка Java позволяют вирусам постоянно проникать. (Стоит отметить, что программное обеспечение броузера обычно предписывает эти ограничения безопасности, а некоторые броузеры позволяют вам выбирать уровень безопасности для обеспечения различных степеней доступа к вашей системе.)

Вы можете относиться скептически к этим, скорее драконским, ограничениям записи файлов на ваш диск. Например, вы можете пожелать построить локальную базу или сохранить данные, чтобы позже использовать их при автономной работе. На первый взгляд кажется, что, в конечном счете, каждый должен работать в онлайне, чтобы сделать что-нибудь важное, но, как было замечено ранее, это не практично (хотя дешевые “Internet-приборы” когда-нибудь смогут удовлетворить требования значительной части пользователей). Решение - это “подписанный апплет”, который использует шифрование с открытым ключом для проверки, что апплет на самом деле пришел оттуда, как это заявлено. Подписанный апплет все еще может засорить ваш диск, но теоретически, так как вы можете привлечь к ответственности создателя апплета, он не будет делать нехороших вещей. Java обеспечивает набор для цифровой подписи, так что вы будете способны со временем позволить апплету выйти за пределы песочницы, если необходимо.

Цифровая подпись упускает важную проблему, это скорость, из-за которой люди переходят в Internet. Если вы загрузили программу с ошибками, и она сделала что-то неблагоприятное, сколько времени займет восстановление повреждений? Это могут быть дни или даже недели. Поэтому, как вы можете отследить программу, которая сделала это? И что хорошего вы сделаете в результате?

Internet против intranet

Web является наиболее общим решением проблемы клиент/сервера, так что это наводит на мысль, что вы можете использовать эту же технологию для решения набора проблем, обычно классических проблем клиент/сервера внутри компании. При традиционном подходе клиент/сервера ваша проблема в том, что у вас разные типы клиентских компьютеров, так как при этом трудно устанавливать новое клиентское программное обеспечение, обе эти проблемы легко решаются с помощью Web броузера и программирования стороны клиента. Когда Web технология используется для информационных сетей, что ограничивается компанией, это называется intranet. Intranet обеспечивает большую безопасность, чем Internet, так как вы физически контролируете доступом к серверам в пределах вашей компании. В терминах обучения это выглядит так, как будто люди однажды поняли общую концепцию броузера, что для них намного легче, чтобы иметь дело с различиями в путях страниц и видов апплетов, так что кривая обучения выглядит понижающейся.

Проблема безопасности выявляет одну из частей, которая, кажется, формируется автоматически в мире программирования стороны клиента. Если ваша программа работает в Internet, вы не знаете под какой платформой вы будите работать, и вы будите очень осторожны и не будете распространять код с ошибками. Вам необходимо нечто кросс-платформенное и безопасное, как язык сценариев или Java.

Если вы работаете в intranet, вы можете иметь набор ограничений. Не секрет, что все ваши машины могут быть под платформой Intel/Windows. В intranet, вы отвечаете за качество вашего собственного кода и можете исправить ошибки, когда они обнаружатся. В дополнение, вы можете уже иметь тело верного кода, который вы будите использовать с более традиционным клиент/серверным подходом, посредством чего вы должны каждый раз физически устанавливать клиентские программы и выполнять обновления. Время, теряемое при установке обновлений, это наиболее непреодолимая причина для перехода к броузеру, поскольку обновления становятся невидимыми и автоматическими. Если вы вовлечены в такую intranet, наиболее лучший подход для укорочения пути - использование существующего базового кода, чем попытки переписать вашу программу на новом языке.

Когда встречаетесь с этим сбивающим с толку множеством решений проблем программирования клиентской стороны, лучший план - это оценка стоимости. Относительно ограничений вашей проблемы: что может сократить путь вашего решения. Так как программирование клиентской стороны - это все-таки программирование, это всегда хорошая идея выбрать быстрый способ разработки для вашего собственного решения. Эта агрессивная позиция необходима, чтобы приготовиться к неизбежным столкновениям с проблемами разработки программы.

Программирование стороны сервера

Все это обсуждение игнорирует проблему программирования стороны сервера. Что случается, когда вы посылаете запрос серверу? Большую часть времени запрос этот просто “перешли мне этот файл”. Ваш броузер, затем, интерпретирует этот файл определенным образом: как HTML страницу, графическое изображение, Java апплет, программу-сценарий и т.п. Более сложный запрос серверу обычно затрагивает транзакцию базы данных. Общий сценарий вовлекает запросы для сложного поиска в базе данных, который сервер форматирует в HTML страницу и посылает вам результат. (Конечно, если клиент имеет большую интеллектуальность с помощью Java или языка сценария, набор данных может быть послан и отформатирован на стороне клиента, что будет быстрее и меньше загрузит сервер.) Или вы можете пожелать зарегистрировать свое имя в базе данных, когда присоединяетесь к группе или составляете заказ, который повлечет изменения в базе данных. Такой запрос к базе данных обрабатывается тем же кодом на стороне сервера, который обычно называется программированием на стороне сервера. Традиционно, клиент-серверное программирование выполнялось с использованием Perl и CGI сценариев, но появились более сложные системы. Сюда включаются Web серверы, основанные на Java, которые позволяют вам выполнять все программирование стороны сервера, написанные на Java, называемые сервлетами. Сервлеты и их продукты, JSP - два наиболее сильных аргумента, из-за чего компании, разрабатывающие Web сайты, переходят на Java, особенно потому что они устраняют проблемы поведения с различными возможными броузерами.

Разделенная арена: приложения

Большинство из написанного о Java было написано об апплетах. Java, на самом деле, язык программирования общего назначения, который может решить проблему любого типа — по крайней мере, в теории. С этой точки зрения, могут быть более эффективные пути решения большинства клиент-серверных проблем. Когда вы выходите за пределы апплета (и одновременно освобождаетесь от ограничений, таких как предотвращение записи на диск) вы попадаете в мир приложений общего назначения, которые запускаются самостоятельно, без Web броузера, просто как обычная программа. Здесь сила Java не только в мобильности, но и в программируемости. Как вы увидите в этой книге, Java имеет много особенностей, которые позволяют вам создавать надежные программы в короткий период, чем при использовании ранее известных языков программирования.

Знайте, что это смешанное благословение. Ваша плата за усовершенствование - медленная скорость выполнения (хотя значительная работа делается в этом направлении — JDK 1.3, в частности, представляет собой так называемое “средоточие” улучшение работоспособности). Как любой язык, Java имеет встроенные ограничения, которые могут сделать его неподходящим для решения определенных типов проблем. Однако, Java - постоянно развивающийся язык и, с появлением новых выпусков, он становится более и более привлекательным для решения большого набора проблем.

Анализ и дизайн

Объектно-ориентированное программирование - это новый и отличный путь думать о программировании. Большинство людей имеют проблемы при первом знании о том, как подступить к ООП проекту. Так как вы знаете, что все можно представить объектом, и так как вы учитесь думать в более объектно-ориентированном стиле, вы можете начать создавать “хороший” дизайн, который использует все преимущества, которые предлагает ООП.

Метод (часто называемый методологией) - является набором обработок и процедур, используемых для разбиения сложной проблемы программирования. Многие методы ООП уже сформулированы еще на расцвете объектно-ориентированного программирования. Этот раздел даст вам почувствовать, что вы пытаетесь достигнуть, когда используете метод.

Особенно в ООП методология - поле для многих экспериментов, что особенно важно для понимания, что метод - это попытка решить проблему, прежде чем вы выберите решение. Это особенно верно в Java, где язык программирования предназначен для снижения сложности (по сравнению с С) возникающей в выражениях программ. Это, фактически, может смягчить необходимость во всех-более-сложных методологиях. Вместо этого простых методологий может хватить в Java для большинства типов проблем, которые вы можете встретить, используя простые методологии с процедурными языками.

Так же важно понять, что “методология” часто очень важна и обещает очень много. Что бы вы ни делали при разработке и написании программы - это метод. Это может быть ваш собственный метод и вы можете не осознавать этого, но это процесс, которым вы следуете, создаете. Если это эффективный процесс, для него необходимо только небольшая настройка при работе с Java. Если вы не удовлетворены вашей производительностью и путем, которым идет ваша программа, вы можете захотеть выбрать формальный метод или выбрать часть из большого количества формальных методов.

Пока вы следуете процессу разработки, наиболее важная проблема при этом: не потеряться. Это легко сделать. Большинство из методов анализа и дизайна предназначены для решения больших проблем. Помните, что большинство проектов выходят за рамки категории, так что вы можете обычно иметь удовлетворительный анализ и дизайн с относительно малым числом рекомендаций метода. [8]. Но некоторый сорт процессов, не имеет значения чем ограничено, будет заставлять вас на вашем пути поступать гораздо лучше, чем просто начинать кодировать.

Легко попасть в ловушку и впасть в аналитический паралич, где вы почувствуете, что вы не можете двигаться вперед, потому что вы не сможете обнаружить каждую малую деталь в текущем состоянии. Помните, не имеет значения, сколько анализов вы выполнили, есть некоторые вещи, относительно системы, которые не откроют себя во время дизайна, а многие вещи не откроют себя, пока вы не начнете программировать или даже пока программа не будет запущена. По этому, важно быстро перейти от анализа и дизайна к реализации теста предполагаемой системы.

Это места заслуживает особого внимания. Исходя из истории процедурных языков, похвально то, что команда продвигается осторожно и каждую минуту разбирает и понимает детали, прежде чем перейдет к дизайну и реализации. Конечно, когда создаете DBMS, необходимо понять требования потребителя. Но DBMS - это классическая проблема, которая хорошо поставлена и хорошо понята; в большинстве таких программ структура базы данных является проблемой для решения. Класс проблем программирования, обсуждаемый в этой главе - из “неопределенного (wild-card)” (мой термин) разнообразия, в котором решение - это не просто переформирование хорошо известного решения, а привлечение одного или нескольких “неопределенных (wild-card) факторов” — элементов, для которых нет хорошо известных предыдущих решений, и для которых необходимо исследование [9]. Попытка полного анализа неизвестной проблемы до перехода к дизайну и реализации приводит к параличу анализа, так как вы не имеете достаточно информации для решения проблем такого рода в фазе анализа. Решение таких проблем требует циклического приближения, а это источник рискованного поведения (которое дает понимание, так как вы пробуете выполнить что-то новое и потенциально вознаграждает и повышает знания). Это выглядит как риск, получаемый при “броске” к предварительной реализации, но это может снизить в неопознанном проекте, потому что вы рано выясняете, является ли обычный подход к проблеме жизнеспособным. Разработка продукта - риск в управлении.

Часто получается, что вы “строите одно, чтобы выбросить”. В ООП вы можете выбросить часть, потому что код инкапсулируется в класс, во время первого прохода вы неизбежно создадите определенный полезный дизайн класса и разработаете полезные идеи относительно разрабатываемой системы, которые нет необходимости выбрасывать. Таким образом, первый проход цикла по проблеме не только даст важную информацию для следующего прохода анализа, дизайна и реализации, он также создаст основу кода.

Говорят, что если вы смотрите на методологию, создающую огромное число деталей и предлагающую много шагов и документов, все еще тяжело понять, когда остановиться. Держите в уме, что вы пробуете узнать:

  1. Что такое объекты? (Как вы разделите ваш проект на составные части?)
  2. Что есть его интерфейсы? (Какие сообщения вам необходимо посылать каждому объекту?)

Если вы не приобрели ничего, кроме объектов и их интерфейсов, вы можете писать программу. По разным причинам вам может быть необходимо более подробное описание и документация, чем эта, но вы не можете избежать неприятностей.

Процесс может быть разбит на пять частей, и Фаза 0 является обязательной начальной ступенью для использования определенной структуры.

Фаза 0: Создание плана

Сначала, вы должны решить, какой шаг вы совершаете в вашем процессе. Это звучит просто (фактически, все это звучит просто), и при этом люди часто не делают этого решения до начала кодирования. Если ваш план: “впрягаемся и начинаем кодировать” - прекрасно. (Иногда это работает, когда вы имеете хорошо понятую проблему. ) Согласитесь, что меньше всего - это планом.

Вы можете также решить на этой фазе, что необходимы некоторые дополнительные структуры обработки, но не все девять ярдов. Понятно, что некоторые программисты любят работать в “свободном режиме”, в котором не навязываются структуры в процесс разработки их работы; “Это будет выполнено, когда будет сделано”. Это может нравиться какое-то время, но я обнаружил, что есть несколько вех на пути, помогающих сфокусировать и сосредоточить ваши усилия вокруг этих вех, вместо того, чтобы застрять на единичной цели “завершить проект”. В дополнение, это разделяет проект на меньшие составные кусочки и делает его менее запутанным (плюс вехи часто более удобны для празднования).

Когда я начинал учить структуру магазина (так что я иногда писал новеллу) я сначала сопротивлялся идеи структуры, чувствуя, что я напишу лучше, если просто позволю течь на страницу. Но позже я понимал, что когда я пишу о компьютерах, структура достаточно понятна для меня, так что мне не нужно думать об этом очень много. Но я все еще структурировал мою работу, хотя только наполовину осознавал ее в моей голове. Даже если вы думаете, что ваш план - просто начать кодирование, вы все еще почему-то проходите последовательность фаз, ставя определенные вопросы и отвечая на них.

Задающее утверждение

Любая система, строимая вами, независимо от сложности, имеет фундаментальные цели; дело в этом, необходима удовлетворительная основа. Если вы можете рассмотреть интерфейс пользователя, детали оборудования или системы, алгоритм кодирования и эффективность проблемы, вы можете в итоге найти ядро — простое и ясное. Так же как и в фильмах Голливуда, это называется высшей концепцией и может быть описано одним или двумя предложениями. Правильное описание - это отправная точка.

Высшая концепция наиболее важна, так как она задает тон вашему проекту; это задающее утверждение. Вам необходимо найти его первым делом (вы можете быть на последней фазе проекта, прежде чем полностью поймете его), но продолжайте пробовать, пока не почувствуете правоту. Например, в системе управления воздушным движением вы можете начать с высшей концепции, сфокусировавшись на системе, которую вы строите: “Башенная программа сохраняет маршруты авиалайнеров”. Но относительно того, что случится, когда вы сократите систему до очень маленького летного поля; надеюсь, это будет только человеческим контроллером или совсем ничего. Более полезная модель не будет касаться решения, которое вы создаете настолько, насколько описываете проблему: “Авиалайнеры прибывают, разгружаются, обслуживаются и загружаются, затем отправляются”.

Фаза 1: Что мы делаем?

В предыдущем поколении дизайна программ (называемом процедурным дизайном ), это называлось “созданием анализа требований и спецификаций системы”. При этом, конечно, были потери; документы с пугающими названиями, которые могли стать большими проектами по своему собственному праву. Однако это была хорошая идея. Анализ требований говорит: “Создавая список руководящих указаний, мы будем использовать его, чтобы знать, когда работа выполнена и потребитель удовлетворен”. Спецификации системы говорят: “Это описание того, что (а не как) программа будет делать, чтобы удовлетворить требованиям ”. Анализ требований является соглашением между вами и потребителем (даже если потребитель работает в вашей компании или является другим объектом или системой). Спецификации системы - это вершина исследований проблемы и, в некотором смысле, открытие в том, будет ли это выполнено и как долго это будет выполняться. И то и другое требует договоренности всех людей (и поэтому они со временем будут изменяться), я думаю, что лучше сохранять их настолько голыми, насколько возможно — идеально для списков и основных диаграмм — для сохранения времени. Вы можете иметь другие ограничения, требующие от вас включить их в увеличивающуюся документацию, но пытайтесь оставить начальную документацию как можно меньше и кратче - это может быть сделано в несколько приемов группового мозгового штурма с лидером, который создает описание. Это не только требует отдачи от каждого, это также способствует приобретению начальных соглашений для каждого в команде. Конечно, может быть более важно начать проект с большим энтузиазмом.

Необходимо сосредоточится на самом главном из того, что вы пробуете выполнить в этой фазе: определить то, что система предполагает выполнять. Наиболее ценный инструмент для этого - это коллекция, называемая “использование причин ”. Использование причин идентифицирует ключевые особенности системы, которые будут реализовывать некоторые классы, которые вы будете использовать. Таким образом, хорошо описываются ответы на такие вопросы, как [10]:

Если вы разрабатываете банкомат, например, использование причин в обычном аспекте функциональности системы позволит описать, что банкомат будет делать во всех возможных ситуациях. Каждая из этих “ситуаций” называется сценарием, а использование причин может помочь при сборе сценариев. Вы можете думать, что сценарий как вопрос, начинающийся с: “Что делает система, если..?” Например, “Что делает банкомат, если пользователь недавно положил чек, меньше 24 часов назад, и нет достаточного количества денег, так как чек еще не был оприходован, для выполнения нужного снятия?”

Использование диаграмм причин, несомненно, проще для предотвращения зависаний в реализации вашей системы:

Каждая взаимодействующая персона представляет “действующее лицо”, которое обычно является человеком или любым другим родом свободного агента. (Это даже может быть другая компьютерная система, как в случае с “ATM”.) Прямоугольник представляет границы вашей системы. Эллипс представляет использование причин, которые описывают ценную работу, выполняемую системой. Линии между действующими лицами и причинами использования представляют взаимодействие.

Не имеет значения, как действительно реализована ваша система, так как она выглядит так для пользователя.

Использование причин не обязательно должно быть чрезвычайно сложны м, даже если лежащая в основе система сложна. Это предназначено для показа системы, как она видна пользователю. Например:

Использование причин вырабатывает спецификации требований, определяя все взаимодействия, которые пользователь может иметь в системе. Вы пробуете обнаружить весь набор использования причин для вашей системы и когда вы сделаете это, то вы получите ядро того, для чего предназначена система. Великолепная вещь - это фокусировка на использовании причин, так как это всегда наталкивает вас на сущность и предохраняет вас от отклонения от проблемы, являющейся критичной при выполнении работы. Таким образом, если вы имеете полный набор причин использования, вы можете описать вашу систему и перейти к следующей фазе. Вероятно, вы не обнаружите их все с первой попытки, но это ничего. Все они откроют себя в свое время и, если вы требуете точные спецификации системы, вы обманитесь.

Если вы ошиблись, вы можете выполнить быстрый этой фазы, используя инструмент грубого приближения: описание системы в несколько параграфов, а затем взглянуть на существительные и глаголы. Существительные могут подсказать действующих лиц, контекст использования причин (например, “холл”), или артефакты, управляемые при использовании причин. Глаголы могут подсказать взаимодействие между действующими лицами и указать шаги при использовании причин. Вы также обнаружите, что существительные и глаголы вырабатывают объекты и сообщения на фазе дизайна ( заметьте, что использование причин описывает взаимодействие между подсистемами, так что “существительные и глаголы” технически можно использовать как инструмент, генерирующий использование причин [11].

Граница между использованием причин и действующими лицами может выходить за пределы пользовательского интерфейса, но это не определяет интерфейс пользователя. О процессе определения и создания интерфейса пользователя смотрите Software for Use by Larry Constantine и Lucy Lockwood, (Addison-Wesley Longman, 1999) или сходите на www.ForUse.com.

Хотя это черная работа, в этой точке важны некоторые основы планирования. Теперь у вас есть обзор того, что вы будете строить, так что вы вероятно способны оценить, сколько это может длиться. Большинство факторов здесь вступают в игру. Если вы рассчитываете длинный план, то компания может решить не осуществлять его (и при этом используются их аргументы, иногда более весомые, чем — это хорошая вещь). Или управляющий может уже решил, сколько такой проект может длиться и попробовать пересмотреть вашу оценку. Но лучше иметь реальную оценку с начала и следовать ее решениям. Имеется много попыток полностью согласоваться с техникой оценки (большинство таких способов диктуются торговой политикой), но, вероятно, лучший подход - основываться на ваш опыт и интуицию. Нутром почувствуйте, сколько времени это займет, затем умножьте это на два и добавьте 10 процентов. Возможно, ваше нутро чувствует правильно; вы можете выполнить какую-то работу за это время. “Удвоение” превратится во что-нибудь приличное, а 10 процентов уйдет на финальную полировку и детали [12]. Однако, вы хотите объяснить это и, независимо от жалоб и манипуляций, которые случаться при использовании такого планирования, это только кажется, что это работает не так.

Фаза 2: Как мы это построим?

В этой фазе вы должны перейти к дизайну, который описывает, как выглядят классы, и как они будут взаимодействовать. Лучшая техника для определения классов и взаимодействий - это карточки Сотрудничества-Классов-Взаимодействия (Class-Responsibility-Collaboration [CRC]). Часть ценности этого инструмента в том, что он низко-технический: вы начинаете с набора чистых карточек, размера 3х5, и вы пишете на них. Каждая карточка представляет один класс, а на карточке вы пишете:
  1. Имя класса. Важно, чтобы это имя содержало ядро того, что делает класс, так как это облегчает понимание.
  2. “Ответственность” класса: что класс должен делать. Обычно, это может добавляться к именам членов-функций (так как эти имена должны описываться в хорошем дизайне), но это не отменяет другие записи. Если вам необходим быстрый процесс, взгляните на проблему с точки зрения ленивого программиста: какие объекты должны магически возникнуть, чтобы решить вашу проблему?
  3. “Сотрудничество” классов: что другие классы делают при взаимодействии с ними? “Взаимодействие” - умышленно широкий термин; он должен означать конгломерат или просто то, что объект существует и будет выполнять обслуживание для объектов класса. Сотрудничество должно также рассматривать аудиторию этого класса. Например, если вы создаете класс Firecracker, кто будет наблюдать за ним: Chemist или Spectator? Создатель будет знать, что химикаты входят в конструкцию, а позже будет отвечать за цвет и освобожденную форму при взрыве.
Вы можете чувствовать, что карточки должны быть больше, вы захотите поместить на них всю информацию, но они специально маленькие, не только, чтобы сохранить ваши классы маленькими, но также и потому, чтобы вы не вдавались в детали слишком рано. Если вы не можете поместить все, что вам необходимо знать о классе на маленькую карточку, класс слишком сложный (либо вы слишком сильно детализируете, или вам надо создать более одного класса). Идеальный класс должен быть легко понимаем. Идея карточек CRC поможет вам перейти к первичному дизайну, так что вы получите большую картину, а затем очистите свой дизайн.

Одно из основных преимуществ карточек CRC в коммуникациях. Это хорошо экономит время в группе без компьютеров. Каждый участник берет ответственность за несколько классов (которые сначала не имеют имен или другой информации). Вы делаете живую эмуляцию, решая по одному сценарию, определяя какие сообщения посылаются различным объектам, чтобы удовлетворить каждому сценарию. Когда вы пройдете этот процесс, вы определите классы, необходимые вам, наряду с их ответственностью и сотрудничеством, и, когда вы выполните это, вы заполните карточки. Когда вы пройдетесь по всем причинам использования, вы получите картину первого среза дизайна.

Прежде, чем я стал использовать CRC карточки, наиболее удовлетворительный опыт решения я имел, когда подходил к начальному дизайну, становясь перед командой — которая не строила прежде ООП проекты — и рисовал объекты на доске. Мы говорили о том, как объекты должны сообщаться с другими и стирали некоторые из них, заменяя их другими объектами. Действительно, я управлял всеми “CRC карточками” на доске. Команда (которая знала, что проект будет делать) реально создавала дизайн; они “овладели” дизайном раньше, чем я им это дал. Все, что я сделал - было руководство процессом путем постановки правильных вопросов, попыток приближений и получения обратной связи от команды, которая изменяла это приближение. Реальная красота процесса была в том, что команда училась тому, как делать объектно-ориентированный дизайн, не просматривая абстрактных примеров, но, работая над одним дизайном, наиболее интересным для них были они сами.

Когда вы придете к CRC карточкам, вы можете пожелать создать больший формат описания вашего дизайна, используя UML[13]. У вас нет необходимости использовать UML, но это может быть полезным, особенно если вы хотите поместить диаграмму на стену для каждого для обдумывания - это хорошая идея. Альтернативой UML является текстовое описание объектов и их взаимодействий, или, в зависимости от языка программирования, сам код [14].

UML также обеспечивает дополнительные комментарии к диаграммам для описания динамической модели вашей системы. Это полезно в ситуациях, при которых состояние переходов системы или подсистемы являются достаточно преобладающими, чтобы они имели собственную диаграмму (как в системах управления). Вам также может понадобиться описание структур данных для системы или подсистемы, в которых данные - преобладающий фактор (такие как базы данных).

Вы поймете, что завершили Фазу 2, когда получите описание объектов и их взаимодействий. Большинство из них - обычно некоторые прячутся в трещины и не дают о себе узнать до Фазы 3. Но это ничего. Все, с чем вы имели дело - это то, что вы исследовали все ваши объекты. Лучше их обнаружить раньше в процессе, но ООП обеспечивает достаточно структур, чтобы это не было плохо, если вы обнаружите их позже. Фактически, разработка объектов имеет склонность случаться на всех пяти стадиях в процессе разработки программы.

Пять стадий дизайна объектов

Разработка жизни объекта не ограничивается тем временем, когда вы пишите программу. Вместо этого, дизайн объекта продолжается на всей последовательности шагов. Полезно это понимать потому, что вы сразу прекращаете совершенствование; вместо этого вы реализуете, то, что поняли из того, что делает объект и как это должно со временем выглядеть. Это виденье также применимо для разработки различных типов программ; шаблон для обычного типа программы изо всех сил проявляют снова и снова эту проблему (Это отмечено в книге “Thinking in Patterns with Java”, которую можно получить на www.BruceEckel.com). Объекты тоже имеют свои шаблоны, которые проявляется через понимание, использование и повторное использование.

1. Открытие объектов. Этот этап возникает во время начального анализа программы. Объекты могут открываться при рассмотрении внешних факторов и ограничений, дублировании элементов в системе и разбиении на концептуальные кусочки. Некоторые объекты очевидны, если вы уже имеете набор библиотек классов. Общность между подсказкой классов базового класса и наследованием может проявиться сразу или позже, в процессе дизайна.

2. Сборка классов. Как только вы построите объекты, вы почувствуете необходимость в новых членах, которые не проявились при исследовании. Внутренние надобности объектов могут требовать других классов для их поддержки.

3. Конструирование системы. И снова дополнительные требования для объектов могут появиться на этом этапе. Как вы узнали, вы развиваете ваши объекты. Необходимость в коммуникации и взаимодействии с другими объектами системы может изменить требования к вашим классам или потребовать новые. Например, вы можете обнаружить необходимость в посреднике или помощнике класса, таком как связанный список, который содержит малую или главную информацию и просто помогает другим функциям класса.

4. Расширение системы. Как только вы добавите новую особенность системы, вы можете обнаружить, что предыдущий дизайн не поддерживает легкое расширение системы. С этой новой информацией вы можете реструктурировать часть системы, возможно, добавляя новые классы или иерархию классов.

5. Повторное использование объектов. Это действительно напряженный тест для классов. Если кто-то пробует использовать их повторно в новой ситуации, он, вероятно, обнаружит рад недостатков. Как только вы измените класс, чтобы адаптировать к более новой программе, основные принципы класса могут стать яснее, пока вы не получите действительно пригодный для повторного использования тип. Однако не ждите, что большинство объектов дизайна системы получится использовать повторно — совершенно допустимо для большинства ваших объектов быть специфичными для системы. Типы многократного использования имеют тенденцию быть более общими и они должны решать более общие проблемы, чтобы их можно было использовать повторно.

Руководство для разработки объектов

Эти этапы подсказывают несколько советов, когда вы думаете о разработке ваших классов:
  1. Позвольте специфичной проблеме сгенерировать класс, а затем дайте классу взрослеть и созревать в процессе решения проблемы.
  2. Помните, обнаружение необходимых классов (и их интерфейсов) - это главное при разработке системы. Если вы уже имеете классы, это будет легким проектом.
  3. Не принуждайте себя знать все с самого начала; учитесь в процессе. Это все равно произойдет.
  4. Начните программировать; убедитесь, что что-то работает так, как вы этого хотели, а что-то не так. Не бойтесь, что вы покончите с процедурным стилем — классы поделят проблему и помогут управлять анархией и энтропией. Плохие классы не разобьют хорошие классы.
  5. Всегда оставляйте объект простым. Маленькие понятные объекты с ясным применением лучше, чем большие сложные интерфейсы. Когда приближается решительный момент, используйте подход Occam Razor: Рассмотрите варианты и выберите наиболее простой, потому что простые классы всегда лучше. Начните с легкого и простого, и вы можете расширять интерфейсы класса, когда будете понимать лучше. Когда придет время, удалить элементы класса будет труднее.

Фаза 3: Построение ядра

Это начальное преобразование от чернового дизайна в компилированное и выполняемое тело, которое может быть протестировано и, что важно, подтверждает или опровергает вашу архитектуру. Это не однопроходный процесс, прежде вы начнете серию шагов, которая постепенно будет строить систему, как вы увидите в фазе 4.

Ваша цель - найти ядро архитектуры вашей системы, что необходимо для реализации и генерации работающей системы, не имеет значения, насколько не закончена система в этом начальном проходе. Вы создаете рабочий участок, который вы будете строить в будущих итерациях. Вы также выполняете первую из многих итераций и тестов системы и даете работодателю понятие о том, как будет выглядеть и прогрессировать система. В идеальном случае вы также подвергнетесь некоторому критическому риску. Вероятно, вы также обнаружите изменения и улучшения, которые могут сделать вашу начальную архитектуру — это те веши, о которых вы не могли узнать без реализации системы.

Часть строительства системы реально проверяет, что вы получили при тестировании ваших аналитических требований и спецификаций системы (если они существуют). Убедитесь, что ваши тесты удовлетворяют требованиям и использованию причин. Когда ядро системы основано, вы готовы перейти к добавлению большей функциональности.

Фаза 4: Итерации использования причин

Когда ядро работает, каждый набор особенностей вы включаете в маленький проект. Вы добавляете набор особенностей во время итерации за разумно короткий период разработки.

Насколько велика итерация? В идеале, каждая итерация занимает от одной до трех недель (это может зависеть от языка программирования). По окончании этого периода вы завершаете итерацию, проверяете систему, имеющую большую функциональность, чем прежде. Что обычно представляет интерес и является базисом для итерации: единичная причина использования. Каждая причина использования представляется пакетом связанной функциональности, которую вы встроили в систему одновременно, во время итерации. Не только потому, что это хорошая идея, но это также более легальная идея использования причины, так как концепция не отменяется после анализа и разработки, а, вместо этого, разрабатываются фундаментальные часть в процессе построения программного обеспечения.

Вы прекратите итерацию, когда вы достигли нужной функциональности или достигли внешнего предельного срока и потребитель может быть удовлетворен текущей версией. (Помните, программное обеспечение - это бизнес заказов.) Поскольку процесс итеративный, вы имеете много удобных случаев для сдачи продукта раньше, чем достигните конечной точки; проект с открытыми исходными текстами работает исключительно итеративно, в среде с высокой обратной связью, которая делает его удовлетворительным.

Процесс итеративной разработки удобен по многим причинам. Вы можете обнаружить и отвести критический риск раньше, чем потребитель получит удобный случай изменить свое понимание, программист получает большее удовлетворение, а проект может продвигаться более четко. Дополнительная выгода заключается в обратной связи с заказчиком, который может видеть текущее состояние продукта и точно знать где что лежит. Это может снизить или отменить необходимость в ошеломляющих встречах и увеличить доверие и поддержку заказчика.

Фаза 5: Эволюция

Это момент в цикле разработки, который имеет традиционное название “поддержка”, этот термин может означать все, начиная с “заставить это работать тем способом, который предполагался с самого начала” и до “добавления особенностей, которые пользователь забыл упомянуть” или до более традиционного “фиксирования появившихся ошибок” или “добавления новых особенностей, в которых возникает необходимость”. Столько много недоразумений появляются в термине “поддержка”, что слегка страдает качество, частично потому, что вы действительно построили первоначальную программу и все, что вам нужно сделать - это заменить части, смазать их и предохранить от разрушения. Надеюсь, это лучший термин для описания того, что происходит.

Я буду использовать термин эволюция [15]. Так что “Вы сначала вы не поймете правильно, чтобы дать себе простор для изучения и возврата, чтобы внести изменения”. Вам может понадобиться сделать много изменений, по ходу того, как вы будите изучать и понимать проблему более глубоко. За элегантность, которую вы производите, если вы развиваетесь, рано или поздно придется платить. Эволюция - это когда ваша программа превращается из хорошей в великолепную, и когда те понятия, которые вы сначала реально не понимали, становятся яснее. Это также означают, что классы могут развиться от простого использования в проекте до многократно используемого ресурса.

Что означает “сделать правильно” - это не только чтобы программа работала в соответствии с требованиями и причинами использования. Это также означает, что внутренняя структура кода понятна вам и дает ощущение, что все хорошо подогнано, без неудобного синтаксиса, чрезмерно больших объектов или неловко расположенных бит кода. В дополнение, вы должны иметь чувство, что структура программы переживет изменения, которые неизбежно будут происходить на протяжении жизни программы и эти изменения можно будет сделать легко и понятно. Это немалое искусство. Вы должны не только понимать, что вы построили, но и как программа будет развиваться (что я называю вектором изменений). К счастью, объектно-ориентированные языки программирование обычно адаптированы для поддержки такого рода постоянных модификаций — ограничения, созданные объектами имеет тенденцию сохранять структуру от ломки. Они так же позволяют вам делать изменения — так как это может выглядеть радикально в процедурном программировании — без разрушительного землетрясения для вашего кода. Фактически, при поддержке эволюционирования может быть более важен подход ООП.

При эволюции вы что-то создаете, что мало похоже на то, что вы думали построить, а затем вы питаете надежду, сравнивая это с вашими требованиями, и смотрите где вы ошиблись. Затем вы возвращаетесь назад и исправляете их путем изменения дизайна и реализации части программы, которая работает неправильно [16]. Вам действительно может понадобиться решить проблему или аспект проблемы, для которой вы некоторое время назад нашли правильное решение. (При этом очень полезно обучение Шаблону Разработки. Вы можете найти информацию в Thinking in Patterns with Java, имеющейся на www.BruceEckel.com.)

Эволюция также происходит при построении системы, слежением за тем, чтобы она соответствовала вашим требованиям и обнаружением того, что она реально не делает того, что вы хотите. Когда вы видите систему в работе, вы находите, что на самом деле хотели решить совсем другую проблему. Если вы думаете в таком роде о происхождении эволюции, то вы хозяин самому себе в построении первой версии настолько быстро, насколько это возможно, так что вы можете обнаружить, что она не делает то, что вы хотите.

Возможно, большинство важных вещей нужно помнить по умолчанию — по определению, на самом деле — если вы модифицируете класс, все супер- и подклассы продолжают функционировать. Вам не нужно бояться изменений (особенно если вы имеете встроенный набор тестовых блоков для проверки корректности ваших модификаций). Модификации не обязательно могут повредить программе, а любые изменения в результате ограничивает подклассы и/или сотрудничество классов, которые вы изменяете.

Планы выплат

Конечно, вы не захотите строить здание без множества точно прорисованных планов. Если вы стоите сарай или собачью конуру ваш план не обязательно должен быть тщательно разработан, но вы, вероятно, начнете с определенных набросков, которые помогут вам на вашем пути. Разработка программного обеспечения доходит до крайностей. Долгое время люди не имели достаточно структур при разработке, поэтому большие проекты начинали рушиться. Взаимодействуя, мы покончили с методологиями, которые имели запутывающее количество структур и деталей, необходимых, в первую очередь, для больших проектов. Эти методологии были достаточно страшны для использования — это выглядело, как будто вы тратили все время на написание документации, а не на программирование. (Это случалось довольно часто.) Я надеюсь, что то, что я показал вам здесь, советует средний путь — с подвижную шкалу. Используйте тот доход, который удовлетворяет вашим требованиям (и вашим персоналиям). Не имеет значения, какой минимальный выбор вы сделаете, некоторые виды планов делают большие улучшения вашего проекта, что не мешает вообще отсутствовать планам. Помните, что по большинству оценок, более 50 процентов проектов неудачные (по некоторым оценкам до 70 процентов)!

Следование плану — предпочтительно тому, который проще и короче — и следуя разработанной перед началом кодирования структуре, вы обнаружите, что вещи ложатся вместе легче, чем если бы вы их поделили и начали разрезать. Вы также реализуете великолепное решение ситуации. По моему опыту, подход с элегантными решениями более удовлетворяет различным уровням требований; он больше выглядит как искусство, чем как технология. И элегантность всегда оплачивается; это не просто пустое стремление. Это не только дает вам легкость в построении и отладке программы, но вы также получаете легкость в понимании и поддержке, что лежит в основе объема финансирования.

Экстремальное программирование

Я изучал анализы и техники дизайна с начала до конца, так как я был в выпускной школе. Концепция Экстремального Программирования (ЭП) наиболее радикальная и очаровательная, как я увидел. Вы можете найти ее в хронике Extreme Programming Explained Kent Beck (Addison-Wesley, 2000) и в Web на www.xprogramming.com.

ЭП - это и философия о работе при программировании и набор руководящих положений, чтобы делать это. Некоторые из руководящих положений отражают другие современные технологии, но две наиболее важных и очевидных вклада, по моему мнению, это “первичное написание тестов” и “парное программирование”. Хотя они строго аргументированы для всего процесса, Beck указывает, что только этими двумя практиками вы улучшаете вашу продуктивность и надежность.

Первичное написание тестов

Тестирование традиционно относится к последней части проекта, после того, как вы “заставили все работать, просто для того, чтобы убедится”. Это простота имеет более низкий приоритет и те люди, которые специализируются в этом, не имели высокого статуса и часто даже основательно отгораживались, подальше от “реальных программистов”. Испытательные команды относятся к тому типу людей, которые заходят так далеко, что носят черную одежду и с ликованием кудахчут всякий раз, когда что-то ломают (честно говоря, я имел такое чувство, когда ломал компиляторы).

ЭП полностью революционизирует концепцию тестирования, давая равный (или больший) приоритет с кодированием. Фактически, вы пишите тест до того, как напишите код, который будет тестироваться, а тест навсегда остается с кодом. Тест должен выполнятся полностью каждый раз, когда вы делаете итерацию проекта (которая часто случается чаще одного раза в день).

Первичное написание теста имеет два особенно важных эффекта.

Во-первых, оно обеспечивает ясное определение интерфейсов класса. Я часто советовал людям “придумывать совершенный класс для решения определенной проблемы”, как инструмент, когда при попытках разработки системы. Стратегия тестирования ЭП идет дальше — она точно указывает, как класс должен выглядеть для потребителя класса и точно указывает как класс должен себя веси. При этом нет неопределенных терминов. Вы можете писать всю эту прозу или создавать все эти диаграммы, которые хотите, описывающие как класс должен вести себя и как он должен выглядеть, но тесты - это договор, который навязывается компилятору и работающей программе. Трудно выдумать более конкретное описание класса, чем тесты.

Пока создаются тесты, вы навязываете классу конкретные вещи и часто обнаруживаете необходимую функциональность, которая может отсутствовать во время экспериментов с UML диаграммами, CRC карточками, использованием причин и т.п.

Второй важный эффект первичного написания тестов приходит из запуска тестов каждый раз, когда вы делаете сборку программного обеспечения. Это реально дает вам вторую половину тестирования в добавок к проводимому компилятору. Если вы взглянете на эволюцию языков программирования в перспективе, вы увидите, что реальное улучшение технологии вращается вокруг тестирования. Языки сборки проверяли только синтаксис, а C налагает некоторые семантические ограничения, что предотвращает некоторые типы ошибок. ООП языки налагают еще больше семантических ограничений, которые, если вы реально об этом думаете, реально формируются при тестировании. “Правильно ли используется этот тип данных?” и “Правильно ли вызывается эта функция?” - это виды тестов, выполняемые компилятором или системой времени выполнения. Мы видим результат применения этих тестов, встроенных в язык: люди становятся способны писать более сложные системы и заставлять их работать ха меньшее время и с меньшими усилиями. Я разгадал почему это так и теперь я реализую это в тестах: вы делаете что-то неправильно, а сеть безопасности, встроенная в тесты, говорит вам, что появилась проблема и указывает на нее.

Встроенная система тестирования, представленная дизайном языка, может зайти очень далеко. В некоторый момент вы должны вступить и добавить остальные тесты, которые произведут полную свиту (при взаимодействии с компилятором с системе времени выполнения), которые проверяют всю вашу программу. Имея компилятор, заглядывающий вам через плечо, нужны ли вам эти вспомогательные тесты с самого начала? Так почему вы пишите из первыми, а запускаете их автоматически при каждом построении вашей системы. Ваши тесты становятся расширением сети безопасности, обеспечиваемой языком.

Одна из вещей, обнаруженных мной при использовании все более и более мощных языков программирования, была в том, что я одобрял попытки более бесстыдных экспериментов, поскольку я знал, что язык сохранит мне время при охоте за ошибками. Схема тестирования ЭП делает то же для всего вашего проекта. Поскольку вы знаете, что ваши тесты всегда перехватят любую проблему, которую вы создадите (и вы регулярно добавляете новые тесты, когда вы их придумываете), вы можете сделать большие изменения, когда вам это нужно, не заботясь о том, что вы бросите весь проект в полную неразбериху. Это невероятно мощно.

Парное программирование

Парное программирование восстает против крепкого индивидуализма, с которым мы были познакомлены с самого начала через школу (где достигаем цели или терпим нашу собственную неудачу и работа с нашим окружением рассматривается как “обман”) и средства информации, особенно Голливудские фильмы, в которых герой обычно сражается против бессмысленного сходства [17]. Программисты тоже рассматривают образцы индивидуальности — “кодировщики - ковбои”, как любит говорить Larry Constantine. Теперь ЭП, которое само сражается против последовательного мышления, говорит, что код должен писаться двумя людьми за каждой рабочей станцией. И это должно быть выполнено для группы рабочих станций без барьеров, которые так любят люди, обслуживающие проект. Фактически, Beck говорит, что первая задача преобразования к ЭП - это прибыть с отвертками и выворачивать и демонтировать все, что мешает. [18] (Это будет требовать менеджера, который будет отводить ярость отдела обслуживания.)

Значение парного программирования в том, что один человек реально выполняет кодирования, пока другой думает об этом. Тот кто думает, держит в мозгу большую картину — не только картину решаемой проблемы, но и руководящие моменты ЭП. Если два человека работают, то менее вероятно, что один из них уйдет, говоря: “Я не хочу сначала писать тест”, например. И если кодировщик застопорится, они могут поменяться местами. Если они оба застопорятся, их раздумья могут решены кем-то еще из рабочего пространства, кто может посодействовать. Работа в паре держит ход работы в колее. Вероятно, более важно при создании программы больше вечеринок и веселья.

Я начал использовать парное программирование в период развития в некоторых моих семинарах и это значительно увеличивало опыт каждого.

Почему Java имеет успех

Причина того, что Java имеет такой успех в том, что целью было решение проблем, стоящих перед разработчиком сегодня. Цели Java увеличивают продуктивность. Эта продуктивность приходит многими путями, но язык разработан так, что помогает вам насколько это возможно, в то время как вам мешают правила или любые требования, которые обычно вносят особенности. Java предназначен для практики; дизайн языка Java основан на предоставлении программисту максимума пользы.

Система легче для выражения и понимания

Классы, предназначенные для решения проблемы, имеют тенденцию выражать ее легче. Это означает, что когда вы пишите код, вы описываете ваше решение в терминах пространства проблемы (“Put the grommet in the bin”), а не в терминах компьютера, что находится в пространстве (“Установить бит в микросхеме, который обозначает, что реле закроется ”). Вы имеете дело с высокоуровневой концепцией и можете делать больше в простой строке кода.

Другая выгода от этой простоты выражения - это поддержка, которая (если отчету можно верить) занимает большую часть стоимости в течение всей жизни программы. Если программа легка в понимании, то она легче в поддержке. Это также может снизить стоимость создания и поддержки документации.

Максимум рычагов управления библиотеками

Наибыстрейший способ создания программы - это использование кода, который уже написан: библиотеки. Главная цель Java - это создание легких в использовании библиотек. Это выполнено путем преобразования библиотек в новые типы данных (классы), так что введение в библиотеку означает добавление нового типа в языке. Так как компилятор Java заботится о том, как используется библиотека — гарантирую правильную инициализацию и очистку и, убеждаясь, что функции вызываются правильно — вы можете сфокусироваться на том, что вы хотите сделать с помощью библиотеки, а не на том, как вы делаете это.

Обработка ошибок

Обработка ошибок в C - печально известная проблема, и она часто игнорируется — при этом обычно скрещивают пальцы. Если вы строите большую, сложную программу, нет ничего хуже, чем наличие ошибки захороненной где-нибудь без намека на то, откуда она появляется. Обработка исключений в Java - это способ гарантировать то, что ошибка замечена и что кое-что произойдет в результате.

Программирование большого

Многие традиционные языки имеют встроенные ограничения на размер и сложность программы. BASIC, например, может быть великолепным для совместного получения быстрого решения некоторого класса проблем, но если программа длиннее нескольких страниц или осмеливается выйти за пределы нормальной области проблемы для этого языка, то получается как попытка плавать в очень вязкой жидкости. Здесь нет ясных строк, которые говорят вам, когда вашего языка недостаточно, даже если бы это было, вы игнорировали бы это. Вы не скажете: “Моя программа на BASIC стала большой; я перепишу ее на C!” Вместо этого вы попробуете несколько новых строчек, которые добавят новую особенность. Так что вы наберете по инерции дополнительные затраты.

Java предназначена помогать программировать большое, так что стираются эти сложные инерционные границы между маленькой и большой программой. Вам, конечно, не нужно использовать ООП, когда вы пишите вспомогательную программу типа “hello world”, но вы можете использовать особенности когда вам это необходимо. И компилятор одинаково агрессивно охотится на случайные ошибки как в маленькой, так и в большой программе.

Стратегии перехода

Если вас подкупила идея ООП, вероятно, вашим следующим вопросом будет: “Как заставить моего менеджера/коллег/предприятие/сотрудников начать использовать объекты?” Думайте о том как вы — один независимый программист — будете говорить об изучении нового языка и новой парадигмы программирования. Сделайте это прежде. С начала пройдите образование и примеры; затем пройдите пробные проекты, чтобы дать себе почувствовать основы, не делая ничего, что может вас смутить. Затем переходите в “реальный мир” проектов, что действительно полезно делать. В ходе вашего первого проекта вы продолжите ваше образование, читая, задавая вопросы экспертам и получая советы от друзей. Это путь многих опытнейших программистов, советующий переключится на Java. Переключение всей компании будет, конечно, предварена определенной группой, но это поможет на каждом шагу помнить, как это делал один человек.

Руководящие принципы

Здесь приведено несколько руководящий принципов для обдумывания, когда делаете переход на ООП и Java:

1. Тренировка

Это первый шаг в некоторых формах образования. Помните о вложениях компании в код и попробуйте не бросать все в беспорядке через шесть или девять месяцев, пока каждый будет ломать голову как работают интерфейсы. Возьмите маленькую группу для идеологической обработки, лучше всего составленную из людей, которые любопытны, хорошо работают вместе и могут функционировать как собственная сеть поддержки, пока они учат Java.

Альтернативный подход, который иногда предпочтительней, это обучение всех уровней компании одновременно, включая обзорные курсы для стратегических управляющих, так же как и курсы дизайна и программирования для строителей проекта. Это особенно хорошо для маленьких компаний, делающих фундаментальный сдвиг на пути создания вещей, или для отделения большой компании. Однако, поскольку стоимость высока, некоторые могут выбрать начало с проекта тренировочного уровня, выполнить проект под руководством (возможно с приглашенным специалистом), а затем команда проекта станет учителями для всей остальной компании.

2. Проекты низкого риска

Проекты низкого риска всегда первые и позволяют ошибаться. Как только вы приобретете определенный опыт, вы можете либо выбрать другие проекты с членами первой команды, или использовать членов команды как штат ООП технической поддержки. Этот первый проект первое время может работать не правильно, так что это не должно иметь критического значения для компании. Он должен быть простым, самозаконченным и поучительным; это значит, что он должен вызывать создание классов, которые будут многозначительны для других программистов компании, когда они получат их, чтобы включится в изучение Java.

3. Модель успеха

Поищите примеры хорошего объектно-ориентированного дизайна, прежде чем начнете набивать шишки. Есть хорошая вероятность, что кто-нибудь уже решил вашу проблему, а если они не решили ее точно, вы, вероятно, можете применить то, что вы выучили об абстракции для модификации существующего дизайна в соответствии с вашими требованиями. Это общая концепция дизайна шаблонов, описанная в Thinking in Patterns with Java, имеющаяся на www.BruceEckel.com.

4. Использование библиотек существующих классов

Первичная экономическая мотивация для перехода к ООП - это простота в использовании существующего кода в форме библиотек классов (обычно, Стандартные Библиотеки Java, которые описываются в этой книге). Короткий цикл разработки приложения в результате которого вы можете создать и использовать не библиотечные объекты. Однако некоторые начинающие программисты не понимают этого, не зная существующих библиотек классов, или, очаровываясь языком, хотят написать классы, которые уже существуют. Ваш успех в ООП и Java будет оптимизирован, если вы раньше в процессе перехода сделаете усилие по поиску и повторному использованию кода других людей.

5. Не переписывайте существующий в Java код

Не лучший выход взять существующий функционирующий код и переписать его на Java. (Если вы должны включить его в объекты, вы можете связаться с кодом C или C++, используя Java Native Interface, описанный в приложении B.) Есть определенные выгоды, особенно, если код планируют использовать еще. Шанс, что вы не увидите впечатляющее увеличение производительности, как вы надеялись в нескольких первых проектах, за исключением того, что проект новый. Java и ООП сияют лучше, когда переводят проект из концепции в реальность.

Помехи управления

Если вы управляющий, ваша работа - приобретение ресурсов для вашей команды для успешного преодоления барьеров и, главное, попробовать обеспечить наивысшую продуктивность и комфортность, чтобы вашей команде больше нравилось выполнять те чудеса, которые вы от них требуете. Переход на Java разбивает это все на три категории и это чудесно, если это вам не стоит слишком много. Хотя переход на Java может быть дешевле — в зависимости от ваших ограничений — чем ООП альтернативы команды программистов на C (и, вероятно программистов на других процедурных языках), это не свобода, и есть препятствия, которые вы должны знать, прежде чем попытаетесь предать ход Java в своей компании и непосредственно осуществлять переход.

Начальная стоимость

Стоимость перехода на Java это больше, чем просто приобретение компилятора Java (компилятор Java от Sun бесплатный, так что это не помеха). Ваши средне- и долгосрочные затраты будут минимизированы, если вы инвестируете их в практику (и, возможно, в руководство вашим первым проектом), а так же, если вы обнаружите и приобретете библиотеки классов, которые решают ваши проблемы прежде, чем попробуете построить самостоятельно. Это затраты реальных денег, которые должны быть вложены в реалистичные планы. В дополнение, здесь есть спрятанные затраты в потере продуктивности, пока идет изучение нового языка и, возможно, новой среды программирования. Практика и руководство, конечно, могут минимизировать их, но члены команды должны преодолевать препятствия сами, чтобы понять новую технологию. Во время этого процесса они могут сделать много ошибок (это будущее, поскольку узнанные ошибки - скорейший путь обучения) и будут менее продуктивны. Даже тогда, с некоторыми проблемами программирования, правильными классами и правильной средой программирования, возможно, быть более продуктивным, пока учите Java (даже с учетом того, что вы делаете много ошибок и пишите мало строчек кода в день), чем если бы вы остались с C.

Проблемы производительности

Общий вопрос: ООП автоматически делает мою программу больше и медленней?” Ответ: “Это зависит.” Дополнительные особенности безопасности в Java традиционно отстают по производительности по сравнению с такими языками, как C++. Такие технологии, как “яркое пятно” и технологии компиляции значительно увеличивают скорость компиляции в большинстве случаев и продолжают усилия в сторону большей производительности.

Когда вы фокусируетесь на повторяющемся прототипе, вы можете складывать компоненты вместе так быстро, как это, возможно, пока игнорируете проблему эффективности. Если вы используете библиотеки третьих сторон, они обычно уже оптимизированы производителем; в этом случае этой проблемы нет, пока вы в режиме разработки. Когда вы имеете систему, которую хотели, если она маленькая и достаточно быстрая, то вы выполнили задачу. Если это не так, вы начинаете настройку с помощью обрабатывающего инструмента, ищите сначала ускорения, которые могут быть сделаны с помощью переписывания малой части кода. Если это не помогает вам, вы ищите исправления, которые можно сделать в лежащей в основе реализации, так чтобы код, использующий определенный класс, не изменялся. Если же ничто другое не решает вашу проблему, вам надо сменить дизайн. Факт, что производительность критична в той части дизайна - это индикатор, что должны существовать первичные критерии дизайна. Вы получите выгоду, найдя его раньше, используя быстрое развитие.

Если вы нашли функцию, являющуюся узким местом, вы можете переписать ее на C/C++, используя платформозависимые методы Java, описанные в Приложении B.

Общие ошибки дизайна

Когда начинаете с вашей командой работать в ООП и Java, программисты обычно проходят через ряд общих ошибок дизайна. Это часто случается из-за недостаточной обратной связи с экспертом во время дизайна и реализации ранних проектов, потому что в компании нет экспертов разработки и, поэтому, может быть сопротивление в получении консультации. Легко слишком рано почувствовать, что вы понимаете ООП и уйти по касательной. Что-то являющееся очевидным для кого-то опытного в языке, может быть предметом больших внутренних обсуждений для новичка. Многие из этих травм можно пропустить, используя опят внешнего эксперта для тренировки и руководства.

Java против C++?

Java во многом выглядит как C++ и так естественно кажется, что C++ будет заменен Java. Но я начал с вопроса о такой логике. Для одних вещей C++ все еще имеет некоторые особенности, которых нет в Java, и, хотя, имеется много обещаний относительно того, что однажды Java станет быстрее чем C++, мы видели равномерные усовершенствования, но никаких разительных достижений. Так же продолжается определенный интерес к C++, так что я не думаю, что этот язык скоро отомрет. (Языки, кажется, висят вокруг. Разговаривая на одном из моих “промежуточный/продвинутый семинар по Java”, Allen Holub заявил, что два наиболее часто используемых языка - это Rexx и COBOL, в таком порядке.)

Я начинаю думать, что сила Java лежит в небольшом отличие области действия, по сравнению с C++. C++ - это язык, который делает попытку заполнить шаблон. Несомненно, он был адаптирован определенными способами для решения определенных проблем. Некоторые инструменты C++ комбинируют библиотеки, модели компонентов и инструменты генерации кода для решения проблемы разработки оконных приложений для конечного пользователя (для Microsoft Windows). И теперь, И все таки, что используют большинством разработчиков для Windows? Microsoft Visual Basic (VB). Несмотря на факт, что VB производит код, который становится неуправляемым, когда программа становится несколько страниц длины (и синтаксис, который положительно может мистифицировать) Так как есть успех и популярность VB, но это не очень хороший пример языкового дизайна. Было бы хорошо иметь легкость и мощность VB без неуправляемого результирующего кода. И в этом, я думаю, Java будет блистать: как “следующий VB”. Вы можете содрогнуться или нет, услышав это, но думать о том, как много в Java предназначено для упрощения программисту решений проблем уровня приложения, таких как работа в сети и кросс-платформенность, и теперь есть дизайн языка, который позволяет создание очень больших и гибких тел кода. В добавок к этому Java фактически имеет наиболее крепкий тип проверки и обработки ошибок системы, из того что я видел в языках, так что вы можете сделать существенный прыжок вперед в производительности программирования.

Должны ли вы использовать в ваших проектах Java вместо C++? Не в Web апплетах есть две проблемы для исследования. Первая, если вы хотите использовать много существующих библиотек C++ (и вы, конечно, получите большую прибавку производительности), или вы имеете существующий базовый код на C или C++, то Java может замедлить вашу разработку, а не ускорить ее.

Если вы разрабатываете весь ваш код, начиная с шишек, то простота Java по сравнению с C++ значительно сократит время разработки — рассказы очевидцев (истории команд C++, с которыми я говорил и кто перешел на Java) сообщают об удвоении скорости против C++. Если производительность Java не имеет значения или вы можете чем-нибудь компенсировать это, явные проблемы времени-до-продажи делают затруднительным выбор C++ против Java.

Наибольшая проблема - производительность. Интерпретатор Java - медленный, даже в 20-50 раз медленнее, чем C по сравнению с обычным интерпретатором Java. Это улучшится через какое-то время, но все еще будет оставаться значительным числом. Компьютеры о скорости; если бы что-то значительно быстрее было сделать на компьютере, то вы бы делали это руками. (Я даже слышал советы, что вы занимаетесь Java чтобы сократить время разработки, чтобы затем, используя инструменты и библиотеки поддержки, переводите ваш код на C++, если вам необходимо высокая скорость выполнения.)

Ключевым моментом, делающим Java подходящим для большинства проектов - это появление ускорителей, называемый “just-in time” (JIT) компилятор, собственная “hotspot” технология Sun, и компиляторов платформозависимого кода. Конечно, компиляторы платформозависимого кода устранят рекламируемое кросс-платформенное выполнение скомпилированной программы, но они так же повысят скорость выполнения, приблизив ее к C и C++. А кросс-платформенная программа на Java будет много легче, чем если это делать на C или C++. (Теоретически, вы должны просто перекомпилировать, но это обещание было сделано и для других языков.)

Вы можете найти сравнения Java и C++ и обзор использования Java в первой редакции этой книги (Эта книга доступна на сопровождающем CD ROM, так же как и на www.BruceEckel.com).

Резюме

В этой главе осуществлена попытка дать вам почувствовать большинство проблем объектно-ориентированного программирования и Java, включая ту, чем отличается ООП и чем обычно отличается Java, концепцию ООП методологий и, наконец, виды проблем, обнаруживаемые при переходе вашей компании к ООП и Java.

ООП и Java не могут быть для всех. Важно оценить свои собственные требования и решить будет ли Java оптимально удовлетворять вашим требованиям или, если это лучше, использовать другую систему программирования (включая те, которые вы используете сейчас). Если вы знаете, что то что вам нужно будет очень специфичным в обозримом будущем, и если вы имеете специфические ограничения, которые Java не сможет удовлетворить, то вам необходимо исследовать альтернативы [19]. Даже если вы, в конечном счете, выберете Java как свой язык, вы, по крайней мере, поймете, что выбор имел и имеет ясное видение, почему вы выбрали это направление.

Вы знаете как выглядят процедурные программы: определение данных и вызов функций. Чтобы найти значение этой программы, вам нужно немного поработать, просмотреть вызовы функций и низкоуровневую концепцию для создания модели в вашем уме. По этой причине мы нуждаемся в промежуточном представлении при разработке процедурной программы — сами по себе эти программы имеют тенденцию быть запущенными, потому что термины выражений больше ориентируются на компьютер, чем на решаемую проблему.

Поскольку Java добавляет много новых концепций поверх того, что вы находите в процедурных языках, ваше естественным предположением может быть то, что main( ) в программе Java будет более сложным, чем для эквивалентной программы C. Но здесь вы будите удивлены: хорошо написанная Java программа обычно более проста и легка в понимании, чем эквивалентная C программа. Как вы увидите - определение объектов, представляющих концепцию в вашей проблемной области (скорее, чем проблему компьютерного представления), и сообщений, посылаемых этим объектам, представляют активность в этой области. Одно из поразительных свойств объектно-ориентированного программирование в том, что хорошо разработанную программу легче понять при чтении кода. Обычно это много меньше кода, поскольку многие ваши проблемы будут решены с помощью кода библиотек многократного использования.


[2] Смотрите Multiparadigm Programming in Leda Timothy Budd (Addison-Wesley 1995).

[3] Некоторые люди делают различия, заявляя, что тип определяет интерфейс, в то время как класс - обычная реализация этого интерфейса.

[4] Я в долгу перед моим другом Scott Meyers за этот термин.

[5] Это обычно достаточно детализировано для большинства диаграмм, и вам нет необходимости указывать будете ли вы использовать агрегирование или композицию.

[6] Мой термин.

[7] Примитивные типы, которые вы выучите позже - специальный случай.

[8] Великолепный пример такой UML Выборки, 2е издание Martin Fowler (Addison-Wesley 2000), который снижает иногда перегруженный UML процесс до управляемого набора.

[9] Мое правило большого пальца для оценки таких проектов: Если есть больше одной пустой карточки, даже не пробуйте планировать сколько времени это займет или сколько это будет стоить, пока не создадите рабочий прототип. Есть слишком много степеней свободы.

[10] Спасибо за помощь James H Jarrett.

[11] Подробнее об использовании причин можно найти в Applying Use Cases Schneider & Winters (Addison-Wesley 1998) и Use Case Driven Object Modeling with UML Rosenberg (Addison-Wesley 1999).

[12] Мой персональный взгляд на это изменился за последнее время. Удвоение и добавление 10 процентов даст вам разумный точный расчет (принимая во внимание не слишком много факторов пустых карточек), но вы все еще должны тщательно работать, чтобы успеть точно в это время. Если вы хотите работать действительно элегантно и наслаждаться в процессе - правильный множитель, я верю, три или четыре.

[13] Для начала я рекомендую вышеупомянутую UML Distilled, 2е издание.

[14] Python (www.Python.org) часто используется как “исполняемый псевдокод”.

[15] Не менее одного аспекта эволюции описано в книге Martin Fowler Refactoring: improving the design of existing code (Addison-Wesley 1999), который использует исключительно примеры на Java.

[16] Иногда это как “быстрое создание прототипов”, где вы, как предполагалось, строите быструю-и-грязную версию, которая помогает вам изучить систему, а затем выбрасываете прототип и строите правильный. Проблема быстрого создания прототипов в том, что люди не выбрасывают прототип, а вместо этого строят на нем. Вместе со слабой структурой процедурного программирования, это часто ведет к грязным системам, очень дорогими в поддержке.

[17] Хотя это может быть более американской перспективой, Голливудсткие истории достигают каждого.

[18] Включая (особенно) PA системы. Однажды я работал в компании, которая настаивала на радиовещании каждого телефонного звонка, приходящего каждому индивидуально, и это прерывало нашу продуктивность (но менеджеры не могли представить себе отмирание такой важной услуги, как PA). Наконец, когда никто не видел, я начал отрезать провода динамика.

[19] Обычно я рекомендую взглянуть на Python (http://www.Python.org).

[ Предыдущая глава ] [ Краткое содержание ] [ Содержание ] [ Индекс ] [ Следующая глава ]