Предыдущая Оглавление Следующая

Адаптер.

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

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

Печально, но не смертельно. Нужно CComplex породить в открытую от CGraphic и в закрытую от CAlien, а функции интерфейса CGraphic переопределить так, чтобы они просто вызывали соответствующие функции CAlien. Это будет адаптер класса. Или просто вложить экземпляр (или указатель, или ссылку на) CAlien внутрь класса CComplex , и так же вызывать его функции. Это будет адаптер объекта.

Пример кода:

// Это - чужак
class CAlien {
public:
	void doAlien ();
};
// Это то, к чему нужно привести интерфейс
class CGraphic {
public:
	virtual void doNatural ()=0;
};
// Класс наследует интерфейс от CGraphics
// а реализацию берет из CAlien.
class CComplex : public CGraphic {
private:
	CAlien m_alien;
public:
	void doNatural () { m_alien.doAlien(); };
};

Вот структура паттерна adapter. На диаграмме классов я употребляю более абстрактные имена классов, наподобие клиент или адаптер.


Мост. Bridge.

Немного отвлечемся от нашей игры. Пусть Ваша программа управляет файлами в различных файловой системе, для чего Вы от класса CFile порождаете потомки CFATFile и CNTFSFile. А еще файлы могут быть либо текстовыми, либо двоичными - да еще и в каждой файловой системой. Если просто порождать потомки, то получится уже четыре класса. Кроме того, код будет дублировать друг друга, а подклассы окажутся привязанными к реализации - поскольку никто не гарантирует одинакового поведения для CFATTextFile и CNTFSTextFile .

Выход в разделении файловых систем и вида файла в разные иерархии классов. То есть, пусть от CFile порождены CFATFile и CNTFSFile, но не более. А классы текстовых и двоичных файлов CTextFile и CBinFile порождены от CAbstractFile, который содержит в себе указатель на CFile. Теперь операции текстовых и двоичных файлов вызывают виртуальные функции CFile - а исполняются те, что нужно. Пример кода таков (не проверял)

// Это классы отвечающие за физическую реализацию
class CFile {
public:
	virtual void doIt1 () = 0;
 	virtual void doIt2 () = 0;
};
// В конкретных классах виртуальные функции
// исполняют несколько полезных вещей непосредственно 
// с файловой системой.
class CFATFile : public CFile {
public:
	void doIt1 () { format("c:");         };
	void doIt2 () { loadVirus("OneHalf"); };
};
class CNTFSFile : public CFile {
public:
	void doIt1 () { mustdie("windows");   }; 
	void doIt2 () { catchHim("Bill");     };
};
// А это классы, отвечающие за файлы на более высоком уровне 
class CAbstractFile : {
public:
	virtual void processFile () = 0;
private:
	// Аргегация низкоуровневого файла
	CFile* m_file;
};
class CTextFile : public CAbstractFile {
public:
	// В зависимости от класса m_file исполнятся
	// разные функции
	void processFile () { m_file->doIt1(); };
};
class CBinFile : public CAbstractFile {
public:
	void processFile () { m_file->doIt2(); };
};

Таким образом паттерн Мост (Bridge) разделяет абстракцию и ее реализацию. Вот структура паттерна.

Компоновщик (Composite)

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

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

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

class CPart
{
  void add (CPart* ) = 0;
  void remove (CPart* ) = 0;
  // Псеводокод
  virtual void doIt ( ) = 0;
};

class CCompositePart : public CPart 
{ 
  void add (CPart* _part) {m_list.add(_part); }
  void remove (CPart* _part) {m_list.remove(_part); }
  // Псеводокод 
  virtual void doIt ( ) { // выполнить doIt() для всех объектов в коллекции m_list }; 
  private: 
      CList<CPart*> m_list; 
}; 
      
class CBuilding : public CCompositePart 
{ 
  // ... 
  void doIt(); 
}; 

class CBrick : public CPart 
{ 
  // ...
    void doIt(); 
};

А вот структура.

Декоратор. Decorator.

Снова к нашей игре.

Пусть юнитам (CUnit) в нашей нужно бывает окружать себя простой рамкой. Хорошим решением будет создать новый подкласс - который будет окружать юнит рамкой (CBorderedUnit). А иногда нужна прокрутка, например если строение слишком большое, чтобы его отражать полностью. Тогда создаем подкласс - который реализует прокрутку (CScrollUnit). Ну, конечно, если понадобится прокрутка и простая рамка, то породим класс (CBorderedScrollUnit) - множественным наследованием. Или один от другого. Только это не решит проблемы, поскольку вот еще видел я в Visual... такую рамочку вокруг компонента, которая такая из восьми черных квадратиков, она еще позволяет изменять размер и положение (ах, Малевич, не поняли его в России, а какое влияние на мир оказал). Эта рамка нужна больше жизни. И теперь мы получаем еще пачку классов CDesing, CDesignBordered, CDesignBordered - и так далее.

В общем, решение неудачное.

А вот решение получше: Пусть всякие рамки компонетам добавляет другой компонент-контейнер, который все запросы к себе исправно перенаправляет лежащему в нем объекту, а от себя добавляет только минимальное дополнительное поведение. При этом контейнер видит базовый класс компонента, и если мы положим в контейнер потомок базового класса или другой похожий контейнер, то он не заметит подмены. Это как в дельфях Вы выкладываете на форму панель, на панель скроллер, на скроллер картинку. О такой феньке я рассказывал в Шаге 7 "Стилей и идиом", там это называлось интерфейсным указателем, а здесь - декоратором; кроме того, требуется порождать компонент и его декоратор от одного базового класса, чтобы уже ИХ контейнер так же не замечал подмены.

// Общий предок с функцией отрисовки.
class CUnit
{
public:
	virtual void draw ();
};
// Какой-то конкретный юнит со своей отрисовкой.
class CSomeUnit : public CUnit
{
public:
	void draw ();
};
// базовый декоратор, перенаправляет вызовы
class CDecorator : public CUnit
{
public:
	CUnit* m_unit;
	// Все декораторы перенаправляют вызовы как минимум
	void draw () {m_unit->draw(); };
};
//Конкретный декоратор, добавляет собственное поведение
class CBorderDecorator : public CDecorator
{
	// вот оно, собственное поведение
	void drawBorder ();
public:
	void draw ()
	{
		// сначала вызывается общее поведение 
		CDecorator::draw();
		// а теперь частное.
		drawBorder ();
	};
};


Предыдущая Оглавление Следующая