Design Patterns

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск

Содержание

Порождающие паттерны проектирования

Абстрактная фабрика (Abstract Factory, Factory), др. название Инструментарий (Kit)

#include <iostream>
 
class CLanguage
{
public:
    virtual void generate() = 0;
};
 
class CPytnon : public CLanguage
{
public:
    void generate() { std::cout << "Generate python code" << std::endl; }
};
 
class CJava : public CLanguage
{
public:
    void generate() { std::cout << "Generate java code" << std::endl; }
};
 
class CLangFactory
{
public:
    virtual CLanguage* createLanguage() = 0;
};
 
class CPythonFactory : public CLangFactory
{
public:
    CLanguage* createLanguage(){ return new CPytnon(); }
};
 
class CJavaFactory : public CLangFactory
{
public:
    CLanguage* createLanguage() { return new CJava(); }
};
 
class CCodeGenerator
{
public:
    CCodeGenerator(CLangFactory* factory)
    {
        CLanguage *pLang = factory->createLanguage();
        pLang->generate();
        delete pLang;
        delete factory;
    }
};
 
CLangFactory* createCodeFactory()
{
    int nLang = -1;
    std::cout << "Enter Language type (0: Python, 1: Java): ";
    std::cin >> nLang;
 
    switch( nLang )
    {
    case 0: return new CPythonFactory();
    case 1: return new CJavaFactory();
    default: std::cout << "Error choice language..." << std::endl; return 0;
    }
}
 
int main()
{
    CLangFactory *plf = createCodeFactory();
    if( plf )
        CCodeGenerator cg( plf );
 
    return 0;
}

Одиночка (Singleton)

Статический

class CSingleton
{
private:
    static CSingleton m_singleton;
 
private:
    CSingleton() {}
    ~CSingleton() {}
    CSingleton(const CSingleton &) {}
    CSingleton & operator=(const CSingleton &) { return *this; }
 
public:
    static CSingleton *instance() { return &m_singleton; }
};
 
CSingleton CSingleton::m_singleton;
 
int main(int , char **)
{
    CSingleton *p = CSingleton::instance();
    // ...
    return 0;
}

Динамический

class CSingleton
{
private:
    static CSingleton *m_pSingleton;
    static int m_nCounter;
 
private:
    CSingleton() {}
    ~CSingleton() {}
    CSingleton(const CSingleton &) {}
    CSingleton & operator=(const CSingleton &) { return *this; }
 
public:
    static CSingleton *instance()
    {
        if( m_nCounter == 0 )
        {
            m_pSingleton = new CSingleton();
        }
        m_nCounter++;
        return m_pSingleton;
    }
    static CSingleton *freeInstance()
    {
        if( m_nCounter > 0 )
        {
            m_nCounter--;
            if( m_nCounter == 0 )
            {
                delete m_pSingleton;
                m_pSingleton = 0;
            }
        }
    }
};
 
CSingleton *CSingleton::m_pSingleton = 0;
int CSingleton::m_nCounter = 0;
 
int main(int , char **)
{
    CSingleton *p = CSingleton::instance();
 
    return 0;
}

Шаблонный

class CClass
{
public:
    virtual ~CClass(){  }
};
 
template <class T>
class CTypedSingleton;
 
template<class T>
class CTypedWrapper : public T, private CTypedSingleton<T>
{
public:
    void operator delete(void *p)
    {
        CTypedSingleton<T>::free();
    }
};
 
template <class T>
class CTypedSingleton
{
    static T* m_self;
    static int m_refcount;
 
protected:
    CTypedSingleton(){}
    CTypedSingleton(const CTypedSingleton&){}
    virtual ~CTypedSingleton(){ m_self = 0; }
    CTypedSingleton &operator=(const CTypedSingleton&){}
 
public:
  static T *init()
  {
      if(!m_self)
          m_self = new CTypedWrapper<T>;
      m_refcount++;
      return m_self;
  }
 
  static void free()
  {
      if( m_refcount > 0)
      {
          --m_refcount;
          if( m_refcount == 0)
          {
              delete m_self;
              m_self = 0;
          }
      }
  }
};
 
template <class T>
T *CTypedSingleton<T>::m_self = 0;
 
template <class T>
int CTypedSingleton<T>::m_refcount = 0;
 
int main(int , char **)
{
    CClass *p = CTypedSingleton<CClass>::init();
    delete p;
 
    return 0;
}

Многопоточный (double-checked locking)

#include <cstdlib>
#include <boost/thread/once.hpp>
 
template <class T>
class Singleton
{
public:
    static T& Instance();
 
private:
    Singleton() {};
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    virtual ~Singleton() {};
 
    static void Init() { boost::call_once(m_init, &Singleton<T>::InitImpl); }
    static void InitImpl() { m_me = new T; atexit(&Singleton<T>::FreeInstance); }
    static void FreeInstance() { boost::call_once(m_done, &Singleton<T>::FreeImpl); };
    static void FreeImpl() { delete m_me; m_me = NULL; }
    friend int atexit(void (__cdecl *func)(void));
 
private:
    static T* volatile m_me;
    static boost::once_flag m_init, m_done;
};
 
template <class T>
T* Singleton<T>::m_me = NULL;
 
template <class T>
boost::once_flag Singleton<T>::m_init = BOOST_ONCE_INIT;
 
template <class T>
boost::once_flag Singleton<T>::m_done = BOOST_ONCE_INIT;
 
template <class T>
T& Singleton<T>::Instance()
{
      Init();
      return (*m_me);
}

Прототип (Prototype)

#include <iostream>
 
// Прототип
class CPrototype
{
public:
    virtual CPrototype* clone() const = 0;
};
 
// Прототип сайта
class CSitePrototype : public CPrototype
{
private:
    int m_nPages;
 
public:
    CSitePrototype(int nPages) : m_nPages( nPages ){}
    CSitePrototype(const CSitePrototype &r) : m_nPages( r.m_nPages ){}
    virtual CSitePrototype* clone() const { return new CSitePrototype( *this ); }
 
    void setPages(int nPages) { m_nPages = nPages; }
    int getPages() const { return m_nPages; }
    void printPages() const { std::cout << "Pages: " << m_nPages << std::endl; }
};
 
// Клиентская сторона
void clientSide()
{
    CPrototype *pPrototype = new CSitePrototype( 256 );
    for (int n = 0; n < 10; n++)
    {
        CSitePrototype *pSite = static_cast<CSitePrototype*>( pPrototype->clone() );
        pSite->setPages( pSite->getPages() * n );
        pSite->printPages();
        delete pSite;
    }
    delete pPrototype;
    pPrototype = 0;
}
 
int main()
{
    clientSide();
    return 0;
}

Создатель экземпляров класса (Creator)

template <typename TItem>
class CDataStorage 
{
private:
    std::vector<TItem*> m_items;
 
public:
    CDataStorage () {};
    ~CDataStorage () 
    {
        for (auto it = m_items.begin (); it != m_items.end(); ++it)
	    delete *it;
    }
 
    std::size_t createItem () 
    {
        std::size_t item_index = m_items.size ();
        m_items.push_back (new TItem());
        return item_index;
    }
 
    std::size_t size () const { return m_items.size ();}
    TItem & itemAt (std::size_t index) { return *m_items[index];}
    const TItem & itemAt (std::size_t index) const { return *m_items[index];}
};
 
int main(int argc, char* argv[])
{
    CDataStorage<int> data;
    data.createItem ();
    data.createItem ();
    int &item1 = data.itemAt (0);
    item1 = 42;
    std::cout << data.itemAt (0);
}

Строитель (Builder)

#include <string>
#include <iostream>
 
class COs
{
private:
    int m_nType;
    int m_nCore;
    std::string m_szName;
 
public:
    void setType( int nType ) { m_nType = nType; }
    void setCore( int nCore ) { m_nCore = nCore; }
    void setName( std::string szName ) { m_szName = szName; }
    void print() { std::cout << "Os type: " << m_nType << " core: " << m_nCore << " name: " << m_szName << std::endl; }
};
 
class COsBuilder
{
protected:
    COs *m_pOs;
 
public:
    void createNewOs() { m_pOs = new COs(); }
    COs *os() { return m_pOs; }
 
    virtual void setOsType() = 0;
    virtual void setOsCore() = 0;
    virtual void setOsName() = 0;
};
 
class CLinuxBuilder : public COsBuilder {
public:
    void setOsType() { m_pOs->setType( 0 ); }
    void setOsCore() { m_pOs->setCore( 11111 ); }
    void setOsName() { m_pOs->setName( "Red hat" ); }
};
 
class CWindowsBuilder : public COsBuilder
{
public:
    void setOsType() { m_pOs->setType( 1 ); }
    void setOsCore() { m_pOs->setCore( 22222 ); }
    void setOsName() { m_pOs->setName( "Windows 8" ); }
};
 
class CSysAdmin
{
private:
    COsBuilder *m_pBuilder;
 
public:
    CSysAdmin(): m_pBuilder(0){}
    virtual ~CSysAdmin() { freeBuilder(); }
 
    void freeBuilder()
    {
        if( m_pBuilder )
        {
            delete m_pBuilder;
            m_pBuilder = 0;
        }
    }
    void setBuilder(COsBuilder* pBuilder)
    {
        freeBuilder();
        m_pBuilder = pBuilder;
    }
    void construct()
    {
        m_pBuilder->createNewOs();
        m_pBuilder->setOsType();
        m_pBuilder->setOsCore();
        m_pBuilder->setOsName();
    }
 
    void printOs(){ m_pBuilder->os()->print(); }
};
 
int main()
{
    CSysAdmin sys;
 
    sys.setBuilder(new CLinuxBuilder);
    sys.construct();
    sys.printOs();
 
    sys.setBuilder(new CWindowsBuilder);
    sys.construct();
    sys.printOs();
}

Фабричный метод (Factory Method) или Виртуальный конструктор (Virtual Constructor)

#include <iostream>
 
class CDocument
{
public:
    virtual void save() = 0;
};
 
class CWord: public CDocument
{
public:
    void save() { std::cout << "Save word document." << std::endl; }
};
 
class CExcel: public CDocument
{
public:
    void save() { std::cout << "Save excel document." << std::endl; }
};
 
class CApplication
{
    int m_nTypeDocument;
public:
    CApplication( int nTypeDocument ):m_nTypeDocument(nTypeDocument){}
    void setTypeDocument(int nTypeDocument ){ m_nTypeDocument = nTypeDocument; }
 
    void saveFile()
    {
        CDocument *pDoc = getTypeDocument();
        if( pDoc )
            pDoc->save();
    }
 
    CDocument *getTypeDocument()
    {
        switch( m_nTypeDocument )
        {
        case 0: return new CWord();
        case 1: return new CExcel();
        }
        return 0;
    }
};
 
int main()
{
    CApplication app( 0 );
    app.saveFile(); // Word
    app.setTypeDocument( 1 );
    app.saveFile(); // Excel
 
    return 0;
}

Структурные паттерны проектирования классов/обьектов

Адаптер (Adapter)

class CDisplay {
public:
    void drawMessage (int x, int y, const std::string &message) 
    { 
        std::cout << "Display at " << x << " " << y << ": " << message << std::endl;
    }
    int width () { return 200; }
    int height () { return 100; }
};
 
class CDisplayAdapter {
private:
    CDisplay *m_pDisplay;
public:
    CDisplayAdapter (CDisplay *display) { m_pDisplay = display;}
    void message (float x, float y, const std::string &message) 
    {
        int display_x = x * m_pDisplay->width ();
        int display_y = y * m_pDisplay->height ();
        m_pDisplay->drawMessage (display_x, display_y, message);
    }
};
 
int main(int argc, char* argv[])
{
    CDisplay *output = new CDisplay ();
    CDisplayAdapter display_adapter (output);
 
    display_adapter.message (0.5f, 0.5f, "I'm center!");
}

Декоратор (Decorator) или Оболочка (Wrapper)

Предназначен для динамического расширения функциональности объекта (добавления дополнительного поведения).

#include <iostream>
 
class CWidget
{
public:
    virtual void draw() = 0;
};
 
class CDialog : public CWidget
{
public:
    void draw(){ std::cout << "Draw CDialog" << std::endl; }
};
 
class CToolBar : public CWidget
{
    CWidget *m_pWrap;
public:
    CToolBar( CWidget *pWrap ): m_pWrap(pWrap){}
    void draw(){ std::cout << "Draw CToolBar" << std::endl; m_pWrap->draw(); }
};
 
class CStatusBar : public CWidget
{
    CWidget *m_pWrap;
public:
    CStatusBar( CWidget *pWrap ): m_pWrap(pWrap){}
    void draw(){ m_pWrap->draw(); std::cout << "Draw CStatusBar" << std::endl; }
};
 
int main( int argc, char **argv)
{
    CWidget *pDlg = new CDialog(); // Диалог без тулбара и статусбара
    pDlg->draw();
 
    std::cout << std::endl;
 
    CWidget *pDlgT = new CToolBar( new CDialog() ); // Диалог c тулбаром и без статусбара
    pDlgT->draw();
 
    std::cout << std::endl;
 
    CWidget *pDlgTS = new CToolBar( new CStatusBar( new CDialog() ) ); // Диалог c тулбаром и c статусбаром
    pDlgTS->draw();
 
    return 0;
}

где, CToolBar и CStatusBar являются декораторами.

Результат:

Draw CDialog
 
Draw CToolBar
Draw CDialog
 
Draw CToolBar
Draw CDialog
Draw CStatusBar

Заместитель (Proxy) или Суррогат (Surrogate)

Контролирует доступ к другому объекту, перехватывая все вызовы.

#include <iostream>
#include <string>
 
class CImage
{
    std::string m_szName;
public:
    CImage(std::string szName):m_szName(szName){}
    void draw(){ std::cout << "Drawing image: " << m_szName << std::endl; }
    void printName()const { std::cout << "CImage::printName" << std::endl; }
};
 
class CProxyImage
{
    CImage *m_pImage;
    std::string m_szName;
public:
    CProxyImage( std::string szName): m_pImage(0), m_szName(szName){}
    void draw()
    {
        if( !m_pImage )
            m_pImage = new CImage(m_szName);
        m_pImage->draw();
    }
    void printName()const
    {
        if( m_pImage )
        {
            m_pImage->printName();
            return;
        }
        std::cout << "CProxyImage::printName" << std::endl;
    }
};
 
int main( int argc, char **argv)
{
    CProxyImage image( "testImage.png" );
    image.printName();
    image.draw();
    image.printName();
 
    return 0;
}
В роли прокси тут выступает CProxyImage, который пока не вызван метод рисования картинки обрабатывает все запросы.

Результат:

CProxyImage::printName
Drawing image: testImage.png
CImage::printName

Информационный эксперт (Information Expert)

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

class CDrawable {
public:
    CDrawable () {};
    virtual ~CDrawable () {};
 
    virtual void draw () = 0;
};
 
class CCircle : public CDrawable {
public:
    virtual void draw () { std::cout << "I'm a circle!" << std::endl;}
};
 
class CRectangle : public CDrawable {
public:
    virtual void draw () { std::cout << "I'm a rectangle!" << std::endl;}
};
 
class CDrawableGroup : public CDrawable {
private:
    std::list<CDrawable*> m_children;
public:
    CDrawableGroup () {};
    virtual ~CDrawableGroup () 
    {
        while (!m_children.empty ()) {
            delete m_children.back ();
            m_children.pop_back ();
        };
    };
 
    void addChild (CDrawable *drawable) { m_children.push_back (drawable); };
    void removeChild (CDrawable *drawable) 
    { 
        m_children.remove (drawable);
        delete drawable;		
    };
 
    virtual void draw () {
        for (auto it = m_children.begin (); it != m_children.end (); ++it)
	    (*it)->draw ();
    };
};
 
int main(int argc, char* argv[])
{
    CDrawableGroup figures;
    figures.addChild (new CCircle ());
 
    CDrawableGroup *rect_group = new CDrawableGroup ();
    rect_group->addChild (new CRectangle ());
    rect_group->addChild (new CRectangle ());
    figures.addChild (rect_group);
    figures.draw ();
}

Мост (Bridge), Handle (описатель) или Тело (Body)

#include <iostream>
 
class CWindowImp
{
public:
    virtual void drawWindow() = 0;
};
 
class CLinuxWindow : public CWindowImp
{
    void drawWindow() { std::cout << "Draw linux window" << std::endl; }
};
 
class CWinWindow : public CWindowImp
{
    void drawWindow() { std::cout << "Draw Windows window" << std::endl; }
};
 
class CMacWindow : public CWindowImp
{
    void drawWindow() { std::cout << "Draw Mac window" << std::endl; }
};
 
class CWindowFactory
{
public:
    enum TYPES
    {
        LINUX = 0,
        WINDOWS,
        MAC
    };
 
    // будем считать, что у нас фабрика синглтон и все нормально с освобождением памяти... :)
    static CWindowImp *getWindowImp( CWindowFactory::TYPES type )
    {
        switch( type )
        {
        case LINUX: return new CLinuxWindow();
            break;
        case WINDOWS: return new CWinWindow();
            break;
        case MAC: return new CMacWindow();
            break;
        }
        return 0;
    }
 
    static CWindowImp *makeWindow()
    {
        // Будем считать, что мы узнали какая у нас ОС
        return getWindowImp(CWindowFactory::LINUX);
    }
};
 
class CWindow
{
public:
    void draw(){ getWindowImp()->drawWindow(); }
    CWindowImp *getWindowImp(){ return CWindowFactory::makeWindow(); }
};
 
int main()
{
    CWindow win;
    win.draw();
}

Низкая связанность (Low Coupling)

Приспособленец (Flyweight)

Устойчивый к изменениям (Protected Variations)

Фасад (Facade)

Паттерны проектирования поведения классов/обьектов

Интерпретатор (Interpreter )

Итератор (Iterator) или Курсор (Cursor)

Команда (Command), Действие (Action) или Транзакция (Транзакция)

Наблюдатель (Observer), Опубликовать - подписаться (Publish - Subscribe) или Delegation Event Model

#include <iostream>
#include <vector>
#include <algorithm>
 
class CObserver;
 
class CModel
{
	std::vector<CObserver*> m_views;
	int m_nValue;
public:
	void attach(CObserver *po) { m_views.push_back( po ); }
	void detach(CObserver *po) 
	{ 
		std::vector<CObserver*>::iterator iter = std::find( m_views.begin(), m_views.end(), po); 
		if( iter != m_views.end() )
		{
			m_views.erase( iter );
		}
	}
	void setValue(int val) 
	{ 
		m_nValue = val; 
		notify(); 
	}
	int getValue() { return m_nValue; }
	void notify();
};
 
class CObserver 
{
    CModel *m_pModel;
public:
    CObserver(CModel *pMod) 
    {
        m_pModel = pMod;
        m_pModel->attach(this);
    }
 
    virtual ~CObserver()
    {
        m_pModel.detach(this);
    }
 
    virtual void update() = 0;
protected:
    CModel *getModel() { return m_pModel; }
};
 
class CViewDiagram: public CObserver 
{
public:
    CViewDiagram(CModel *pMod): CObserver(pMod){}
    void update() { std::cout << "CViewDiagram: " << getModel()->getValue() << std::endl; }
};
 
class CViewDocument: public CObserver 
{
public:
    CViewDocument(CModel *pMod): CObserver(pMod){}
    void update() { std::cout << "CViewDocument: " << getModel()->getValue() << std::endl; }
};
 
void CModel::notify()
{
	for (size_t i = 0; i < m_views.size(); i++)
		m_views[i]->update();
}
 
int main() 
{
	CModel model;
	CViewDocument doc( &model );
	CViewDiagram diagr( &model );
 
	for( int n= 0; n < 10; ++n)
	{
		model.setValue( n );
		if( n ==5 )
			model.detach( &diagr );
	}
}
В роли CObserver в данном примере выступают классы виды: CViewDiagram с CViewDocument. Они получают уведомления об изменении в модели.

Вывод:

CViewDocument: 0
CViewDiagram: 0
CViewDocument: 1
CViewDiagram: 1
CViewDocument: 2
CViewDiagram: 2
CViewDocument: 3
CViewDiagram: 3
CViewDocument: 4
CViewDiagram: 4
CViewDocument: 5
CViewDiagram: 5
CViewDocument: 6
CViewDocument: 7
CViewDocument: 8
CViewDocument: 9

Не разговаривайте с неизвестными (Don't talk to strangers)

Посетитель (Visitor)

Посредник (Mediator)

Состояние (State)

#include <iostream>
 
class CSocket;
class CStateOpen;
class CStateRead;
class CStateClose;
 
class CState
{
public:
	virtual ~CState() {}
 
	virtual void open(CSocket *s) { std::cout << "Already open socket" << std::endl; }
	virtual void read(CSocket *s) { std::cout << "Already read socket" << std::endl; }
	virtual void close(CSocket *s) { std::cout << "Already close socket" << std::endl; }
};
 
class CSocket
{
	CState *m_pCurrentState;
public:
	CSocket();
	void setCurrent(CState *p){ m_pCurrentState = p; }
	void open() { m_pCurrentState->open(this); } 
	void read() { m_pCurrentState->read(this); } 
	void close() { m_pCurrentState->close(this); }
};
 
class CStateOpen: public CState
{
public:
	CStateOpen(){ std::cout << "	CStateOpen" << std::endl; }
	~CStateOpen(){ std::cout << "	~CStateOpen" << std::endl; }
	virtual void read(CSocket *s);
	virtual void close(CSocket *s);
};
 
class CStateRead: public CState
{
public:
	CStateRead(){ std::cout << "	CStateRead" << std::endl; }
	~CStateRead(){ std::cout << "	~CStateRead" << std::endl; }
	virtual void open(CSocket *s) { std::cout << "	Socket already open..." << std::endl; }
	virtual void close(CSocket *s);
};
 
class CStateClose: public CState
{
public:
	CStateClose(){ std::cout << "	CStateClose" << std::endl; }
	~CStateClose(){ std::cout << "	~CStateClose" << std::endl; }
	virtual void open(CSocket *s) 
	{ 
		std::cout << "	Open closing socket" << std::endl; 
		s->setCurrent( new CStateOpen()); 
	}
	virtual void read(CSocket *s) { std::cout << "	Error: Don't read closing socket" << std::endl; }
};
 
CSocket::CSocket()
{ 
	m_pCurrentState = new CStateClose(); 
}
 
void CStateOpen::read(CSocket *s) 
{ 
	std::cout << "	Read socket" << std::endl; 
	s->setCurrent( new CStateRead()); 
}
void CStateOpen::close(CSocket *s) 
{ 
	std::cout << "	Close socket after opening" << std::endl; 
	s->setCurrent( new CStateClose()); 
}
 
void CStateRead::close(CSocket *s)
{ 
	std::cout << "	Close socket after reading" << std::endl; 
	s->setCurrent( new CStateClose()); 
}
 
int main()
{
	void(CSocket::*ptrs[])() = { &CSocket::open, &CSocket::read, &CSocket::close };
	CSocket sock;
	int num;
	while( 1 )
	{
		std::cout << "Enter 0/2: ";
		std::cin >> num;
		(sock.*ptrs[num])();
	}
}
В данном случае классами состояния являются: CStateOpen, CStateRead и CStateClose. Которые в случае того или иного действия с сокетом меняются...
        CStateClose
Enter 0/2: 0
        Open closing socket
        CStateOpen
Enter 0/2: 1
        Read socket
        CStateRead
Enter 0/2: 2
        Close socket after reading
        CStateClose
Enter 0/2: 0
        Open closing socket
        CStateOpen
Enter 0/2: 2
        Close socket after opening
        CStateClose
Enter 0/2: 1
        Error: Don't read closing socket

Стратегия (Strategy)

Хранитель (Memento)

Цепочка обязанностей (Chain of Responsibility)

Шаблонный метод (Template Method)

#include <iostream>
 
class CApp
{
public:
	void openDocument() 
	{ 
		//...
		doOpenDocument(); 
		//...
	}
 
protected:
	virtual void doOpenDocument() { std::cout << "Open simple document" << std::endl; }
};
 
class CXmlApp: public CApp
{
protected:
	void doOpenDocument() { std::cout << "Open XML document" << std::endl; }
};
 
class CTxtApp: public CApp
{
protected:
	void doOpenDocument() { std::cout << "Open TXT document" << std::endl; }
};
 
int main()
{
	CApp *pApps[] = { &CApp(), &CXmlApp(), &CTxtApp() };
	for( int n = 0; n < 3; ++n )
		pApps[n]->openDocument();
	return 0;
}
В данном примере метод doOpenDocument является шаблонным методом.

Вывод:

Open simple document
Open XML document
Open TXT document

Высокое зацепление (High Cohesion)

Контроллер (Controller)

Полиморфизм (Polymorphism)

Искусственный (Pure Fabrication)

Перенаправление (Indirection)