• 【设计模式】 模式PK:包装模式群PK


    1、概述

      我们讲了这么多的设计模式,大家有没有发觉在很多的模式中有些角色是不干活的?它们只是充当黔首作用,你有问题,找我,但我不处理,我让其他人处理。最典型的就是代理模式了,代理角色接收请求然后传递到被代理角色处理。门面模式也是一样,门面角色的任务就是把请求转发到子系统。类似这种结构的模式还有很多,我们先给这种类型的模式定义一个名字,叫做包装模式(wrappingpattern)。注意,包装模式是一组模式而不是一个。包装模式包括哪些设计模式呢?包装模式包括:装饰模式、适配器模式、门面模式、代理模式、桥梁模式。下面我们通过一组例子来说明这五个包装模式的区别。

    2、代理模式

    2.1 类图

      现在很多明星都有经纪人,一般有什么事他们都会说:“你找我的经纪人谈好了”,下面我们就看看这一过程怎么模拟。假设有一个追星族想找明星签字,我们看看采用代理模式怎么实现。代理模式是包装模式中的最一般的实现。类图很简单,就是一个简单的代理模式。

    2.2 代码

    2.2.1 明星接口

    class CIStar
    {
    public:
        CIStar(){}
        ~CIStar(){}
    
        //明星都会签名
        virtual void mvSign() = 0; 
    };

    2.2.2 明星

      明星只有一个行为:签字。我们来看明星的实现。

    class CSinger : public CIStar
    {
    public:
        CSinger(){}
        ~CSinger(){}
    
        void mvSign()
        {
            cout << "明星签字: 我是XXX歌手" << endl;
        }
    };

    2.2.3 经纪人

      经纪人与明星应该有相同的行为,比如说签名,虽然经纪人不签名,但是他把你要签名的笔记本、衣服、CD等传递过去让真正的明星签字。

      应该非常明确地指出一个经纪人是谁的代理,因此要在构造函数中接收一个明星对象,确定是要做这个明星的代理。

    class CAgent : public CIStar
    {
    public:
        //构造函数传递明星
        CAgent(CIStar *opStar) : mopStar(opStar) {}
        ~CAgent(){}
    
        //经纪人是不会签字的,找歌手签字
        void mvSign()
        {
            mopStar->mvSign();
        }
    
    private:
        //定义是谁的经纪人
        CIStar *mopStar;
    };

    2.2.4 场景调用

      我们再来看看追星族是怎么找明星签字的。

    int main()
    {
        //崇拜的明星是谁
        CIStar *op_star = new CSinger;
    
        //找到明星的经纪人
        CIStar *op_agent = new CAgent(op_star);
    
        cout << "追星族找歌手签名" << endl;
        op_agent->mvSign();
    
        return 0;
    }

    2.2.5 执行结果

      很简单,找到明星的代理,然后明星就签字了。运行结果如下所示:

      看看我们的程序逻辑,我们是找明星的经纪人签字,真实签字的是明星,经纪人只是把这个请求传递给明星处理而已,这是普通的代理模式的典型应用。

    3、装饰模式

    3.1 类图

      明星也都是一步一步地奋斗出来的,谁都不是一步就成为大明星的。甚至一些演员通过粉饰自己给观众一个好的印象,现在我们就来看怎么粉饰一个演员。

    3.2 代码 

      下面我们就来看看这些过程如何实现。

    3.2.1 明星接口

    class CIStar
    {
    public:
        CIStar(){}
        ~CIStar(){}
        
        //演戏
        virtual void mvAct() = 0;
    };

    3.2.2 假明星

    class CFreakStar : public CIStar
    {
    public:
        CFreakStar(){}
        ~CFreakStar(){}
    
        void mvAct()
        {
            cout << "演中: 演技很拙劣" << endl;
        }
    };

    3.2.3 抽象装饰类

      们看看这个明星是怎么粉饰的,先定义一个抽象装饰类。

    class CDecorator : public CIStar
    {
    public:
        CDecorator( CIStar *opStar ) : mopStar(opStar){}
        ~CDecorator(){}
    
        void mvAct()
        {
            mopStar->mvAct();
        }
    
    private:
        //粉饰的是谁
        CIStar *mopStar;
    };

    3.2.4 装饰类

      前后两次修饰,开演前毫无忌惮地吹嘘,吹大话装饰。

    class CHotAir : public CDecorator
    {
    public:
        CHotAir(CIStar *opStar) : CDecorator(opStar){}
        ~CHotAir(){}
    
        void mvAct()
        {
            cout << "演前: 夸夸其谈, 没有自己不能演的角色" << endl;
            CDecorator::mvAct();
        }
    };

      大家发现这个明星演技不好的时候,他拼命找借口,说是那天天气不好、心情不好等。

    class CDeny : public CDecorator
    {
    public:
        CDeny(CIStar *opStar) : CDecorator(opStar){}
        ~CDeny(){}
    
        void mvAct()
        {
            CDecorator::mvAct();
            cout << "演后: 百般抵赖, 死不承认" << endl;
        }
    };

    3.2.5 场景调用

      我们建立一个场景把这种情况展示一下。

    int main()
    {
        //定义出所谓的明星
        CIStar *op_freak = new CFreakStar;
    
        //看看他是怎么粉饰自己的
        //演前吹嘘自己无所不能
        op_freak = new CHotAir(op_freak);
        //演完后, 死不承认自己演的不好
        op_freak = new CDeny(op_freak);
    
        cout << "====看看一些虚假明星的形象====" << endl;
        op_freak->mvAct();
    
        return 0;
    }

    3.2.6 执行结果

    4、适配器模式

    4.1 类图

      我们知道在演艺圈中还存在一种情况:替身,替身也是演员,只是普通的演员而已,在一段戏中,前十五分钟是明星本人,后十五分钟也是明星本人,就中间的五分钟是替身,那这个场景该怎么描述呢?注意中间那五分钟,这个时候一个普通演员被导演认为是明星演员,我们来看类图。导演找了一个普通演员作为明星的替身,不过观众看到的还是明星的身份。

    4.2 代码

    4.2.1 明星接口

    class CIStar
    {
    public:
        CIStar(){}
        ~CIStar(){}
    
        //明星都要演戏
        virtual void mvAct(const string &sContext) = 0;
    };

    4.2.2 明星

      再来看一个具体的电影明星,他的主要职责就是演戏。

    class CFilmStar : public CIStar
    {
    public:
        CFilmStar(){}
        ~CFilmStar(){}
    
        void mvAct(const string &sContext)
        {
            cout << "明星演戏: " << sContext.c_str() << endl;
        }
    };

    4.2.3 普通演员接口

      我们再来看普通演员,明星就那么多,但是普通演员非常多,我们看其接口。

    class CIActor
    {
    public:
        CIActor(){}
        ~CIActor(){}
    
        //普通演员演戏
        virtual void mvPlayAct(const string &sContext) = 0;
    };

    4.2.4 普通演员

      普通演员也是演员,是要演戏的,我们来看一个普通演员的实现。

    class CUnknownActor : public CIActor
    {
    public:
        CUnknownActor(){}
        ~CUnknownActor(){}
    
        void mvPlayAct(const string &sContext)
        {
            cout << "普通演员: " << sContext.c_str() << endl;
        }
    };

    4.2.5 替身

    class CStandin : public CIStar
    {
    public:
        CStandin(CIActor *opActor) : mopActor(opActor){}
        ~CStandin(){}
    
        void mvAct(const string &sContext)
        {
            mopActor->mvPlayAct(sContext);
        }
    
    private:
        //替身是谁
        CIActor *mopActor;
    };

    4.2.6 场景调用

      是一个通用的替身,哪个普通演员能担任哪个明星的替身是由导演决定的,导演想让谁当就让谁当,我们来看导演。

      这里使用了适配器模式,把一个普通的演员转换为一个明星演员。

    int main()
    {
        cout << "=======演戏过程模拟==========" << endl;
        //定义一个大明星
        CIStar *op_star = new CFilmStar;
        op_star->mvAct("前十五分钟, 明星本人演戏");
        //导演把一个普通演员当做明星演员来用
        CIActor *op_actor = new CUnknownActor;
        CIStar *op_standin = new CStandin(op_actor);
        op_standin->mvAct("中间五分钟, 替身在演戏");
        op_star->mvAct("后十五分钟, 明星本人演戏");
    
        return 0;
    }

    5、桥梁模式

    5.1 类图

      我们继续说明星圈的事情,现在明星类型太多了,比如电影明星、电视明星、歌星、体育明星、网络明星等,每个类型的明星都有明确的职责,电影明星的主要工作就是演电影,电视明星的主要工作就是演电视剧或者主持电视节目。再看看现在的明星,单一发展的基本没有,主持人出专辑、体育明星演电影、歌星拍戏等太平常了,我们就用程序来表现一下多元化情形。

       类图中定义了一个抽象明星AbsStar,然后产生出各个具体类型的明星,比如电影明星FilmStar、歌星Singer,当然还可以继续扩展下去。这里还定义了一个抽象的行为AbsAction,描述明星所具有的活动,比如演电影、唱歌等,在这种设计下,明星可以扩展,明星的活动也可以扩展,非常灵活。

    5.2 代码

    5.2.1 抽象活动

      很简单,只有一个活动的描述,由子类来实现。

    class CAbsAction
    {
    public:
        CAbsAction(){}
        ~CAbsAction(){}
    
        //每个活动都有描述
        virtual void mvDesc() = 0;
    };

    5.2.2 具体活动

    // 演电影
    class CActFilm : public CAbsAction
    {
    public:
        CActFilm(){}
        ~CActFilm(){}
    
        void mvDesc()
        {
            cout << "演出精彩绝伦的电影" << endl;
        }
    };
    
    //唱歌
    class CSing : public CAbsAction
    {
    public:
        CSing(){}
        ~CSing(){}
    
        void mvDesc()
        {
            cout << "唱出优美的歌曲" << endl;
        }
    };

    5.2.3 抽象明星

      各种精彩的活动都有了,我们再来看抽象明星,它是所有明星的代表。

      明星都有自己的主要活动(或者是主要工作),我们在抽象明星中只是定义明星有活动,具体有什么活动由各个子类实现。

    class CAbsStar
    {
    public:
        //通过构造函数传递具体活动
        CAbsStar(CAbsAction *opAction) : mopAction(opAction) {}
    
        ~CAbsStar(){}
    
        virtual void mvDoJob()
        {
            mopAction->mvDesc();
        }
    
    protected:
        //一个明星参加哪些活动
        CAbsAction *mopAction;
    };

    5.2.4 电影明星

    class CFilmStar : public CAbsStar
    {
    public:
        //默认的电影明星的主要工作是拍电影
        CFilmStar() : CAbsStar(new CActFilm){}
        ~CFilmStar(){}
    
        //也可以重新设置一个新职业
        CFilmStar(CAbsAction *opAction) : CAbsStar(opAction){}
    
        //细化电影明星的职责
        void mvDoJob()
        {
            cout << "======影星的工作=====" << endl;
            CAbsStar::mvDoJob();
        }
    };

      电影明星的本职工作就应该是演电影,因此就有了一个无参构造函数来定义电影明星的默认工作,如果明星要客串一下去唱歌也可以,有参构造解决了该问题。

    5.2.5 歌星

      歌星和电影明星类似。

    class CSinger : public CAbsStar
    {
    public:
        //歌星的默认活动是唱歌
        CSinger() : CAbsStar(new CSing){}
        ~CSinger(){}
    
        //也可以重新设置一个新职业
        CSinger(CAbsAction *opAction) : CAbsStar(opAction){}
    
        //细化歌星的职责
        void mvDoJob()
        {
            cout << "======歌星的工作=====" << endl;
            CAbsStar::mvDoJob();
        }
    };

    5.2.6 场景调用

      我们使用电影明星和歌星来作为代表,这两类明星也是我们经常听到或看到的,下面建立一个场景类来模拟一下明星的事迹。

    int main()
    {
        //声明一个电影明星
        CAbsStar *op_zhang = new CFilmStar;
        //声明一个歌星
        CAbsStar *op_li = new CSinger;
        //展示一下各个明星的主要工作
        op_zhang->mvDoJob();
        op_li->mvDoJob();
        //当然, 也有部分明星不务正业, 比如歌星演戏
        cout << "不务正业的明星" << endl;
        op_li = new CSinger(new CActFilm);
        op_li->mvDoJob();
    
        return 0;
    }

    5.2.7 执行结果

      好了,各类明星都有自己的本职工作,但是偶尔客串一个其他类型的活动也是允许的,如此设计后,明星就可以不用固定在自己的本职工作上,而是向其他方向发展,比如影视歌三栖明星。

    6、门面模式

      门面模式我们在其他文章已经讲解得比较多了,本篇不再赘述。

    7、总结

      5个包装模式是大家在系统设计中经常会用到的模式,它们具有相似的特征:都是通过委托的方式对一个对象或一系列对象(例如门面模式)施行包装,有了包装,设计的系统才更加灵活、稳定,并且极具扩展性。从实现的角度来看,它们都是代理的一种具体表现形式,我们来看看它们在使用场景上有什么区别。

      代理模式主要用在不希望展示一个对象内部细节的场景中,比如一个远程服务不需要把远程连接的所有细节都暴露给外部模块,通过增加一个代理类,可以很轻松地实现被代理类的功能封装。

      装饰模式是一种特殊的代理模式,它倡导的是在不改变接口的前提下为对象增强功能,或者动态添加额外职责。就扩展性而言,它比子类更加灵活,例如在一个已经运行的项目中,可以很轻松地通过增加装饰类来扩展系统的功能。

      适配器模式的主要意图是接口转换,把一个对象的接口转换成系统希望的另外一个接口,这里的系统指的不仅仅是一个应用,也可能是某个环境,比如通过接口转换可以屏蔽外界接口,以免外界接口深入系统内部,从而提高系统的稳定性和可靠性。

      桥梁模式是在抽象层产生耦合,解决的是自行扩展的问题,它可以使两个有耦合关系的对象互不影响地扩展,比如对于使用笔画图这样的需求,可以采用桥梁模式设计成用什么笔(铅笔、毛笔)画什么图(圆形、方形)的方案,至于以后需求的变更,如增加笔的类型,增加图形等,对该设计来说是小菜一碟。

      门面模式是一个粗粒度的封装,它提供一个方便访问子系统的接口,不具有任何的业务逻辑,仅仅是一个访问复杂系统的快速通道,没有它,子系统照样运行,有了它,只是更方便访问而已。

  • 相关阅读:
    COCI2013-2014 Contest#3 F 单调栈
    Topcoder SRM568 Div1 DisjointSemicircles (二分图染色)
    COCI2013-2014 Contest#1 F SLASTIČAR
    TopCoder SRM 561 Orienteering(树形dp)
    COCI20122013 Contest#5 F
    2016 多校5 ATM
    2014多校6 Another Letter Tree
    HAOI2015 数组游戏
    [CCO 2017]接雨滴
    Luogu P6789 寒妖王
  • 原文地址:https://www.cnblogs.com/ChinaHook/p/7482536.html
Copyright © 2020-2023  润新知