【设计模式】 模式PK:策略模式VS桥梁模式

1、概述

我们先来看两种模式的通用类图。

两者之间确实很相似。如果把策略模式的环境角色变更为一个抽象类加一个实现类,或者桥梁模式的抽象角色未实现,只有修正抽象化角色,想想看,这两个类图有什么地方不一样?完全一样!正是由于类似场景的存在才导致了两者在实际应用中经常混淆的情况发生,我们来举例说明两者有何差别。

大家都知道邮件有两种格式:文本邮件(TextMail)和超文本邮件(HTMLMaiL),在文本邮件中只能有简单的文字信息,而在超文本邮件中可以有复杂文字(带有颜色、字体等属性)、图片、视频等,如果你使用Foxmail邮件客户端的话就应该有深刻体验,看到一份邮件,怎么没内容?原来是你忘记点击那个“HTML邮件”标签了。下面我们就来讲解如何发送这两种不同格式的邮件,研究一下这两种模式如何处理这样的场景。

2、策略模式实现邮件发送

2.1 类图

使用策略模式发送邮件,我们认为这两种邮件是两种不同的封装格式,给定了发件人、收件人、标题、内容的一封邮件,按照两种不同的格式分别进行封装,然后发送之。按照这样的分析,我们发现邮件的两种不同封装格式就是两种不同的算法,具体到策略模式就是两种不同策略,这样看已经很简单了,我们可以直接套用策略模式来实现。

我们定义了一个邮件模板,它有两个实现类:TextMail(文本邮件)和HtmlMail(超文本邮件),分别实现两种不同格式的邮件封装。MailServer是一个环境角色,它接收一个MailTemplate对象,然后通过sendMail方法发送出去。

2.2 代码

2.2.1 抽象邮件

class CMailTemplate
{
public:
    CMailTemplate(const string &sFrom, const string &sTo, const string &sSubject , const string &sContent) :
                                msFrom(sFrom), msTo(sTo), msSubject(sSubject), msContent(sContent){}

    ~CMailTemplate(){};

    void mvSetFrom(const string &sFrom) { msFrom = sFrom; }
    string msGetFrom() { return msFrom; }

    void mvSetTo(const string &sTo) { msTo = sTo; }
    string msGetTo() { return msTo; }

    void mvSetSubject(const string &sSubject) { msSubject = sSubject; }
    string msGetSubject() { return msSubject; }

    void mvSetContent(const string &sContent) { msContent = sContent; }
    virtual string msGetContent(){ return msContent; }

private:
    string msFrom;       //邮件发件人
    string msTo;         //收件人
    string msSubject;    //邮件标题
    string msContent;    //通过构造函数传递邮件信息
};

抽象类没有抽象的方法,设置为抽象类还有什么意义呢?有意义,在这里我们定义了一个这样的抽象类:它具有邮件的所有属性,但不是一个具体可以被实例化的对象。例如,你对邮件服务器说“给我制造一封邮件”,邮件服务器肯定拒绝,为什么?你要产生什么邮件?什么格式的?邮件对邮件服务器来说是一个抽象表示,是一个可描述但不可形象化的事物。你可以这样说:“我要一封标题为XX,发件人是XXX的文本格式的邮件”,这就是一个可实例化的对象,因此我们的设计就产生了两个子类以具体化邮件,而且每种邮件格式对邮件的内容都有不同的处理。

2.2.2 文本邮件

class CTextMail : public CMailTemplate
{
public:
    CTextMail(const string &sFrom, const string &sTo, const string &sSubject, const string &sContent) :
                        CMailTemplate(sFrom, sTo, sSubject, sContent) {}

    ~CTextMail() {}

    string msGetContent()
    {
        //文本类型设置邮件的格式为: text/plain
        string s_content = "\nContent-Type: text/plain;charset=GB2312\n" + CMailTemplate::msGetContent();
        //同时对邮件进行base64编码处理,这里用一句话代替
        s_content += "\n邮件格式为: 文本格式";

        return s_content;
    }
};

我们覆写了msGetContent方法,因为要把一封邮件设置为文本邮件必须加上一个特殊的标志:text/plain,用于告诉解析这份邮件的客户端:“我是一封文本格式的邮件,别解析错了”。

2.2.3 超文本邮件

同样,超文本格式的邮件也有类似的设置。

class CHtmlMail : public CMailTemplate
{
public:
    CHtmlMail(const string &sFrom, const string &sTo, const string &sSubject, const string &sContent) :
        CMailTemplate(sFrom, sTo, sSubject, sContent) {}

    ~CHtmlMail() {}

    string msGetContent()
    {
        //超文本类型设置邮件的格式为: multipart/mixed
        string s_content = "\nContent-Type: multipart/mixed; charset= GB2312\n" + CMailTemplate::msGetContent();
        //同时对邮件进行base64编码处理,这里用一句话代替
        s_content += "\n邮件格式为: 超文本格式";

        return s_content;
    }
};

优秀一点的邮件客户端会对邮件的格式进行检查,比如编写一封超文本格式的邮件,在内容中加上了<font>标签,但是遗忘了</font>结尾标签,邮件的产生者(也就是邮件的客户端)会提示进行修正,我们这里用了“邮件格式为:超文本格式”来代表该逻辑。两个实现类实现了不同的算法,给定相同的发件人、收件人、标题和内容可以产生不同的邮件信息。

2.2.4 邮件服务器

class CMailServer
{
public:
    CMailServer(CMailTemplate *opMail) : mopMail(opMail) {}
    ~CMailServer(){}

    //发送邮件
    void mvSendMail()
    {
        cout << "====正在发送的邮件信息====" << endl;
        //发件人
        cout << "发件人: " << mopMail->msGetFrom().c_str() << endl;
        //收件人
        cout << "收件人:" << mopMail->msGetTo().c_str() << endl;
        //标题
        cout << "邮件标题: " << mopMail->msGetSubject().c_str() << endl;
        //邮件内容
        cout << "邮件内容: " << mopMail->msGetContent().c_str() << endl;
    }

private:
    //发送的是哪封邮件
    CMailTemplate *mopMail;
};

很简单,邮件服务器接收了一封邮件,然后调用自己的发送程序进行发送。有人可能要问了,为什么不把mvSendMail方法移植到邮件模板类中呢?这也是邮件模板类的一个行为,邮件可以被发送。是的,这确实是邮件的一个行为,完全可以这样做,两者没有什么区别,只是从不同的角度看待该方法而已。

2.2.5 场景调用

int main()
{
    //创建一封TEXT格式的邮件
    CMailTemplate *op_mail = new CHtmlMail("[email protected]", "[email protected]", "外星人攻击地球了", "结果是外星人被地球人打败了!");

    //创建一个Mail发送程序
    CMailServer *op_server = new CMailServer(op_mail);
    op_server->mvSendMail();

    return 0;
}

2.2.6 执行结果

当然,如果想产生一封文本格式的邮件,只要稍稍修改一下场景类就可以了:new CHtmlMail修改为new CTextMail,非常简单。在该场景中,我们使用策略模式实现两种算法的自由切换,它提供了这样的保证:封装邮件的两种行为是可选择的,至于选择哪个算法是由上层模块决定的。策略模式要完成的任务就是提供两种可以替换的算法。

3、桥梁模式实现邮件发送

3.1 类图

桥梁模式关注的是抽象和实现的分离,它是结构型模式,结构型模式研究的是如何建立一个软件架构,下面我们就来看看桥梁模式是如何构件一套发送邮件的架构的。

类图中我们增加了SendMail和Postfix两个邮件服务器来实现类,在邮件模板中允许增加发送者标记,其他与策略模式都相同。我们在这里已经完成了一个独立的架构,邮件有了,发送邮件的服务器也具备了,是一个完整的邮件发送程序。需要注意的是,SendMail类不是一个动词行为(发送邮件),它指的是一款开源邮件服务器产品,一般*nix系统的默认邮件服务器就是SendMail;Postfix也是一款开源的邮件服务器产品,其性能、稳定性都在逐步赶超SendMail。

3.2 代码

3.2.1 邮件模板

我们来看代码实现,邮件模板仅仅增加了一个add方法,文本邮件、超文本邮件都没有任何改变。

//邮件模板
class CMailTemplate
{
public:
    CMailTemplate(const string &sFrom, const string &sTo, const string &sSubject, const string &sContent) :
        msFrom(sFrom), msTo(sTo), msSubject(sSubject), msContent(sContent){}

    ~CMailTemplate(){};

    void mvSetFrom(const string &sFrom) { msFrom = sFrom; }
    string msGetFrom() { return msFrom; }

    void mvSetTo(const string &sTo) { msTo = sTo; }
    string msGetTo() { return msTo; }

    void mvSetSubject(const string &sSubject) { msSubject = sSubject; }
    string msGetSubject() { return msSubject; }

    void mvSetContent(const string &sContent) { msContent = sContent; }
    virtual string msGetContent(){ return msContent; }

    //允许增加邮件发送标志
    void mvAdd(const string &sSendInfo)
    {
        msContent = sSendInfo + msContent;
    }

private:
    string msFrom;        //邮件发件人
    string msTo;          //收件人
    string msSubject;     //邮件标题
    string msContent;     //通过构造函数传递邮件信息
};

//文本邮件
class CTextMail : public CMailTemplate
{
public:
    CTextMail(const string &sFrom, const string &sTo, const string &sSubject, const string &sContent) :
        CMailTemplate(sFrom, sTo, sSubject, sContent) {}

    ~CTextMail() {}

    string msGetContent()
    {
        //文本类型设置邮件的格式为: text/plain
        string s_content = "\nContent-Type: text/plain;charset=GB2312\n" + CMailTemplate::msGetContent();
        //同时对邮件进行base64编码处理,这里用一句话代替
        s_content += "\n邮件格式为: 文本格式";

        return s_content;
    }
};

//超文本邮件
class CHtmlMail : public CMailTemplate
{
public:
    CHtmlMail(const string &sFrom, const string &sTo, const string &sSubject, const string &sContent) :
        CMailTemplate(sFrom, sTo, sSubject, sContent) {}

    ~CHtmlMail() {}

    string msGetContent()
    {
        //超文本类型设置邮件的格式为: multipart/mixed
        string s_content = "\nContent-Type: multipart/mixed; charset= GB2312\n" + CMailTemplate::msGetContent();
        //同时对邮件进行base64编码处理,这里用一句话代替
        s_content += "\n邮件格式为: 超文本格式";

        return s_content;
    }
};

3.2.2 邮件服务器

我们来看邮件服务器,也就是桥梁模式的抽象化角色。

class CMailServer
{
public:
    CMailServer(CMailTemplate *opMail) : mopMail(opMail) {};
    ~CMailServer(){};

    //发送邮件
    virtual void mvSendMail()
    {
        cout << "====正在发送的邮件信息====" << endl;
        //发件人
        cout << "发件人:" << mopMail->msGetFrom().c_str() << endl;
        //收件人
        cout << "收件人:" << mopMail->msGetTo().c_str() << endl;
        //标题
        cout << "邮件标题: " << mopMail->msGetSubject().c_str() << endl;
        //邮件内容
        cout << "邮件内容: " << mopMail->msGetContent().c_str() << endl;
    }

protected:
    //发送的是哪封邮件
    CMailTemplate *mopMail;
};

该类相对于策略模式的环境角色有两个改变:

● 修改为抽象类。为什么要修改成抽象类?因为我们在设计一个架构,邮件服务器是一个具体的、可实例化的对象吗?“给我一台邮件服务器”能实现吗?不能,只能说“给我一台Postfix邮件服务器”,这才能实现,必须有一个明确的可指向对象。

● 变量m修改为Protected访问权限,方便子类调用。

3.2.3 Postfix邮件服务器

class CPostfix : public CMailServer
{
public:
    CPostfix(CMailTemplate *opMail) : CMailServer(opMail) {}
    ~CPostfix(){}

    //修正邮件发送程序
    void mvSendMail()
    {
        //增加邮件服务器信息
        string s_content = "Received: from XXXX (unknown [xxx.xxx.xxx.xxx]) by aaa.aaa.com(Postfix) with ESMTP id 8DBCB1456B8\n";
        mopMail->mvAdd(s_content);
        CMailServer::mvSendMail();
    }
};

3.2.4 SendMail邮件服务器

为什么要覆写mvSendMail程序呢?这是因为每个邮件服务器在发送邮件时都会在邮件内容上留下自己的标志,一是广告作用,二是为了互联网上统计需要,三是方便同质软件的共振。

class CSendMail : public CMailServer
{
public:
    CSendMail(CMailTemplate *opMail) : CMailServer(opMail) {}
    ~CSendMail(){}

    //修正邮件发送程序
    void mvSendMail()
    {
        //增加邮件服务器信息
        string s_content = "Received: (sendmail); 7 Nov 2009 04:14:44 +0100";
        mopMail->mvAdd(s_content);
        CMailServer::mvSendMail();
    }
};

3.2.5 场景调用

邮件和邮件服务器都有了,我们来看怎么发送邮件。

int main()
{
        //创建一封TEXT格式的邮件
        CMailTemplate *op_mail = new CTextMail("[email protected]", "[email protected]", "外星人攻击地球了", " 结果地球人打败了外星人!");
        //使用Postfix发送邮件
        CMailServer *op_post = new CPostfix(op_mail);
        //发送邮件
        op_post->mvSendMail();

        return 0;
}

3.2.6 运行结果

当然了,还有其他三种发送邮件的方式:Postfix发送文本邮件以及SendMail发送文本邮件和超文本邮件。

4、总结

策略模式和桥梁模式是如此相似,我们只能从它们的意图上来分析。策略模式是一个行为模式,旨在封装一系列的行为,在例子中我们认为把邮件的必要信息(发件人、收件人、标题、内容)封装成一个对象就是一个行为,封装的格式(算法)不同,行为也就不同。而桥梁模式则是解决在不破坏封装的情况下如何抽取出它的抽象部分和实现部分,它的前提是不破坏封装,让抽象部分和实现部分都可以独立地变化,在例子中,我们的邮件服务器和邮件模板是不是都可以独立地变化?不管是邮件服务器还是邮件模板,只要继承了抽象类就可以继续扩展,它的主旨是建立一个不破坏封装性的可扩展架构。

简单来说,策略模式是使用继承和多态建立一套可以自由切换算法的模式,桥梁模式是在不破坏封装的前提下解决抽象和实现都可以独立扩展的模式。桥梁模式必然有两个“桥墩”——抽象化角色和实现化角色,只要桥墩搭建好,桥就有了,而策略模式只有一个抽象角色,可以没有实现,也可以有很多实现。

还是很难区分,是吧?多想想两者的意图,就可以理解为什么要建立两个相似的模式了。我们在做系统设计时,可以不考虑到底使用的是策略模式还是桥梁模式,只要好用,能够解决问题就成,“不管黑猫白猫,抓住老鼠的就是好猫”。

时间: 2024-10-18 13:19:24

【设计模式】 模式PK:策略模式VS桥梁模式的相关文章

行为型设计模式之模板方法(TEMPLATE METHOD)模式 ,策略(Strategy )模式

1 模板方法(TEMPLATE METHOD)模式: 模板方法模式把我们不知道具体实现的步聚封装成抽象方法,提供一些按正确顺序调用它们的具体方法(这些具体方法统称为模板方法),这样构成一个抽象基类.子类通过继承这个抽象基类去实现各个步聚的抽象方法,而工作流程却由父类来控制. 2 模板方法应用于下列情况: 1) 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现. 2)各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复.首先识别现有代码中的不同之处,并且将不同之处分离为新

【java设计模式】之 策略(strategy)模式

策略模式在实际中使用的还是挺多的,先来看一个场景:某个市场人员接到单儿后的报价策略,保价策略很复杂,但是可以简单做如下归类: 新客户小批量报价 新客户大批量报价 老客户小批量报价 老客户大批量报价 具体选用哪个报价策略,这需要根据实际情况来确定,这时候采用策略模式即可解决这个问题.这个问题中,如果我们不采用策略模式会怎样处理呢?很自然的会想到使用if判断,或者switch-case-来解决,根据不同的用户确定不同的报价.类似于如下的结构: public double getPrice(Strin

设计模式之桥梁模式20170721

行为型设计模式之桥梁模式: 一.含义 桥梁模式也叫做桥接模式,其定义如下: 将抽象和实现解耦,使得两者可以独立地变化. 只要记住一句话就行:抽象角色引用实现角色,或者说抽象角色的部分实现是由实现角色完成的. 二.代码说明 1.主要有四个角色 1)抽象化角色 它的主要职责是定义出该角色的行为,同时保存一个对实现化角色的引用,该角色一般是抽象类 2)实现化角色 它是接口或者抽象类,定义角色必须的行为和属性 3)修正抽象化角色 它引用实现化角色对抽象化角色进行修正 4)具体实现化角色 它实现接口或抽象

24种设计模式--桥梁模式【Bridge Pattern】

今天我要说说我自己,梦想中的我自己,我身价过亿,有两个大公司,一个是房地产公司,一个是服装制造业,这两个公司都很赚钱,天天帮我在累加财富,其实是什么公司我倒是不关心,我关心的是是不是在赚钱,赚了多少,我先用类图表示一下我这两个公司: 类图很简单,声明了一个 Corp 抽象类,定义一个公司的抽象模型,公司首先要是赚钱的,不赚钱谁开公司,做义务或善举那也是有背后利益支撑的,我还是赞这句话“天下熙熙,皆为利来:天下壤壤,皆为利往”,那我们先看 Corp 类的源代码: 1 package com.pat

说说设计模式~桥梁模式(Bridge)

返回目录 在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用Bridge模式. 意图 [GOF95]在提出桥梁模式的时候指出,桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化".这句话有三个关键词,也就是抽象化.实现化和脱耦. 桥梁模式的成员 抽象化 存在于多个实体中

JAVA设计模式之桥梁模式

在阎宏博士的<JAVA与模式>一书中开头是这样描述桥梁(Bridge)模式的: 桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式.桥梁模式的用意是“将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化”. 桥梁模式的用意 桥梁模式虽然不是一个使用频率很高的模式,但是熟悉这个模式对于理解面向对象的设计原则,包括“开-闭”原则以及组合/聚合复用原则都很有帮助.理解好这两个原则,有助于形成正确

我的设计模式:工厂模式和桥梁模式

1.简单工厂模式  Factory Method Pattern 能生产某类(接口管理)东东,可以指定特定的类 延迟初始化:     使用全局变量Map减少类的初始化过程 获取接口下的全部实现类:ClassUtils 2.抽象工厂模式  Abstract Factory Patter 问题:人类中分男和女   人类的喜怒哀乐情绪 产品等级和产品族 工厂(接口   抽象类(共性)   实现类(个性))       人类(接口   抽象类(共性)  实现类(个性)) 3.桥梁模式  Bridge P

设计模式 桥梁模式 JDBC

桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式.桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化". 桥梁模式的用意 桥梁模式虽然不是一个使用频率很高的模式,但是熟悉这个模式对于理解面向对象的设计原则,包括"开-闭"原则以及组合/聚合复用原则都很有帮助.理解好这两个原则,有助于形成正确的设计思想和培养良好的设计风格. 桥梁模式的用意是&

桥梁模式(Bridge)

桥梁模式属于结构类的设计模式,示意结构图如下: 桥梁模式所涉及的角色有: ● 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用. ● 修正抽象化(RefinedAbstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义. ● 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现.必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样.实现化角色应当只给出底层操作,而抽象化角色应

第 11 章 桥梁模式【Bridge Pattern】

以下内容出自:<<24种设计模式介绍与6大设计原则>> 今天我要说说我自己,梦想中的我自己,我身价过亿,有两个大公司,一个是房地产公司,一个是服装制造业,这两个公司都很赚钱,天天帮我在累加财富,其实是什么公司我倒是不关心,我关心的是是不是在赚钱,赚了多少,这才是我关心的,我是商人呀,唯利是图是我的本性,偷税漏税是我的方法,欺上瞒下.压榨员工血汗 我是的手段嘛,我先用类图表示一下我这两个公司: 类图很简单,声明了一个Corp 抽象类,定义一个公司的抽象模型,公司首要是赚钱的,不赚钱谁