面向对象设计模式 (面向对象的设计模式)

代理模式(Proxy pattern)就是给某一个对象提供一个代理,并由代理对象控制对原对象的引用。在一些情况下,一个客户不想或者不能直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。例如电脑桌面的快捷方式就是一个代理对象,快捷方式是它所引用的程序的一个代理。

代理模式根据其目的和实现方式不同可分为很多种类,其中常用的几种代理模式简要说明如下:

远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。

虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。

保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。

设计模式之代理对象,面向对象的23种设计模式有哪些

Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。

Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。

RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。

#include <iostream>
using namespace std;

#define SAFE_DELETE(p) if (p) { delete p; p = NULL;}

class CSubject
{
public:
    CSubject(){};
    virtual ~CSubject(){}

    virtual void Request() = 0;
};

class CRealSubject : public CSubject
{
public:
    CRealSubject(){}
    ~CRealSubject(){}

    void Request()
    {
        cout<<"CRealSubject Request"<<endl;
    }
};

class CProxy : public CSubject
{
public:
    CProxy() : m_pRealSubject(NULL){}
    ~CProxy()
    {
        SAFE_DELETE(m_pRealSubject);
    }

    void Request()
    {
        if (NULL == m_pRealSubject)
        {
            m_pRealSubject = new CRealSubject();
        }
        cout<<"CProxy Request"<<endl;
        m_pRealSubject->Request();
    }

private:
    CRealSubject *m_pRealSubject;
};

int main()
{
    CSubject *pSubject = new CProxy();
    pSubject->Request();
    SAFE_DELETE(pSubject);
}

Output:

CProxy Request            
CRealSubject Request   

智能指针使用引用计数实现时,就是最好的使用代理模式的例子。在下面的例子中,SmartPtr就是一个代理类,而T*m_pData才是实际的数据。SmartPtr代理实际的数据,去实现了指针的行为,添加了引用计数,从而实现了智能指针。

#include <iostream>
#include <windows.h>	// InterlockedIncrement
using namespace std;

#define SAFE_DELETE(p) if (p) { delete p; p = NULL; }

class KRefCount			// 封装了一个整数及其增1和减1操作的计数器对象
{
public:
    KRefCount():m_nCount(0){}

public:
    unsigned AddRef()	// 保证在一个线程访问变量时其它线程不能访问
	{ 
		return InterlockedIncrement(&m_nCount); 
	}
    unsigned Release()
	{ 
		return InterlockedDecrement(&m_nCount); 
	}
    void Reset()
	{ 
		m_nCount = 0; 
	}

private:
    long m_nCount;
};

template <typename T>	// 模板
class SmartPtr			// 封装了一个指针及计数器对象
{
public:
    SmartPtr(void)
        : m_pData(NULL)
    {
        m_pReference = new KRefCount();
        m_pReference->AddRef();
		cout<<"智能指针空构造!"<<endl; // for test
    }

    SmartPtr(T* pValue)
        : m_pData(pValue)
    {
        m_pReference = new KRefCount();
        m_pReference->AddRef();
		cout<<"智能指针参数构造!"<<endl; // for test
    }

    SmartPtr(const SmartPtr<T>& sp)
        : m_pData(sp.m_pData)
        , m_pReference(sp.m_pReference)
    {
        m_pReference->AddRef();
		cout<<"智能指针复制构造!"<<endl; // for test
    }

    ~SmartPtr(void)
    {
        if (m_pReference && m_pReference->Release() == 0)
        {
            SAFE_DELETE(m_pData);
            SAFE_DELETE(m_pReference);
			cout<<"智能指针析造!"<<endl; // for test
        }
    }

	// 操作符重载
    inline T& operator*()
    {
        return *m_pData;
    }

    inline T* operator->()
    {
        return m_pData;
    }

    SmartPtr<T>& operator=(const SmartPtr<T>& sp)
    {
        if (this != &sp)
        {
            if (m_pReference && m_pReference->Release() == 0)
            {
                SAFE_DELETE(m_pData);
                SAFE_DELETE(m_pReference);
            }

            m_pData = sp.m_pData;
            m_pReference = sp.m_pReference;
            m_pReference->AddRef();
        }

        return *this;
    }

    SmartPtr<T>& operator=(T* pValue)
    {
        if (m_pReference && m_pReference->Release() == 0)
        {
            SAFE_DELETE(m_pData);
            SAFE_DELETE(m_pReference);
        }

        m_pData = pValue;
        m_pReference = new KRefCount;
        m_pReference->AddRef();
        return *this;
    }

    T* Get()				// 返回智能指针对象的裸指针
    {
        T* ptr = NULL;        
        ptr = m_pData;
        return ptr;
    }

    void Attach(T* pObject)// 将裸指针绑定到智能指针对象
    {
        if (m_pReference->Release() == 0)
        {
            SAFE_DELETE(m_pData);
            SAFE_DELETE(m_pReference);
        }

        m_pData = pObject;
        m_pReference = new KRefCount;
        m_pReference->AddRef();
    }

    T* Detach()// 返回智能指针对象的裸指针交将计数器置0
    {
        T* ptr = NULL;

        if (m_pData)
        {           
            ptr = m_pData;
            m_pData = NULL;
            m_pReference->Reset();
        }
        return ptr;
    }

private:
    KRefCount* m_pReference;	// 计数器对象
            T* m_pData;			// 裸指针
};

class CTest						// for test
{
public:
    CTest(int b) : a(b) {}
    int a;
};

int main()
{
	{							// local scope for test
		SmartPtr<CTest> pSmartPtr1(new CTest(10));
		SmartPtr<CTest> pSmartPtr2(new CTest(20));

		pSmartPtr1 = pSmartPtr2;
		cout<<(*pSmartPtr1).a+(*pSmartPtr2).a<<endl; // 40
	}
	system("pause");
}

/*
Output:
智能指针参数构造!
智能指针参数构造!
40
智能指针析造!
*/

1 代理模式的应用场景

代理模式的类型较多,不同类型的代理模式有不同的优缺点,它们应用于不同的场合:

1.1 当客户端对象需要访问远程主机中的对象时可以使用远程代理。

1.2 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。

1.3 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。

1.4 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。

1.5 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理。

2 代理模式和装饰模式的异同

代理模式和装饰模式的代码实现方式很相同,主要不同点是代理模式关注与被代理对象行为的控制,然而装饰模式关注于在一个对象上动态的添加方法。

代理模式可以对客户端隐藏被代理对象的具体实现,代理模式的时候常常是在一个代理类中创建一个对象的实例,当使用装饰模式的时候,将原始对象转为一个参数传递给装饰者的构造器中。

代理模式强调的是限制,装饰模式强调的是增强

3 代理模式的优缺点

2.1 优点

3.1.1 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。

3.1.2 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。

3.2 缺点

3.2.1 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。

3.2.2 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。

4 代理模式和委托

代理:是把一些事情交给某人帮忙去完成。

委托:是当某件事情发生的时候,顺便干某件事情。委托就相当于一个触发器罢了。

5 总结

代理模式和适配器模式的类适配器很像,再一看,又和装饰模式非常像;不仔细区分,真的是很容易混乱的。下面就做简单的区分,说多了也都是“狡辩”了。

适配器Adapter为它所适配的对象提供了一个不同的接口。相反,代理提供了与它的实体相同的接口。然而,用于访问保护的代理可能会拒绝执行实体会执行的操作,因此,它的接口实际上可能只是实体接口的一个子集。

尽管Decorator的实现部分与代理相似,但Decorator的目的不一样。Decorator为对象添加一个或多个功能,而代理则控制对对象的访问。

结构型设计模式之间都有一些细微的差别。你也可以说,在适配器模式进行接口适配时,添加一些数据转换就变成了远程代理;你也可以说装饰模式虽然功能不一样,但也是大同小异;是的,不管你怎么说,就像1000 个读者心中有1000 个哈姆雷特一样,每个人对设计模式的理解都是不一样的;最重要的是我们能在实际应用中进行活学活用,如果能做到这个;不管什么设计模式,那只是一个名字,就像对于富人来说,钱只是一个银行卡上的一个数字一样。

-End-