[ Обновленные темы · Новые сообщения · Участники · Правила форума · Поиск · RSS ]
  • Страница 2 из 2
  • «
  • 1
  • 2
Архитектурный подход к программированию. Паттерны.
eXceedДата: Четверг, 08.05.2008, 12:35 | Сообщение # 16
Генералиссимус
Группа: Гости
Сообщений: 5466
Репутация: 616
Статус: Offline
Expert
Quote
Assembler у КЗОИ с нового учебного года перестанет быть факультативом, а станет обязательной дисциплиной (мы внесли изменения в учебный план).

Так держать! Нужно давно начинать программировать как настоящие мужчины! xD


bda-expert.ru — это система форумов, где можно общаться быстро и свободно, где любая точка зрения имеет право на жизнь.
 
eXceedДата: Четверг, 08.05.2008, 13:03 | Сообщение # 17
Генералиссимус
Группа: Гости
Сообщений: 5466
Репутация: 616
Статус: Offline
Абстрактная фабрика (Abstract Factory, Factory)

Это порождающий паттерн проектирования.

Проблема: Создать семейство взаимосвязанных или взаимозависимых обьектов (не специфицируя их конкретных классов).

Решение: Создать абстрактный класс, в котором объявлен интерфейс для создания конкретных классов.

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

Преимущества: Изолирует конкретные классы. Поскольку "Абстрактная фабрика" инкапсулирует ответственность за создание классов и сам процесс их создания, то она изолирует клиента от деталей реализации классов. Упрощена замена "Абстрактной фабрики", поскольку она используется в приложении только один раз при инстанцировании.

Недостатки: Интерфейс "Абстрактной фабрики" фиксирует набор обьектов, которые можно создать. Расширение "Абстрактной фабрики" для изготовления новых обьектов часто затруднительно.

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

#include <iostream>
#include <string>
#include <map>
#include <boost/shared_ptr.hpp>

template <class TBase>
class TFactory
{
public:

typedef boost::shared_ptr<TBase> BasePtr;

template <class TDerived>
void Register(const std::string& name)
{
m_FactoryContainer[name] = BaseTypePtr(new TDerivedType<TDerived>);
}

BasePtr Create(const std::string& name)
{
return m_FactoryContainer[name]->Create();
}

private:

class BaseType
{
public:

virtual ~BaseType() {}

virtual BasePtr Create() const = 0;
};

typedef boost::shared_ptr<BaseType> BaseTypePtr;

template <class T>
class TDerivedType : public BaseType
{
public:

virtual BasePtr Create() const
{
return new T;
}
};

typedef std::map<std::string, BaseTypePtr> FactoryContainer;

FactoryContainer m_FactoryContainer;
};

class Furniture
{
public:

~Furniture() {}

virtual void ShowName() = 0;
};

class Table : public Furniture
{
public:

virtual void ShowName()
{
std::cout << "Table" << std::endl;
}
};

class Chair : public Furniture
{
public:

virtual void ShowName()
{
std::cout << "Chair" << std::endl;
}
};

int main()
{
typedef TFactory<Furniture> FurnitureFactory;
typedef FurnitureFactory::BasePtr FurniturePtr;

FurnitureFactory factory;

factory.Register<Table>("Table");
factory.Register<Chair>("Chair");

FurniturePtr f1 = factory.Create("Table");
FurniturePtr f2 = factory.Create("Chair");

f1->ShowName();
f2->ShowName();

return 0;
}

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

В следующий раз рассмотрим паттерн Адаптер и обсудим некоторые виды архитектур.


bda-expert.ru — это система форумов, где можно общаться быстро и свободно, где любая точка зрения имеет право на жизнь.
 
vitalyuДата: Пятница, 09.05.2008, 16:05 | Сообщение # 18
Генерал-полковник
Группа: Гости
Сообщений: 852
Репутация: 108
Статус: Offline
Quote (eXceed)
Quote Ведь, как удобно, к примеру, сделать указатель на интерфейс и обращаться по нему из модулей

U Fail!Указатели на интерфейс не создаются. Через интерфейс ты взаимодействуешь с модулем.

Сам себя опроверг нижеследующим сообщением

Quote (eXceed)
Работаешь по указателям. Хотя вот насчет такой возможности в Delphi я сомневаюсь. В C++ просто работаешь с указателем на объект.

Тогда, к чему было U Fail? dry По указателям на объект интерфейса в Delphi работать МОЖНО и НУЖНО!!!

Спасиб, канечн, за статьи, интересно почитать, но и др. считать некомпетентными не стоит dry

Добавлено (09.05.2008, 15:59)
---------------------------------------------
FAQ по Delphi, раздел "Представление интерфейса в памяти"

Quote
type
TMethodTable = array[0..9999] of Pointer;
TNextLineFunc = function (Self: ITextReader): Boolean;
var
Intf: ITextReader; // интерфейсная переменна
IntfPtr: Pointer; // адрес внутри интерфейсной переменной
TablePtr: ^TMethodTable; // указатель на таблицу методов интерфейса
MethodPtr: Pointer; // указатель на метод
begin
...
IntfPtr := Pointer(Intf); // 1) извлечение адреса из интерфейсной переменной
TablePtr := Pointer(IntfPtr^); // 2) извлечение адреса таблицы методов интерфейса
MethodPtr := TablePtr^[3]; // 3) извлечение адреса нужного метода из таблицы
TNextLineFunc(MethodPtr)(Intf); // 4) вызов метода через переходник
...
end.

Добавлено (09.05.2008, 16:05)
---------------------------------------------
Лан, не в том суть .. Главное разобрались .. Здесь не спорить, а учиться надо .. smile
Про фабрику объектов вечерком отпишу wink

Да, про КЗОИ и ASM - в моей группе хоть и было факультативом, но ходили все. Сейчас у нас курсовая по криптографии, и все не раз вспоминали добрым словом этот курс, когда оптимизировали математику АСМовскими вставками ..

И вообще, хотелось бы больше предметов по программированию. Вот, все, что у нас было по программе - это семестр C++ (причем Borland C++, имхо, тупо) и семестр ASM .. и все .. Какие из нас специалисты? Нет, ну мы, конечно, не прогеры (жалко), но хотя бы должны уметь программу на "закладки" проверить .. нужно больше инфы системной!

xor eax, eax


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


Сообщение отредактировал vitalyu - Пятница, 09.05.2008, 16:14
 
eXceedДата: Пятница, 09.05.2008, 17:36 | Сообщение # 19
Генералиссимус
Группа: Гости
Сообщений: 5466
Репутация: 616
Статус: Offline
Дело в том, что интерфейс класса это не объект! А лишь методы которые класс экспортирует!
Да, в СГУ не учат программировать нормально, так, как программируют программисты. Но пока я есть здесь и пока пишу статьи -> учитесь, совершенствуйтесь.


bda-expert.ru — это система форумов, где можно общаться быстро и свободно, где любая точка зрения имеет право на жизнь.
 
eXceedДата: Суббота, 10.05.2008, 13:52 | Сообщение # 20
Генералиссимус
Группа: Гости
Сообщений: 5466
Репутация: 616
Статус: Offline
Продолжаем разбирать паттерны. Сегодня это паттерн Адаптер. Тип паттерна: структурный.

Задача

Система поддерживает требуемые данные и поведение, но имеет неподходящий интерфейс. Чаще всего шаблон Адаптер применяется если необходимо создать класс, производный от вновь определяемого или уже существующего абстрактного класса.

Способ решения

Адаптер предусматривает создание класса-оболочки с требуемым интерфейсом.

Участники

Класс Adapter приводит интерфейс класса Adaptee в соответствие с интерфейсом класса Target (наследником которого является Adapter). Это позволяет объекту Client использовать объект Adaptee так, словно он является экземпляром класса Target.

Следствия

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

Реализация

Включение уже существующего класса в другой класс. Интерфейс включающего класса приводится в соответствие с новыми требованиями, а вызовы его методов преобразуются в вызовы методов включённого класса.

Замечание

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

Применение

Типичным примером использования шаблона Адаптер можно назвать создание классов, приводящих к единому интерфейсу функции языка PHP обеспечивающие доступ к различным СУБД.

Пример реализации выложу как только появится время =)

Прикрепления: 6436930.png (20.4 Kb)


bda-expert.ru — это система форумов, где можно общаться быстро и свободно, где любая точка зрения имеет право на жизнь.
 
eXceedДата: Понедельник, 12.05.2008, 16:30 | Сообщение # 21
Генералиссимус
Группа: Гости
Сообщений: 5466
Репутация: 616
Статус: Offline
Небольшой оффтоп на правах модератора.

Всё, что мне надо - есть, спать и программировать.

~ Self-perfection про Смысл жизни

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

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

ВАЖНО ПОНИМАТЬ, ЧТО РАННЕЕ ВЫЯВЛЕНИЕ НЕДУГА И ПРОФИЛАКТИКА ЗНАЧИТЕЛЬНО УПРОЩАЕТ ЛЕЧЕНИЕ, КОТОРОЕ НА ПОЗДНИХ СТАДИЯХ ЗАБОЛЕВАНИЯ СТАНОВИТСЯ И ВОВСЕ НЕВОЗМОЖНЫМ!

Особую группу риска традиционно составляют подростки, которые в силу доступности компьютеров в современном мире могут узнать о существовании не только безобидных порнографии и пиратских видеоиграх, но и о гораздо более серьёзном и крайне опасном явлении — программировании! Легко попадая под влияние внешне притягательных, но крайне деструктивных внутренне, странных символов и знаков, которые в глазах подростка начинают приобретать мистический смысл, нестабильное душевное состояние подростка окончательно расшатывается. Родителям надо проявлять крайнюю волю в борьбе с недугом, но в то же время быть терпеливыми к помутнениям рассудка своего чада. Лечение программирования — процесс комплексный и содержит в себе как демонстрацию порнографии и участие в мордобое, так и активную терапию алкоголем и куревом. Не поскупитесь купить своему ребёнку девушку легкого поведения — это ощутимо поможет в процессе стабилизации пошатнувшегося психического состояния подростка и возвращения его интереса к обьектам в реальном, а не вымышленном мире.

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

ПОСТАРАЕМСЯ ПОБЕДИТЬ БОЛЕЗНЬ ВМЕСТЕ!

Оригинальная статья тут.


bda-expert.ru — это система форумов, где можно общаться быстро и свободно, где любая точка зрения имеет право на жизнь.
 
eXceedДата: Пятница, 16.05.2008, 01:16 | Сообщение # 22
Генералиссимус
Группа: Гости
Сообщений: 5466
Репутация: 616
Статус: Offline
Продолжаем курить паттерны.

Сегодня плотно забиваем и раскуриваем один из важнейших паттернов при построении сложных интеллектуальных систем: Наблюдатель.

Наблюдатель — поведенческий шаблон проектирования. Также известен как «подчинённые» (Dependents), «издатель-подписчик» (Publisher-Subscriber).

Назначение

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

При реализации шаблона «наблюдатель» обычно используются следующие классы.

* Observable - интерфейс, определяющий методы для добавления, удаления и оповещения наблюдателей.
* Observer - интерфейс, с помощью которого наблюдаемый объект оповещает наблюдателей.
* ConcreteObservable - конкретный класс, который реализует интерфейс Observable.
* ConcreteObserver - конкретный класс, который реализует интерфейс Observer.

Область применения

Шаблон «наблюдатель» применяется в тех случаях, когда система обладает следующими свойствами:

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

Данный шаблон часто применяют в ситуациях, в которых отправителя сообщений не интересует, что делают с предоставленной им информацией получатели.

Пример реализации(Виталик, для тебя особенно интересно будет):

Есть у тебя базовый GUI-шный контрол. У него есть наследники - кнопки, лист боксы, эдиты и т.п. Предположим, что базовый контрол реагирует только на событие draw и больше ни на что. Для определения реакции по-умолчанию на другие события (move, size, mouse move, mouse enter/leave, input и т.п.) ты заводишь в базовом классе контейнер слушателей сообщений следующего вида:

class MessageListener
{
public:
MessageListener(){}
virtual ~MessageListener(){}

void processMessage(Message* mess, Widget* receiver=0)=0;
};

//....

class Widget
{
public:
//..
void addMessageListener(MessageListener*);
void removeMessageListener(MessageListener*);

void processMessage(Message* mess);
protected:
typedef std::set<MessageListener*> MessageListeners;
MessageListeners message_listeners;
};

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

Теперь, предположим, чтомы унаследовали от класса Widget класс Button и хотим, чтобы он по-особому обрабатывал сообщения от мыши (как было отмечено выше, Widget реагирует только на draw, а мышь/клавиатуру ниразу не обрабатывает). Что мы для этого должны сделать? Правильно - унаследовать от MessageListener класс ButtonMouseMessageListener, определить в нем реакцию на события мыши и добавить в список слушателей объекта Button (в конструкторе например). Можно также создать базовый класс MouseMessageListener вида:

class MouseMessageListener :
public MessageListener
{
public:
MouseListener(){}
virtual ~MouseListener(){}

virtual void onClick(MouseMessage*, Widget*){}
virtual void onEnter(MouseMessage*, Widget*){}
virtual void onExit(MouseMessage*, Widget*){}
virtual void onMove(MouseMessage*, Widget*){}

virtual void processMessage(Message* message, Widget* receiver)
{
if(...)
{
onClick(mouse_message, receiver);
}

if(...)
{
onEnter(mouse_message, receiver);
}

if(...)
{
onExit(mouse_message, receiver);
}

if(...)
{
onMove(mouse_message, receiver);
}
}

protected:
Point old_position; // предыдущая позиция
};

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

Т.е. паттерн очень полезен при обработке событий или получении уведомлений.

Еще пример - есть у нас render target и как раз перед тем как движок начнет туда рисовать нам нужно произвести над этим таргетом какие-либо действия (заранее не известные и, возможно, непосредственно с таргетом не связанные). Делаем просто - как с классом Widget. Присобачиваем к классу RenderTarget возможность добавлять слушателей и перед тем как движок начинает рендерить вызываем у всех подписаных listeners метод firePreUpdate(). После завершения рендера - firePostUpdate(). При этом получаем возможность "внедряться" в код движка путем добавления своего слушателя.

Прикрепления: 9729188.png (8.6 Kb)


bda-expert.ru — это система форумов, где можно общаться быстро и свободно, где любая точка зрения имеет право на жизнь.
 
eXceedДата: Пятница, 23.05.2008, 14:20 | Сообщение # 23
Генералиссимус
Группа: Гости
Сообщений: 5466
Репутация: 616
Статус: Offline
Продолжаем раскуривать паттерны проектирования. Сегодня рассмотрим паттерн Заместитель(Proxy)

Шаблон Proxy (Заместитель) — Шаблон проектирования. Предоставляет объект, контролирующий доступ, перехватывая все вызовы к нему.

Проблема

Необходимо управлять доступом к объекту, так чтобы создавать громоздкие объекты «по требованию».

Решение

Создать суррогат громоздкого объекта. «Заместитель» хранит ссылку, которая позволяет заместителю обратиться к реальному субъекту (объект класса «Заместитель» может обращаться к объекту класса «Субъект», если интерфейсы «Реального Субъекта» и «Субъекта» одинаковы). Поскольку интерфейс «Реального Субъекта» идентичен интерфейсу «Субъекта», так, что «Заместителя» можно подставить вместо «Реального Субъекта», контролирует доступ к «Реальному Субъекту», может отвечать за создание или удаление «Реального Субъекта». «Субъект» определяет общий для «Реального Субъекта» и «Заместителя» интерфейс, так, что «Заместитель» может быть использован везде, где ожидается «Реальный Субъект». При необходимости запросы могут быть переадресованы «Заместителем» «Реальному Субъекту».

Шаблон proxy бывает нескольких видов, а именно:

* Удаленный: обеспечивает связь с «Субъектом» который находится в другом адресном пространстве или на удалённой машине. Так же может отвечать за кодирование запроса и его аргументов и отправку закодированного запроса реальному «Субъекту»,
* Виртуальный: Обеспечивает создание реального «Субъекта» только тогда, когда он действительно понадобится. Так же может кэшировать часть информации о реальном «Субъекте», чтобы отложить его создание,
* Копировать-при-записи:: Обеспечивает копирование "субъекта" при выполнении клиентом определённых действий. Частный случай "виртуального прокси"
* Защищающий: может проверять, имеет ли вызывающий объект необходимые для выполнения запроса права.

* Кеширующий прокси: Обеспечивает временное хранение результатов рассчёта да отдачи множественным клиентам, которые могут разделить эти результаты.
* Экранирующий прокси: Защищает «Субъект» от опасных клиентов (или наоборот).
* Синхронизирующий прокси: Производит синхронизированный контроль доступа к «Субъекту» в асинхронной многопоточной среде.
* Smart reference proxy: Производит дополнительные действиия когда на «Субъект» создается ссылка, например расчитывает количество активных ссылок на «Субъект».

Применимость

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

Пример реализации

Code

/**
  * "Subject"
  */
class IMath {
public:
     virtual double add(double x, double y) = 0;
     virtual double sub(double x, double y) = 0;
     virtual double mul(double x, double y) = 0;
     virtual double div(double x, double y) = 0;
};
   
/**
  * "Real Subject"
  */
class Math : public IMath {
public:
     double add(double x, double y) {
         return x + y;
     }
   
     double sub(double x, double y) {
         return x - y;
     }
   
     double mul(double x, double y) {
         return x * y;
     }
   
     double div(double x, double y) {
         return x / y;
     }
};
   
/**
  * "Proxy Object"
  */
class MathProxy : public IMath {
public:
     double add(double x, double y) {
         return math.add(x, y);
     }
   
     double sub(double x, double y) {
         return math.sub(x, y);
     }
   
     double mul(double x, double y) {
         return math.mul(x, y);
     }
   
     double div(double x, double y) {
         return math.div(x, y);
     }
   
private:
     Math math;
};
   
#include <iostream>
   
using std::cout;
using std::endl;
   
int main() {
   
     // Create math proxy
     MathProxy p;
   
     // Do the math
     cout << "4 + 2 = " << p.add(4, 2) << endl;
     cout << "4 - 2 = " << p.sub(4, 2) << endl;
     cout << "4 * 2 = " << p.mul(4, 2) << endl;
     cout << "4 / 2 = " << p.div(4, 2) << endl;
   
     return 0;
}
Прикрепления: 9736325.gif (8.4 Kb)


bda-expert.ru — это система форумов, где можно общаться быстро и свободно, где любая точка зрения имеет право на жизнь.
 
eXceedДата: Среда, 04.06.2008, 13:52 | Сообщение # 24
Генералиссимус
Группа: Гости
Сообщений: 5466
Репутация: 616
Статус: Offline
Внимание, сегодня я привожу пример реализации паттерна на C#, т.к. этот язык меня снова заразил своей функциональностью. Но это временное явление. Ну и для неискушенных программированием код C# будет более понятным.

Паттерн Decorator.

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

В общем случае паттерн состоит из 4 частей

1. IWidget - общий интерфейс или класс
2. WidgetA … WidgetZ – классы которые реализуют интерфейс IWidget для каждого конкретного случая
3. Decorator – базовый класс для декораторов который должен содержать объект IWidget. Его целесообразно использовать в том случае если планируется создавать множество декораторов. Определив в нем свойство Child типа IWidget и работу с ним, можно упростить классы производных классов декораторов.
4. DecoratorA … DecoratorB – классы конкретных декораторов, супер классом для которых является Decorator. Каждый класс добавляет только определенную часть функциональности.

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

Code

IWidget widget  = new DecoratorA(new DecoratorB(new Widget()));
Widget.Draw();

В данном случае при вызове метода Draw() сначала вызовется метод Draw() у объекта DecoratorA, далее DecoratorB, а потом у самого виджета. Ничто не мешает прервать цепочку вызовов, то есть один из декораторов может принять решения не вызывать соответствующий метод декорируемого им объекта.

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

Реализация паттерна Decorator. Внимание! Возможны глупые ошибки в коде, т.к. C# для меня не основной язык и я порой путаюсь с синтаксисом.

Code

public interface IWidget   
{
      void Draw();
}

public abstract class Decorator : IWidget   
{
      public Decorator()   
      {
      }

      public Decorator(IWidget child)   
      {
          Child = child;
      }

      private IWidget child;

      protected IWidget Child   
     {
          get { return child; }
          set { child = value; }
      }

      public virtual void Draw()   
      {
          if (Child != null)   
             {
              Child.Draw();
             }
      }
}

public class DecoratorA : Decorator   
{
      public DecoratorA()   
      {
      }

      public DecoratorA(IWidget child) : base(child)   
      {
      }

      public override void Draw()   
      {
          Console.WriteLine("DecoratorA");
          base.Draw();
      }
}

public class DecoratorB : Decorator   
{
      public DecoratorB()   
      {
      }

      public DecoratorB(IWidget child) : base(child)   
      {
      }

      public override void Draw()   
      {
          Console.WriteLine("DecoratorB");
          base.Draw();
      }
}

public class Widget : IWidget   
{
      public Widget()   
      {
      }

      public void Draw()   
      {
          Console.WriteLine("Widget");
      }
}

Самым наглядным примером является реализация работы с потоками

Code

byte[] data = ….;
ByteInputStream byteStream = new ByteInputStream(data);
DataInputStream dataStream = new DataInpuStream(byteStream);

Здесь создается поток байтов и далее он декорируется DataInputStream. Стоит отметить, что и DataInputStream и ByteInputStream наследуются от класса Stream, то есть имеют общий интерфейс. Так же возможны случаи с FileStream, StringWriter и прочими классами.

Еще одним полезным свойствам паттерна является его возможность эмулировать наследование от sealed классов, то есть классов, наследование от которых запрещено.

Code

public interface Stream   
{
    byte[] Read();
}

public sealed class ZipStream : Stream   
{
   public byte[] Read()   
   {     
     …
     return …;
   }
}

public class BufferedStream  : Stream   
{
   public BufferedStream(Stream stream)   
   {
    this.stream = stream;
   }
     
   private Stream stream;
     
   public byte[] Read()   
   {
     byte[] data = stream.Read();
     ….
     return  …;
   }
}

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

В качестве схожих паттернов выделяются Proxy (Заместитель), Адаптер (Adapter) и Компоновщик (Composite). Заместитель аналогично декоратору делегирует вызовы методов, однако сам объект, в частности, может пока не существовать вовсе или находиться на удаленной системе. Адаптер можно рассматривать как частный случай декоратора, при котором интерфейсы декоратора и декорируемого объекта различны. Сам паттерн Декоратор является частным случаем Компоновщика, содержащего один объект, такой вывод можно сделать, сравнив UML схемы (не совсем верно, так как оба паттерна предназначены для разных целей, что говорит о том, что шаблон проектирования нечто большее, чем просто UML схема). Все перечисленные шаблоны является структурными, как и сам Декоратор.

Далее мы рассмотрим подвиды паттерна Decorator.
з.ы. А вот почему снова C#? Виталик помнит мою ненависть к .NET =) Ответ прост C# - это функциональный язык. Очень красивый и мощный. Ну и разработчикам на C# платят довольно хорошо. Поэтому я решил изучить этот язык.

Прикрепления: 5487091.png (22.3 Kb)


bda-expert.ru — это система форумов, где можно общаться быстро и свободно, где любая точка зрения имеет право на жизнь.
 
eXceedДата: Воскресенье, 22.06.2008, 02:33 | Сообщение # 25
Генералиссимус
Группа: Гости
Сообщений: 5466
Репутация: 616
Статус: Offline
Продолжаем раскуривать паттерны. Сегодня это будет усовершенствованный Typed Message. Паттерн похож на Observer(наблюдатель) и выполняют схожие функции. В отличие от Observer, паттерн Typed Message полагается не на ручную диспечеризацию сообщений с помощью dynamic_cast, а на автоматическую проверку типов компилятором. Именно это свойство Typed Message делает этот паттерн лучшей релизацией идеи широковещательных сообщений на подобии Observer. Здесь я привожу пример классической реализации паттерна.

Code
template
<
class MessageType
>
class TypedMessage
{
public:
class Listener
{
public:
Listener()
{
TypedMessage::attach( this );
}virtual int acceptMessage( const MessageType & ) = 0;
};

typedef
std::list
ListenerList;

static void attach( Listener* aListener )
{
registry.push_back(aListener);
}

static void notify( TypedMessage *message )
{
for ( ListenerList::iterator i = registry.begin();
i != registry.end(); i++)
{
(*i)->acceptMessage( * (MessageType *) message );
}
}

void notify()
{
MessageType::notify( this );
}

private:
static ListenerList registry;
};

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

Code
template
<
class MessageType
>
class TypedMessage
{
public:
struct Listener
{
Listener()
{
TypedMessage::attach( this );
}~Listener()
{
TypedMessage::detach( this );
}

virtual int acceptMessage( const MessageType & ) = 0;
};

void notify()
{
MessageType::notify( this );
}

private:
friend
class Listener;

typedef
std::list
ListenerList;

static void attach( Listener *listener )
{
ms_registry.push_back( listener );
}

static void detach( Listener *listener )
{
ms_registry.erase( std::find( ms_registry.begin(),
ms_registry.end(),
listener ) );
}

static void notify( TypedMessage *message )
{
for ( ListenerList::iterator i = ms_registry.begin();
i != ms_registry.end(); i++)
{
(*i)->acceptMessage( * (MessageType *) message );
}
}

private:

static ListenerList ms_registry;
};

Еще один недостаток связан с использованием данного шаблона. Дело в том, что для объявления сообщения недостаточно написать что-то вроде:

Code
struct MyMessage: TypedMessage<MyMessage> {};

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

Code

static ListenerList &getRegistry()
{
static std::auto_ptr holder(new ListenerList);
return * holder.get();
}

Как показала практика использование статического управленца динамической памяти a la std::auto_ptr в большинстве случаев предотвращает крах программы пользователя на этапе разрушения статических объектов. Хотя желательно все таки для этих целей использовать феникс-синглтон из библиотеки Loki.
Получена базовая стабильная реализация шаблона Typed Message. Если применить к ней подход расщепления шаблона на стратегии, получится очень мощная выразительная идиома.


bda-expert.ru — это система форумов, где можно общаться быстро и свободно, где любая точка зрения имеет право на жизнь.
 
eXceedДата: Пятница, 12.09.2008, 20:27 | Сообщение # 26
Генералиссимус
Группа: Гости
Сообщений: 5466
Репутация: 616
Статус: Offline
Паттерн Pimpl - Pointer to Implementation

Вам необходимо воспользоваться внешним API, но при этом вы не хотите позволить платформно-зависимому коду расползаться по проекту и не хотите чтобы у пользователя была возможность им воспользоваться напрямую?

Пример реализации:

Code

// Message.h

#include <string>
#include <memory>

class Message
{
public:

      Message();
      ~Message();

      void ShowMessage(const std::string& message);

private:

      class Impl;
      typedef std::auto_ptr<Impl> ImplPtr;

      ImplPtr m_Impl;
};

Code

// Message.cpp

#include "Message.h"
#include "MessageImpl.inl"

Message::Message()
: m_Impl(ImplPtr(new Impl))
{

}

Message::~Message()
{

}

void Message::ShowMessage(const std::string& message)
{
      m_Impl->ShowMessage(message);
}

Code

// MessageImpl.inl

#include <Windows.h>

class Message::Impl
{
public:

      void ShowMessage(const std::string& message);
};

void Message::Impl::ShowMessage(const std::string& message)
{
      MessageBoxA(0, message.c_str(), "Message", MB_OK);
}

Code
// Main.cpp

#include "Message.h"

int main()
{
Message msgBox;

msgBox.ShowMessage("Hello pimpl pattern!");

return 0;
}

Паттерн позволяет скрыть от пользователя класс с платформно-зависимым кодом. В нашем случае этот класс — Message::Impl. Весь платформно-зависимый код невидим для пользователя. Пользователь класса Message не сможет напрямую вызвать MessageBoxA, так как включение Windows.h происходит внутри .cpp. Паттерн позволяет локализовать весь платформно-зависимый код в одном файле и дает гарантию, что ни ваш пользователь, ни вы сами, не сможете работать напрямую с платформно-зависимым кодом. Таким образом вы получаете полную изоляцию и локализацию платформно-зависимого кода в одном файле. Для того, чтобы выполнить переезд на новую операционную систему, или даже на другой тип платформы, вам достаточно просто переписать этот самый Impl. Весь остальной код платформно-независим.
За Impl-ом обычно скрывают реализацию работы с сокетами, файлами, объектами синхронизации, и прочими общепринятыми системными сущностями.


bda-expert.ru — это система форумов, где можно общаться быстро и свободно, где любая точка зрения имеет право на жизнь.

Сообщение отредактировал eXceed - Пятница, 12.09.2008, 20:28
 
eXceedДата: Пятница, 12.09.2008, 20:30 | Сообщение # 27
Генералиссимус
Группа: Гости
Сообщений: 5466
Репутация: 616
Статус: Offline
Паттерн - Transaction

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

Code
#include <exception>
#include <iostream>
#include <vector>
#include <boost/shared_ptr.hpp>

class Transaction
{
public:

     Transaction()
     : m_Revert(true)
     {
     }

     ~Transaction()
     {
         if(m_Revert)
         {
             Revert();
         }
     }

     template <class T>
     void Begin(T& t)
     {
         m_ParamContainer.push_back(ParamPtr(new TParamImpl<T>(t)));
     };

     void Commit()
     {
         m_Revert = false;
     }

private:

     class Param
     {
     public:

         virtual ~Param() {};
         virtual void Revert() const = 0;
     };

     template <class T>
     class TParamImpl : public Param
     {
     public:

         TParamImpl(T& t)
         : m_Reference(t)
         , m_Original (t)
         {
         }

         virtual void Revert() const
         {
             m_Reference = m_Original;
         }

     private:

         T& m_Reference;
         T  m_Original;
     };

     typedef boost::shared_ptr<Param> ParamPtr;

     typedef std::vector<ParamPtr> ParamContainer;

     bool m_Revert;
     ParamContainer m_ParamContainer;

     void Revert()
     {
         for
         (
             ParamContainer::const_reverse_iterator i = m_ParamContainer.rbegin();
             i != m_ParamContainer.rend();
             ++i
         )
         {
             (*i)->Revert();
         }
     }
};

int main()
{
     std::string name = "Angelina";
     float bosom  = 85.3f;
     float waist  = 68.5f;
     float pelvis = 88.8f;

     try
     {
         Transaction transcation;
         transcation.Begin(name);
         transcation.Begin(bosom);
         transcation.Begin(waist);
         transcation.Begin(pelvis);

         name   = "Olga";
         bosom  = 102.4f;
         waist  = 59.3f;
         pelvis = 92.3f;

         // Дальше что-то происходит
         // ...

         throw std::runtime_error("Wife is coming!");

         transcation.Commit();
     }
     catch(const std::exception&)
     {
         std::cout << "Hi dear!" << std::endl;
     }

     return 0;
}

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

Тема транзакционного поведения будет подробно рассмотрена позже.


bda-expert.ru — это система форумов, где можно общаться быстро и свободно, где любая точка зрения имеет право на жизнь.
 
  • Страница 2 из 2
  • «
  • 1
  • 2
Поиск:

close