结构型模式—代理模式

Posted 浅墨浓香

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了结构型模式—代理模式相关的知识,希望对你有一定的参考价值。

1. 代理模式(Proxy Pattern)的定义

(1)为其他对象提供一种代理以控制对这个对象的访问

  ①代理模式在客户和被客户访问的对象之间,引入了一定程度的间接性,客户是直接使用代理,让代理来与被访问的对象进行交互。

  ②这种附加的间接性增加了灵活性和不同的用途

(2)代理模式的结构和说明

 

  ①Proxy:代理对象,通常实现与具体的目标对象一样的接口,这样就可以使用代理来代替具体的目标对象。

  A、保存一个指向具体目标对象的指针,可以在需要的时候调用具体的目标对象,若RealSubject和Subject的接口相同,Proxy会引用Subject,并控制对具体目标对象的访问

  B、可以为代理类传入一个RealSubject的引用。也可以在Proxy内部自己创建一个RealSubject,并可能负责创建和删除它

  C、从Subject继承,这样代理就可以用来替代目标对象

  ②Subject:目标接口,定义代理和具体目标对象的接口,这样可以在任何使用具体目标对象的地方使用代理对象,让客户端透明地使用代理。

  ③RealSubject:具体的目标对象,真正实现目标接口要求的功能。

(3)思考代理模式

  ①代理模式的本质控制对象访问,通过代理目标对象,把代理对象插入到客户和目标对象之间,从而为客户和目标对象引入一定的间接性。正是这个间接性,给代理对象很多活动空间。代理对象可以在调用具体的目标对象前后,附加很多操作,从而实现新的功能或扩展功能。更狠的是,代理对象还可以不去创建和调用目标对象,也就是说,目标对象被完全代理掉了(替换)

  ②代理模式的动机:在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者或系统结构带来很多麻烦。这时可以增加一个代理对象,以不失透明操作对象的同理来管理/控制对对象的访问。

  ③代理模式的实现:主要使用对象的组合和委托。但也可以采用对象继承的方式来实现代理。

【编程实验】联系代理(而不是歌星本人)开演唱会

 

#include <iostream>

using namespace std;

//结构型模式:代理模式
//场景:联系代理(而不是歌星本人)开演唱会

class Star
{
public:
    virtual void confer() = 0;        //面谈
    virtual void signContract() = 0;  //签合同
    virtual void bookTicket() = 0;    //订票
    virtual void sing() = 0;          //唱歌
    virtual void collectMoney() = 0;   //收钱
};

//RealStar
class RealStar : public Star
{
public:
    void confer()
    {
        cout << "RealStart.confer()" << endl;
    }
    void signContract()
    {
        cout << "RealStart.signContract()" << endl;
    }
    void bookTicket()
    {
        cout << "RealStart.bookTicket()" << endl;
    }
    void sing()
    {
        cout << "RealStart(ZhouJieLun).sing(()" << endl;
    }
    void collectMoney()
    {
        cout << "RealStart.collectMoney()" << endl;
    }
};

//ProxyStar
class ProxyStar : public Star
{
private:
    Star* realStar;
public:
    ProxyStar(Star* realStar){this->realStar = realStar;}
    void confer()
    {
        cout << "ProxyStar.confer()" << endl;
    }
    void signContract()
    {
        cout << "ProxyStar.signContract()" << endl;
    }
    void bookTicket()
    {
        cout << "ProxyStar.bookTicket()" << endl;
    }
    void sing()
    {
        realStar->sing();
    }
    void collectMoney()
    {
        cout << "ProxyStar.collectMoney()" << endl;
    }
};

int main()
{
    Star* realStar = new RealStar();
    Star* proxyStar = new ProxyStar(realStar);

    //面谈、签合同、订票、收钱由代理来做
    //实际唱歌时,由代理去找真实明星来唱,对于客户端不需要
    //直接和真实明白打交道。
    proxyStar->confer();
    proxyStar->signContract();
    proxyStar->bookTicket();
    proxyStar->sing();      //唱歌时由代理去找真实明星来唱
    proxyStar->collectMoney();

    delete realStar;
    delete proxyStar;
    return 0;
}

2. 代理模式的分类

(1)虚代理可以根据需要来创建“大”对象,只有到必须创建对象的时候,虚代理才会去创建对象,从而大大加快程序运行速度,并节省资源。通过虚代理可以对系统进行优化。如开发一个大文档查看软件(如图片100MB),在打开文件前,先显示缩略图,而不是将所有图片都显示出来,在需要查看图片时,再用Proxy来进行大图片的打开)

(2)保护代理:可以在访问一个对象的前后,执行很多附加的操作,除了进行权限控制之外,还可以进行很多跟业务相关的处理,而不需要修改被代理的对象。也就是说,可以通过代理来给目标对象增加功能。

(3)Copy-on-write代理:也是虚代理的一个分支,在客户端操作的时候,只有对象确实改变了,才会真的拷贝(或克隆)一个目标的对象。在实现Copy-on-write时必须对目标对象进行引用计数。代理仅会增加引用。只有当用户请求一个修改目标对象的操作时,代理才会真正的拷贝它。在这种情况下,代理还必须减少目标对象的引用次数。当引用计数为0时,将目标对象删除。

(4)远程代理:隐藏了一个对象存在于不同地址空间的事实,也即是客户通过远程代理去访问一个对象,根本就不关心这个对象在哪里,也不关心如何通过网络去访问到这个对象,从客户的角度来看,它只是在使用代理对象而己。

(5)智能指针:和保护代理类似,也是允许在访问一个对象的前后,执行很多附加的操作,这样一来就可以做很多额外的事情,比如引用计数、第1次引用一个持久对象时,将它载入内存、在访问一个实际对象前,检查是否己经锁定了它,以确保其他对象不能改变它等。

(6)Cache代理:为那些昂贵操作的结果提供临时的存储空间,以便多个客户端可以共享这些结构。

(7)防火墙代理:保护对象不被恶意用户访问或操作。

(8)同步代理:使多个用户能够同时访问目标对象而没有冲突。

3. 代理的优点

(1)职责清晰:RealSubject就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成其他事务,附带的结果就是编程简洁清晰。

(2)可扩展性好:RealSubject可以随时变化,只要它实现了接口,甭管如何变化,代理可以在完全不做任何修改的情况下使用。

4. 代理的使用场景

(1)需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理

(2)需要按照需要创建开销很大的对象的时候,可以使用虚代理

(3)需要控制对原始对象的访问的时候,可以使用保护代理

(4)需要在访问对象执行一些附加操作时,可以使用智能指引代理。

【编程实验】利用虚代理实现大对象的延时加载

/*
//结构型模式:代理模式
//场景:一次性访问多条数据问题。
//需求:一个HR(人力资源)应用项目中,当用户选择一个部门时要把这个部门下所有员工都显示出来(只显示部门ID和姓名)
        在需要的时候,可以选择查看某位员工的详细信息
//思路:由于访问多条用户数据时,基本上只需要看到用户的姓名可以考虑刚开始从数据库查询返回的用户数据只有编号和用户姓名,当客户想要详细查看某个用户数据时,再根据编号从数
        据库中获取完整的用户数据。这样可以大大减少对内存的开销
//解决方案:使用代理(虚代理),刚开始只用一个不完整的用户对象(代理对象)。当需要访问时,再由代理对象从数据库中重新获取相应的数据,而这个过程对用户来说是透明的。
*/

#include <iostream>
#include <string>
#include <vector>

using namespace std;

//////////////////////////////////////////////////////////////////////////
// 字符串分割
//
// -------------------------------------------------------------------------
// 函数     : Split
// 功能     : 分割STL标准字符串
// 返回值   : void
// 参数     : Container<std::basic_string<CharT> >& v 存放分割结果
// 参数     : const std::basic_string<CharT>& s 待分割字符串
// 参数     : const std::basic_string<CharT>& c 分割字符串
// -------------------------------------------------------------------------
template<typename CharT, template<typename S, typename Q = std::allocator<S> > class Container>
void Split(Container<std::basic_string<CharT> >& v, const std::basic_string<CharT>& s, const std::basic_string<CharT>& c);

template<template<typename S, typename Q = std::allocator<S> > class Container>
void Split(Container<std::basic_string<char> >& v, const std::basic_string<char>& s, const std::basic_string<char>& c)
{
    if (0 == c.length())
        return;

    std::basic_string<char>::size_type pos1 = 0;
    std::basic_string<char>::size_type pos2 = 0;

    pos1 = 0;
    pos2 = s.find(c);
    while (std::basic_string<char>::npos != pos2)
    {
        v.push_back(s.substr(pos1, pos2 - pos1));

        pos1 = pos2 + c.size();
        pos2 = s.find(c, pos1);
    }

    if (pos1 != s.length())
    {
        v.push_back(s.substr(pos1));
    }
}

//******************************辅助类***************************
//测试数据(在内存中模拟数据库中的值)
class TestDB
{
private:
   TestDB(){}
public:
    static vector<string> userDB; //人员列表
    //static vector<string> depDB;  //部门列表
};

//人员列表初始数据
static vector<string>::value_type initUsers[] =
{
    vector<string>::value_type("use0001,ZhangSan1,010101,male"),
    vector<string>::value_type("use0002,ZhangSan2,010101,male"),
    vector<string>::value_type("use0003,ZhangSan3,010102,male"),
    vector<string>::value_type("use0004,ZhangSan4,010201,male"),
    vector<string>::value_type("use0005,ZhangSan5,010201,male"),
    vector<string>::value_type("use0006,ZhangSan6,010202,male")

};
vector<string> TestDB::userDB(initUsers, initUsers + 6);

//部门列表初始数据
//编号规则:如010201:表示总公司下的二分公司里的开发部
//static vector<string>::value_type initDepsDB[] =
//{
//    vector<string>::value_type("01,Company"),         //总公司
//    vector<string>::value_type("0101,BranchCompany1"),//一分公司
//    vector<string>::value_type("0102,BranchCompany2"),//二分公司
//    vector<string>::value_type("010101,DevelopDep"),  //开发部
//    vector<string>::value_type("010102,TestDep"),     //测试部
//    vector<string>::value_type("010201,DevelopDep"),  //开发部
//    vector<string>::value_type("010201,serviceDep")   //客服部
//
//};
//vector<string> TestDB::depDB(initDepsDB, initDepsDB + 7);

//**************************************代理类****************************
//定义用户数据对象的接口
class UserModelApi
{
public:
    virtual string& getUserId() = 0;
    virtual void setUserId(string userId) = 0;
    virtual string&  getName() = 0;
    virtual void setName(string name) = 0;
    virtual string&  getDepId() = 0;
    virtual void setDepId(string name) = 0;
    virtual string&  getSex() = 0;
    virtual void setSex(string sex) = 0;
    virtual ~UserModelApi(){};
};

//真实的目标对象——描述用户数据的对象
class UserModel: public UserModelApi
{
private:
    string userId; //用户编号
    string name;   //用户姓名
    string depId;  //部门编号
    string sex;    //用户性别
public:

    string& getUserId(){return userId;}
    void setUserId(string userId)
    {
        this->userId = userId;
    }

    string& getName(){return name;}
    void setName(string name)
    {
        this->name = name;
    }

    string& getDepId(){return depId;}
    void setDepId(string depId)
    {
        this->depId = depId;
    }

    string& getSex(){return sex;}
    void setSex(string sex)
    {
        this->sex = sex;
    }
};

//代理对象,代理用户数据对象
class Proxy : public UserModelApi
{
private:
    UserModel* realSubject; //持有被代理的具体的目标对象
    bool loaded;   //用来标识是否己经重新装载过数据了

    //重新询数据库以获取完整的用户数据
    void reload()
    {
        cout << "reload full information,userId ==" <<
             realSubject->getUserId() << endl;

        //实际中,这里应该处理连接数据库的代码

        //这里获取除了userId和name外的数据
        vector<string>::iterator iter = TestDB::userDB.begin();
        vector<string> tmpStr;
        while (iter != TestDB::userDB.end())
        {
            tmpStr.clear();
            Split(tmpStr,*iter,",");

            if(tmpStr.size() == 4)
            {
                if(tmpStr[0] == realSubject->getUserId())
                {
                    realSubject->setDepId(tmpStr[2]);
                    realSubject->setSex(tmpStr[3]);
                }
            }
            ++iter;
        }
    }

public:
    Proxy():loaded(false)
    {
        realSubject = new UserModel();
    }

    ~Proxy(){delete realSubject;}

    string& getUserId()
    {
        return realSubject->getUserId();
    }

    void setUserId(string userId)
    {
        realSubject->setUserId(userId);
    }

    string& getName()
    {
       return  realSubject->getName();
    }

    void setName(string name)
    {
        realSubject->setName(name);
    }

    string& getDepId()
    {
        if(!loaded)
        {
            //从数据库中重新装载
            reload();
            //设置重新装载标志为true
            loaded = true;
        }
       return  realSubject->getDepId();
    }

    void setDepId(string depId)
    {
        realSubject->setDepId(depId);
    }

    string& getSex()
    {
        if(!loaded)
        {
            //从数据库中重新装载
            reload();
            //设置重新装载标志为true
            loaded = true;
        }
        return realSubject->getSex();
    }

    void setSex(string sex)
    {
        realSubject->setSex(sex);
    }

    string toString()
    {
        return "userId = " + getUserId() + ","  +
               "name = "   + getName()   + ", " +
               "depId = "  + getDepId()  + ", " +
               "sex = "    + getSex();
    }
};

//实现示例要求的功能
class UserManager
{
private:
    vector<UserModelApi*>  users;

    //清除
    void clearUsers()
    {
        vector<UserModelApi*>::iterator iter = users.begin();
        while(iter != users.end())
        {
            delete *iter;
        }
        users.clear();
    }
public:
    //根据部门编号来获取该部门下的所有人员
    vector<UserModelApi*> getUserByDepId(string depId)
    {
        //只查询userId和name两个值就可以了
        clearUsers();

        //实际中,这里须访问数据库
        vector<string>::iterator iter = TestDB::userDB.begin();
        vector<string> tmpStr;
        while (iter != TestDB::userDB.end())
        {
            tmpStr.clear();
            Split(tmpStr,*iter,",");

            if(tmpStr.size() == 4)
            {
                if(tmpStr[2].substr(0,4) == depId)
                {
                    //这里创建代理对象,而不是直接创建UserModel
                    Proxy* proxy = new Proxy();

                    //只设置userId和name两个值就可以了
                    proxy->setUserId(tmpStr[0]);
                    proxy->setName(tmpStr[1]);
                    users.push_back(proxy);
                }
            }
            ++iter;
        }
        return users;
    }

    ~UserManager()
    {
        clearUsers();
    }
};

int main()
{
    UserManager userManager;
    vector<UserModelApi*> Users = userManager.getUserByDepId("0101");

    //如果只是显示用户名称,则不需要重新查询数据库
    vector<UserModelApi*>::iterator iter = Users.begin();
    while(iter !=Users.end())
    {
        cout <<"userId:=" << (*iter)->getUserId() <<" userName:=" <<
             (*iter)->getName() << endl;
        ++iter;
    }

    cout << endl;

    //如果访问非用户编号和用户姓名外的属性,那就会重新查询数据库
    vector<UserModelApi*>::iterator iter2 = Users.begin();
    while(iter2 !=Users.end())
    {
        //cout << ((Proxy*)(*iter2))->toString() << endl;
        cout <<"userId:=" << (*iter2)->getUserId() << ", " <<
               "userName:=" << (*iter2)->getName()   << ", " <<
               "depId:= "<< (*iter2)->getDepId()  << endl;
        ++iter2;
    }
    return 0;
}

5. 相关模式

(1)代理模式与适配器模式

  ①相似:都为另一个对象提供间接性的访问,而且都是从自身以外的一个接口向另这个对象转发请求。

  ②不同:适配器模式主要用来解决接口之间不匹配的问题,它通常为所适配对象提供一个不同的接口;而代理模式会实现和目标对象相同的接口

(2)代理模式与装饰模式

  ①相似:装饰模式的实现和保护代理的实现上是类似的。都是在转调其他对象的前后执行一定的功能。

  ②不同:装饰模式的目的是为让你不生成子类就可以给对象添加职责,也就是为了动态增加功能;而代理模式的主要目的是控制对对象的访问(如当代理检测到有敏感关键词时,就直接返回,不会再调用真实的目标对象,但装饰模式的目的是增加新功能,所以一般得继续调用真实目标对象,所以可以认为装饰只是新增功能,但不能跳过原来的功能)。

  ③当实现代理模式的时候,我们常常在一个代理类中创建一个真实目标对象的实例,但装饰模式时,我们通常的做法是将原始对象作为一个参数传递给装饰者的构造器。

以上是关于结构型模式—代理模式的主要内容,如果未能解决你的问题,请参考以下文章

代理模式 - 结构

[设计模式] 设计模式课程(十三)-- 代理模式

[设计模式] 设计模式课程(十三)-- 代理模式

结构型模式:代理模式

Proxy(代理)-对象结构型模式

结构型模式—代理模式