![]() |
![]() |
![]() |
Анимация
JavaScript
|
Главная Библионтека Транзакции и гениальные указатели ![]() Мы подошли к последней главе, посвященной косвенным обращениям (которая, впрочем, далеко не исчерпывает этой темы). Давайте посмотрим, как развить идею умных указателей. Встречавшиеся до сих пор идиомы и примеры умных и мудрых указателей, курсоров и итераторов главным образом были локализованы в одном инкапсулированном объекте. Хватит мелочиться - пора поговорить о том, как будет выглядеть архитектура в целом, если применить эти идиомы в более широком масштабе. Указатели, рассматриваемые в этой главе, умны настолько, что становится просто страшно. Эпитеты «умный» и «мудрый» уже не описывают их в достаточной степени. «Потрясающие, ослепительные, невероятно одаренные» звучит слишком выспренне, поэтому я скромно назову их «гениальными». Предупреждение: в этой главе проверяется не только ваше мастерство, но и воображение. Описанные ниже проблемы и идиомы напоминают узкую горную тропу, по которой можно проехать только на велосипеде. Надевайте шлем и почаще останавливайтесь, чтобы передохнуть. Тернистые пути дизайна Как правило, после описания идиом в этой книге сразу же рассматриваются их практические применения. Позвольте мне отклониться от этой традиции и обсудить некоторые проблемы дизайна, которые понадобятся нам позднее - по мере того, как будет разворачиваться действие этой главы. Транзакции В приложениях клиент/сервер и базах данных часто встречается ситуация, когда несколько удаленных пользователей обращаются с запросом на обновление одной базы или структуры данных. Квант обновления, или транзакция (transaction), с точки зрения клиента может состоять из нескольких элементарных изменений в базе или структуре данных. В таких ситуациях разработчики руководствуются несколькими правилами, позаимствованными из мира СУБД: 1. Транзакции должны быть атомарными. Либо вносятся все изменения, необходимые для совершения транзакции, либо никакие. 2. Во время обработки транзакции одного клиента данные должны выглядеть так, как они выглядели в начале незавершенных транзакций всех остальных клиентов. До момента своего завершения транзакция остается невидимой для других клиентов; все выглядит так, словно транзакция и не начиналась. 3. Если сразу несколько клиентов в своих транзакциях пытаются изменить один объект, допускается успешное завершение не более одной транзакции. Последнее правило гарантирует, что каждый объект обновляется не более чем одним клиентом. Существуют две стратегии его реализации: 1. Перед выполнением обновления каждая транзакция блокирует все объекты, которые она собирается изменить. Если транзакции не удается установить необходимые блокировки, она вообще не начинается. 2. Запрос на блокировку выдается не вначале, а во время транзакции по мере выполнения обновлений. Если необходимый объект уже заблокирован другим клиентом, то транзакция либо ожидает его освобождения, либо завершается неудачей. Вторая стратегия может привести к взаимной блокировке (deadlock): транзакция A блокирует объект X и ожидает возможности блокировки объекта Y, тогда как транзакция B блокирует объект Y и ожидает возможности блокировки объекта X. В мире баз данных на эту тему написано огромное количество литературы, поэтому нет смысла ее здесь подробно рассматривать. Нас интересуют те идиомы и возможности С++, которые облегчают решение целого класса подобных задач. Отмена Многие графические приложения любезно разрешают пользователю отменить последнюю выполненную операцию. На первый взгляд в этом нет ничего сложного, но в действительности не все так просто. В объектно-ориентированном мире найдется не так уж много проблем дизайна, которые вызывают большую головную боль, чем проблемы отмены в сложных приложениях. Чтобы реализовать отмену, многие программисты «зашивают» в программу структуры данных, ориентированные на конкретную операцию, но такой подход чреват ошибками и неустойчив при сопровождении программы. В некоторых языках (таких как Lisp) можно буквально делать «снимки» памяти в различные моменты времени. При наличии такого снимка вернуться к прежнему состоянию приложения очень просто. Увы. В С++ таких изящных возможностей не предусмотрено; зато наши программы не запрашивают 3 Гб памяти и успевают завершить работу еще до обеда. Так что это замечание стоит воспринимать не как критику С++, а как констатацию факта: проблема есть, и ее необходимо решать. В дальнейшем обсуждении будут рассматриваться две вариации на эту тему. Одни приложения всегда предоставляют один уровень отмены, а в других пользователь может выбирать команду Undo снова и снова - программа каждый раз возвращается на одну операцию назад. Конечно, ограничения все же существуют, но обычно не очень жесткие. Это называется «многоуровневой отменой» - проблема, которой мы займемся после одноуровневой отмены. Вторая вариация относится к контекстной отмене. Если пользователь выполняет операцию отмены сначала в одном, а потом - в другом окне, то после возврата к первому окну и выполнения команды Undo обычно следует отменить последнюю операцию только для первого окна, а не для всего приложения. Иначе говоря, операция отмены учитывает контекст приложения на момент своего вызова. Хватит? Существуют и другие проблемы, относящиеся к тому же классу, но и сказанного вполне достаточно, чтобы подтолкнуть нас к дальнейшим исследованиям. Большинство программистов рассматривает эти проблемы как нечто изолированное в контексте конкретного приложения. Однако, как вы вскоре убедитесь, некоторые специфические (а кто-то скажет - извращенные) особенности синтаксиса С++ позволяют приблизиться к общим решениям. Помните: мы изучаем С++ и его идиомы, а не структуры данных или принципы проектирования программ. Многие архитектуры и варианты здесь не приводятся, однако это вовсе не говорит против них. Образы и указатели Разве можно начать новую главу и не ввести новую разновидность указателей? Состояние объекта можно восстановить двумя способами: сохранить его образ (image) до и после операции или же хранить информацию, достаточную для выполнения операций в обратном направлении. Вариант достаточно прост и универсален, а попытки возврата через выполнение операций в обратном направлении привязаны к конкретному объекту и приложению, поэтому я предпочитаю хранить несколько копий одного объекта. Ключевая концепция, обеспечивающая реализацию этой методики на С++ - указатель образов (image pointer). Такие указатели незаметно для клиента содержат несколько 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 [ 41 ] 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
![]() |