В.В. Кулямин - Технологии программирования. Компонентный подход (1134162), страница 6
Текст из файла (страница 6)
Одним из примеров«живого» процесса разработки является набор техник, известный как экстремальноепрограммирование (Extreme Programming, XP). Некоторые аспекты этих подходов также будутрассмотрены в данном курсе.Принципы работы со сложными системамиПомимо методических рекомендаций, при конструировании больших систем частоиспользуются прагматические принципы работы со сложными системами вообще. Они играютзначительную роль в выработке качественных технических решений в достаточно широкомконтексте.
Эти принципы позволяют распределять работы между участвующими в проектахлюдьми с меньшими затратами на обеспечение их взаимодействия и акцентировать вниманиекаждого из участников на наиболее существенных для его части работы характеристиках системы.К таким принципам относятся использование абстракции и уточнения, модульная разработка ипереиспользование.• Абстракция (abstraction) и уточнение (refinement).Абстракция является универсальным подходом к рассмотрению сложных вещей.
Интеллектодного человека достаточно ограничен и просто не в силах иметь дело сразу со всемиэлементами и свойствами систем большой сложности. Известно, что человеку крайнетяжело держать в голове одновременно десяток-полтора различных мыслей, а всовременных системах число различных существенных аспектов доходит до сотен. Длятого чтобы как-то все-таки работать с такими системами, мы пользуемся своейвозможностью абстрагироваться, т.е. отвлекаться от всего, что несущественно длядостижения поставленной в данной момент частной цели и не влияет на те аспектырассматриваемого предмета, которые для этой цели важны.Чтобы перейти от абстрактного представления к более конкретному, используетсяобратный процесс последовательного уточнения. Рассмотрев систему в каждом аспекте вотдельности, мы пытаемся объединить результаты анализа, добавляя аспекты по одному иобращая при этом внимание на возможные взаимные влияния и возникающие связи междуэлементами, выявленными при анализе отдельных аспектов.Абстракция и уточнение используются, прежде всего, для получения работоспособныхрешений, гарантирующих нужные свойства результирующей системы.Пример абстракции и уточнения.Систему хранения идентификаторов пользователей Интернет-магазина можно представитькак множество целых чисел, забыв о том, что эти числа — идентификаторы пользователей,и о том, что все это как-то связано с Интернет-магазином.Затем описанную модель системы хранения идентификаторов пользователей Интернетмагазина можно уточнить, определив конкретную реализацию множества чисел, например,на основе сбалансированных красно-черных деревьев (см.
[2], раздел 14, глава III и JDKклассы java.util.TreeSet и java.util.TreeMap).14•Другой пример.Рассматривая задачу передачи данных по сети, можно временно абстрагироваться отбольшинства проблем организации связи и заниматься только одним аспектом —организацией надежной передачи данных в нужной последовательности. При этом можнопредполагать, что мы как-то умеем передавать данные между двумя компьютерами в сети,хотя, быть может, и с потерями и с нарушением порядка их прибытия по сравнению спорядком отправки.
Установленные ограничения выделяют достаточно узкий набор задач.Любое их решение представляет собой некоторый протокол передачи данныхтранспортного уровня, т.е. нацеленный именно на надежную упорядоченную доставкуданных. Выбирая такой протокол из уже существующих, например, TCP, или разрабатываяновый, мы производим уточнение исходной общей задачи передачи данных.Другой способ уточнения — перейти к рассмотрению протоколов, обеспечивающихисходные условия для нашей первой абстракции, т.е. возможность вообще что-топередавать по сети.
При этом возникают протоколы нижележащих уровней — сетевого(отвечают за организацию связи между не соединенными непосредственно компьютерамипри наличии между ними цепи машин, соединенных напрямую), канального (такиепротоколы отвечают за определение формата передаваемых данных и надежность передачиотдельных элементов информации между двумя физически соединенными компьютерами)и физического (отвечают за определение физического носителя передаваемого сигнала иправильность интерпретации таких сигналов обеими машинами, в частности, законкретный способ передачи битов с помощью электрических сигналов или радиоволн).Модульность (modularity).Модульность — принцип организации больших систем в виде наборов подсистем, модулейили компонентов.
Этот принцип предписывает организовывать сложную систему в виденабора более простых систем — модулей, взаимодействующих друг с другом через четкоопределенные интерфейсы. При этом каждая задача, решаемая всей системой, разбиваетсяна более простые, решаемые отдельными модулями подзадачи, решение которых, будучискомбинировано определенным образом, дает в итоге решение исходной задачи. Послеэтого можно отдельно рассматривать каждую подзадачу и модуль, ответственный за еерешение, и отдельно — вопросы интеграции полученного набора модулей в целостнуюсистему, способную решать исходные задачи.Выделение четких интерфейсов для взаимодействия упрощает интеграцию, позволяяпроводить ее на основе явно очерченных возможностей этих интерфейсов, без обращения кмногочисленным внутренним элементам модулей, что привело бы к росту сложности.Пример.Примером разбиения на модули может служить структура пакетов и классов библиотекиJDK.
Классы, связанные с основными сущностями языка Java и виртуальной машины,собраны в пакете java.lang. Вспомогательные широко применяемые в различныхприложениях классы, такие, как коллекции, представления даты и пр., собраны вjava.util. Классы, используемые для реализации потокового ввода-вывода — в пакетеjava.io, и т.д.Интерфейсом класса служат его общедоступные методы, а интерфейсом пакета — егообщедоступные классы.Другой пример.Другой пример модульности — принятый способ организации протоколов передачиданных. Мы уже видели, что удобно выделять несколько уровней протоколов, чтобы накаждом решать свои задачи. При этом надо определить, как информация передается отмашины к машине при помощи всего этого многоуровневого механизма.
Обычное решениетаково: для каждого уровня определяется способ передачи информации с или на верхнийуровень — предоставляемые данным уровнем службы. Точно так же определяется, в какихслужбах нижнего уровня нуждается верхний, т.е. как передать данные на нижний уровень иполучить их оттуда. После этого каждый протокол на данном уровне может бытьсформулирован в терминах обращений к нижнему уровню и должен реализовать операции15службы, необходимые верхнему. Это позволяет заменять протокол-модуль на одном уровнебез внесения изменений в другие.Хорошее разбиение системы на модули — непростая задача. При ее выполнениипривлекаются следующие дополнительные принципы.o Выделение интерфейсов и сокрытие информации.Модули должны взаимодействовать друг с другом через четко определенныеинтерфейсы и скрывать друг от друга внутреннюю информацию — внутренние данные,детали реализации интерфейсных операций.При этом интерфейс модуля обычно значительно меньше, чем набор всех операций иданных в нем.Например, класс java.util.Queue<type E>, реализующий функциональность очередиэлементов типа E, имеет следующий интерфейс.E element()Возвращает элемент, стоящий в голове очереди, не изменяяее.
Создает исключение NoSuchElementException, еслиочередь пуста.boolean offer(E o)Вставляет, если возможно, данный элемент в конец очереди.Возвращает true, если вставка прошла успешно, false —иначе.E peek()Возвращает элемент, стоящий в голове очереди, не изменяяее. Возвращает null, если очередь пуста.E poll()Возвращает элемент, стоящий в голове очереди, и удаляет егоиз очереди. Возвращает null, если очередь пуста.E remove()Возвращает элемент, стоящий в голове очереди, и удаляет егоиз очереди. Создает исключение NoSuchElementException,если очередь пуста.Внутренние же данные и операции одного из классов, реализующих данный интерфейс,— PriorityBlockingQueue<E> — достаточно сложны. Этот класс реализует очередь сэффективной синхронизацией операций, позволяющей работать с таким объектомнескольким параллельным потокам без лишних ограничений на их синхронизацию.Например, один поток может добавлять элемент в конец непустой очереди, а другой вто же время извлекать ее первый элемент.package java.util.concurrent;import java.util.concurrent.locks.*;import java.util.*;public class PriorityBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {private static final long serialVersionUID = 5595510919245408276L;private final PriorityQueue<E> q;private final ReentrantLock lock = new ReentrantLock(true);private final ReentrantLock.ConditionObject notEmpty =lock.newCondition();public PriorityBlockingQueue() { ...