Читайте также:
|
|
При реализации паттерна заместитель можно использовать следующие возможности языка:
а перегрузку оператора доступа к членам в C++. Язык C++ поддерживает перегрузку оператора доступа к членам класса ->. Это позволяет производить дополнительные действия при любом разыменовании указателя на объект. Для реализации некоторых видов заместителей это оказывается полезно, поскольку заместитель ведет себя аналогично указателю. В следующем примере показано, как воспользоваться данным приемом для реализации виртуального заместителя imagePtr:
class Image;
extern Image* LoadAnImageFile(const char*); // внешняя функция
class ImagePtr { public:
ImagePtr (const char* imageFile);
virtual -ImagePtr ();
virtual Image* operator-> ();
virtual Image& operator* (); private:
Image* Loadlmage (); private:
Image* _image;
const char* _imageFile;
ImagePtr::ImagePtr (const char* theImageFile) _imageFile = theImageFile; _image = 0;
"1 "1 |
Структурные паттерны
Image* ImagePtr::LoadImage () {
if (_image ==0) {
_image = LoadAnlmageFile(_imageFile);
}
return _image; }
Перегруженные операторы -> и * используют операцию Loadlmage для возврата клиенту изображения, хранящегося в переменной _image (при необходимости загрузив его):
Image* ImagePtr::operator-> () { return Loadlmage();
Image& ImagePtr::operator* () {
return *LoadImage(); }
Такой подход позволяет вызывать операции объекта Image через объекты ImagePtr, не заботясь о том, что они не являются частью интерфейса данного класса:
ImagePtr image = ImagePtr("anlmageFileName"); image->Draw(Point (50, 100));
// (image.operator->())->Draw(Point(50, 100))
Обратите внимание, что заместитель изображения ведет себя подобно указателю, но не объявлен как указатель на Image. Это означает, что использовать его в точности как настоящий указатель на Image нельзя. Поэтому при таком подходе клиентам следует трактовать объекты Image и ImagePtr по-разному.
Перегрузка оператора доступа - лучшее решение далеко не для всех видов заместителей. Некоторым из них должно быть точно известно, какая операция вызывается, а в таких случаях перегрузка оператора доступа не работает. Рассмотрим пример виртуального заместителя, обсуждавшийся в разделе «Мотивация». Изображение нужно загружать в точно определенное время - при вызове операции Draw, а не при каждом обращении к нему. Перегрузка оператора доступа не позволяет различить подобные случаи. В такой ситуации придется вручную реализовать каждую операцию заместителя, переадресующую запрос субъекту.
Обычно все эти операции очень похожи друг на друга, как видно из примера кода в одноименном разделе. Они проверяют, что запрос корректен, что объект-адресат существует и т.д., а потом уже перенаправляют ему запрос. Писать этот код снова и снова надоедает. Поэтому нередко для его автомаА тической генерации используют препроцессор;
а метод doesNotUnderstand в Smalltalk. В языке Smalltalk есть возможность, позволяющая автоматически поддержать переадресацию запросов. При отправлении клиентом сообщения, для которого у получателя нет соответствующего метода, Smalltalk вызывает метод doesNotUnderstand: aMessage.
Паттерн Proxy
Заместитель может переопределить doesNotUnderstand так, что сообщение будет переадресовано субъекту.
Дабы гарантировать, что запрос будет перенаправлен субъекту, а не просто тихо поглощен заместителем, класс Proxy можно определить так, что он не станет понимать никаких сообщений. Smalltalk позволяет это сделать, надо лишь, чтобы у Proxy не было суперкласса1.
Главный недостаток метода doesNotUnderstand: в том, что в большинстве Smalltalk-систем имеется несколько специальных сообщений, обрабатываемых непосредственно виртуальной машиной, а в этом случае стандартный механизм поиска методов обходится. Правда, единственной такой операцией, написанной в классе Ob j ect (следовательно, могущей затронуть заместителей), является тождество ==.
Если вы собираетесь применять doesNotUnderstand: для реализация заместителя, то должны как-то решить вышеописанную проблему. Нельзя же ожидать, что совпадение заместителей - это то же самое, что и совпадение реальных субъектов. К сожалению, doesNotUnderstand: изначально создавался для обработки ошибок, а не для построения заместителей, поэтому его быстродействие оставляет желать лучшего;
а заместителю не всегда должен быть известен тип реального объекта. Если класс Proxy может работать с субъектом только через его абстрактный интерфейс, то не нужно создавать Proxy для каждого класса реального субъекта Real Sub j ect; заместитель может обращаться к любому из них единообразно. Но если заместитель должен инстанцировать реальных субъектов (как обстоит дело в случае виртуальных заместителей), то знание конкретного класса обязательно.
К проблемам реализации можно отнести и решение вопроса о том, как обращаться к еще не инстанцированному субъекту. Некоторые заместители должны обращаться к своим субъектам вне зависимости от того, где они находятся - диске или в памяти. Это означает, что нужно использовать какую-то форму не зависящих от адресного пространства идентификаторов объектов. В разделе «Мотивация» для этой цели использовалось имя файла.
Пример кода
В коде реализовано два вида заместителей: виртуальный, описанный в разделе «Мотивация», и реализованный с помощью метода doesNotUnderstand:.2
а виртуальный заместитель. В классе Graphic определен интерфейс для графических объектов:
class Graphic { public:
virtual -Graphic();
1 Эта техника используется при реализации распределенных объектов в системе NEXTSTEP [Add94]
(точнее, в классе NXProxy). Только там переопределяется метод forward - эквивалент описанного
только что приема в Smalltalk.
2 Еще один вид заместителя дает паттерн итератор.
Структурные паттерны
virtual void Draw(const Point& at) = 0; virtual void HandleMouse (Event& event) = 0;
virtual const Point& GetExtent() = 0;
virtual void Load(istream& from) = 0; virtual void Save(ostream& to) = 0; protected:
Graphic();
Класс Image реализует интерфейс Graphic для отображения файлов изображений. В нем замещена операция HandleMouse, посредством которой пользователь может интерактивно изменять размер изображения:
class Image: public Graphic { public:
Image(const char* file); // загрузка изображения из файла
virtual ~Image();
virtual void Draw(const Point& at); virtual void HandleMouse(Event& event);
virtual const Point& GetExtent();
virtual void Load(istream& from); virtual void Save(ostream& to); private: //...
Класс imageProxy имеет тот же интерфейс, что и Image:
class ImageProxy: public Graphic { public:
ImageProxy(const char* imageFile);
virtual ~ImageProxy();
virtual void Draw(const Point& at); virtual void HandleMouse(Event& event);
virtual const Points GetExtent();
virtual void Load(istream& from);
virtual void Save(ostream& to); protected:
Image* GetlmageO; private:
Image* _image;
Point _extent;
char* _fileName; i.
Дата добавления: 2015-09-11; просмотров: 63 | Поможем написать вашу работу | Нарушение авторских прав |