分享
 
 
 

运用设计模式设计MIME编码类

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

运用设计模式设计MIME编码类 -- 兼谈Template Method和Strategy模式的区别

作者: 温昱 (lcspace.nease.net)

下载本文示例源代码

本文讲述可重用、易扩充的MIME编码类的设计思路;并顺便对比了Template Method和Strategy模式的区别。

一、背景知识

MIME是一种Internet协议,全称为“Multipurpose Internet Mail Extensions” ,中文名称为“多用途互联网邮件扩展”。其实,它的应用并不局限于收发Internet邮件——它已经成为Internet上传输多媒体信息的基本协议之一。本文仅关心MIME的编码算法。

MIME编码的原理就是把 8 bit 的内容转换成 7 bit 的形式以能正确传输,在接收方收到之后,再将其还原成 8 bit 的内容。对邮件进行编码最初的原因是因为

Internet 上的很多网关不能正确传输8 bit 内码的字符,比如汉字等。MIME编码共有Base64、Quoted-printable、7bit、8bit和Binary等几种。

Base64算法将输入的字符串或一段数据编码成只含有{''A''-''Z'', ''a''-''z'', ''0''-''9'', ''+'', ''/''}这64个字符的串,''=''用于填充。其编码的方法是,将输入数据流每次取6

bit,用此6 bit的值(0-63)作为索引去查表,输出相应字符。这样,每3个字节将编码为4个字符(3×8 → 4×6);不满4个字符的以''=''填充。

Quoted-printable算法根据输入的字符串或字节范围进行编码,若是不需编码的字符,直接输出;若需要编码,则先输出''='',后面跟着以2个字符表示的十六进制字节值。

二、设计目标

我们计划开发一套MIME编码和解码的类,适用于可以想到的多种应用场合:

· Email客户端程序

· 乱码察看程序

· 图片等二进制对象存入XML文件

设计目标如下:

· 可重用

· 易使用

· 易扩充

三、设计过程

本部分分为下面3节,注意它们不是并列的3种设计方案,而是达到趋于合理的设计的思考过程:

· 设计成仅提供方法的Utility

· 设计成使用Template Method模式的String Class

· 设计成使用Strategy模式的String Class

1、设计成仅提供方法的Utility

首先跳进我脑子的想法就是设计成Utility(仅仅提供方法的类),我想可能是我受C影响太大的缘故吧。

它的接口会是什么样子呢?差不多象bool UMime::Encode(unsigned char * outTargetBuf,

int & outTargetBufLen,

const unsigned char * const inSourceBuf,

int inSourceBufLen);

bool UMime::Decode(unsigned char * outTargetBuf,

int & outTargetBufLen,

const unsigned char * const inSourceBuf,

int inSourceBufLen);吧。不行,为了满足易使用要求,应该支持CString类型的buffer吧,再增加2个接口函数

bool UMime::Encode(CString & outTargetStr,CString & inSourceStr);

bool UMime::Decode(CString & outTargetStr,CString & inSourceStr);

这样以来,UMime一共包括4个接口函数。 好像还不错?高兴得太早了。因为将来应用中很可能出现CString和unsigned

char *协同工作的情形。比如应用从XML文件中读出一个字符串放到一个CString型变量中,而这个字符串是一个Bmp图片的MIME编码,它解码过后自然应放到unsigned

char *的buffer中。所以我们还要增加下面4个接口函数:bool UMime::Encode(CString & outTargetStr,

const unsigned char * const inSourceBuf,

int inSourceBufLen);

bool UMime::Decode(CString & outTargetStr,

const unsigned char * const inSourceBuf,

int inSourceBufLen);

bool UMime::Encode(unsigned char * outTargetBuf,

int & outTargetBufLen,

CString & inSourceStr);

bool UMime::Decode(unsigned char * outTargetBuf,

int & outTargetBufLen,

CString & inSourceStr);

以免用户类型转换之苦。

啊哈,这么8个极为相似的接口函数搅在一起,好像一团麻呀。可重用性似乎满足了,但易使用性和易扩展性完全谈不上。

2、设计成使用Template Method模式的String Class

第2种方案浮现在脑海中:

· 既然整个算法就是将一个Buffer转换成另一个Buffer,写成一个String Class是非常自然的设计

· 用Class的成员变量保存Target Buffer及其长度(因为Buffer中可能有’\0’),另外提供GetBuf()和GetBufLen()作为查询Target

Buffer的接口

· 直接从构造函数传递Source Buffer的信息

该类大概象这样:class CMimeString

{

public:

enum PROCESSTYPE

{

ENCODING = 0,

DECODING = 1

};

CMimeString(PROCESSTYPE inType, const unsigned char * const inBuf, int inBufLen);

CMimeString(PROCESSTYPE inType, CString & inStr);

virtual ~ CMimeString();

unsigned char * GetBuf( void );

int GetBufLen( void );

operator LPCTSTR() const;

};

哈,似乎很美妙。

· Source Buffer仍然支持unsigned char *和CString这 2种类型,而Target Buffer由CMimeString本身来管理不必用户操心了。

· 但具体应用不是对二进制对象进行编码时,可以不用foo( s.GetBuf() )而直接用foo( s ),因为operator LPCTSTR()

const;自动负责类型转换。

· 直接从构造函数传递Source Buffer的信息,使得接口更为精简。

当具体使用CMimeString时,大概象这样:CString buf("sadfsdfsdf");

CMimeString mime(CMimeString::ENCODING, buf);

MessageBox( s );

看来易使用性不错,下面要着重解决易扩展性了。CMimeString的实现部分会象这样:

class CMimeString

{

protected:

unsigned char * mBuf;

int mBufLen;

virtual void Encode( unsigned char * inBuf, int inBufLen );

virtual void Decode( unsigned char * inBuf, int inBufLen );

};其中的两个虚函数是专门为易扩展性准备的,要实现新的MIME编码算法,只需要从CMimeString继承一个子类:

class CBase64String : public CMimeString

{

protected:

virtual void Encode( unsigned char * inBuf, int inBufLen );

virtual void Decode( unsigned char * inBuf, int inBufLen );

};

类图如下:

这两个虚函数是在哪里被调用的呢?在基类的构造函数中。CMimeString::CMimeString(WHICHTYPE inType, CString & inStr)

{

mBuf = 0;

mBufLen = 0;

if( inType == ENCODING )

{

Encode((unsigned char *)(inStr.operator LPCTSTR()), inStr.GetLength());

}

else if( inType == DECODING )

{

Decode((unsigned char *)(inStr.operator LPCTSTR()), inStr.GetLength());

}

}

看上去是很不错的Template Method模式的运用,但是有问题——因为“在构造函数中调用虚函数”并无多态特性!

CBase64String::CBase64String(PROCESSTYPE inType, CString inStr)

{

OnlyInitSelf();

}

之后CString buf("sadfsdfsdf");

CBase64String base64(CMimeString::ENCODING, buf);

MessageBox( base64 );

是不对的,仍然是基类的CMimeString::Encode()被调用了,而且OnlyInitSelf()在Encode()被调用之后才被调到。

是不是有些懊恼?别急。分析问题背后的问题:我们实际上是想用Template Method模式,而且是让构造函数扮演Template Method的角色,而它先天(C++本身决定的)就不是这块料。

现在,摆在面前的至少有2条道路。第1种方法是,坚持使用Template Method模式,但要增加一个接口函数扮演Template Method角色。这样一来,我们使用CMimeString时就不如“直接从构造函数传递参数”方便。第2种方法是,坚持直接从构造函数传递参数,放弃Template

Method模式,改用其它模式完成“改变算法”的职责。我决定采用第2种方法。

3、设计成使用Strategy模式的String Class

除了Template Method模式以为,Strategy模式也可以履行“改变算法”的职责,我们就用Strategy模式代替Template

Method模式继续完成CMimeString的设计,类图如下:

新的CMimeString的类声明如下:

class CMimeString

{

public:

enum PROCESSTYPE

{

ENCODING = 0,

DECODING = 1

};

enum ENCODETYPE

{

WYMIME = 0,

BASE64 = 1

};

CMimeString(PROCESSTYPE inType, ENCODETYPE inAlgoType, CString & inStr);

CMimeString(PROCESSTYPE inType, ENCODETYPE inAlgoType,

unsigned char * inBuf, int inBufLen);

virtual ~CMimeString();

int GetBufLen(void);

unsigned char * GetBuf(void);

operator LPCTSTR() const;

};

CMimeAlgo的类声明如下:

class CMimeAlgo

{

public:

CMimeAlgo();

~CMimeAlgo();

virtual void Encode( unsigned char ** outBuf, int & outBufLen,

unsigned char * inSrcBuf, int inSrcLen );

virtual void Decode( unsigned char ** outBuf, int & outBufLen,

unsigned char * inSrcBuf, int inSrcLen );

};

CBase64Algo的类声明如下:

class CBase64Algo : public CMimeAlgo

{

public:

CBase64Algo();

~CBase64Algo();

virtual void Encode( unsigned char ** outBuf, int & outBufLen,

unsigned char * inSrcBuf, int inSrcLen );

virtual void Decode( unsigned char ** outBuf, int & outBufLen,

unsigned char * inSrcBuf, int inSrcLen );

};

具体使用Base64算法是会象这样:

CString buf("sdfsdsdfsdfsdf");

CMimeString base64( CMimeString::ENCODING, CMimeString::BASE64, buf );

MessageBox(base64);

哈哈,基本满意。

四、使用举例

下面编一个小程序,重在演示CMimeString的用法。有2点需要说明:

· 程序比较简单,仅支持Base64编码和解码;

· 而且对一个串进行解码时并没有检查它是否是合法的Base64编码的结果串(有些字符串是不可能成为Base64编码的结果的),因此对串someString解码后再编码得到的串anotherString可能和someString并不相同。

五、Template

Method和Strategy模式的区别

上面的设计过程中,牵涉到Template Method和Strategy这2个设计模式,本部分对它们简要总结和对比。

1、Template Method模式Tips

·Tip 1:关键字:Skeleton。

·Tip 2:图:

·Tip 3:支持变化。Subclass可以只改变算法的特定步骤,而不改变和继续使用算法的Skeleton。图中黄色的Class就是后来写的,而且工作量很小,只需Override相应的Virtual函数。其中的ConcreteClass3的改动量更小,它从已有的ConcreteClass1继承,只Override其中的一个Virtual函数。

Template Method可以说是最常见的模式,在MFC中,全局函数AfxWndProc()就是一例。

·Tip 4:支持框架。著名的Framework方面的“好莱坞法则”(Don''t call us, we''ll call you )就是主要由Template

Method支持的“反向控制”(Superclass调用Subclass的Method)产生的。

2、Strategy模式Tips

·Tip 1:关键字。Aalgorithm Family。

·Tip 2:图:

可以看到,为了达到“将Aalgorithm从Data分离出来”的目的,代价是Context和Strategy 2 个对象。

·Tip 3:实现和使用。

实例化问题。从图中可以看到,Context和ConcreteStrategy的实例化,都将由“Application工程师”负责。

case语句。“Application工程师”不写case语句了,改“Architecture工程师”要写了。有空研究一下Borland

ObjectWindow的源码。

Borland ObjectWindow之Dialog验证用户输入合法性,用了Strategy模式:

·Tip 4:支持变化。Strategy lets the algorithm vary independently from clients

that use it。图中的黄色Class就是假想后来扩充的。

·Tip 5:局限性。

Strategy and Context之间是紧耦合。Strategy and Context interact to implement

the algorithm. A context may pass all data required by the algorithm to

the strategy when the algorithm is called. Alternatively, the context

can pass itself as an argument to Strategy operations. That lets the strategy

call back on the context as required.

Strategy对Clients不能完全透明。Clients must be aware of different Strategies。

Therefore you should use the Strategy pattern only when the variation

in behavior is relevant to clients。想想看,Client要负责ConcreteStrategy(和Context)的实例化,正是决定选哪一个ConcreteStrategy的过程,使得“Strategy对Clients不能完全透明”。

3、Template Method和Strategy模式的对比

对比如下:

· 相同点,都是行为型模式,目的都是方便地改变算法。

· 不同点,实现方式前者使用继承,称为类模式;后者使用委托,称为对象模式。

《设计模式》一书在讲到Template method模式和Strategy模式的关系时说:“模板方法使用继承来改变算法的一部分。Strategy使用委托来改变整个算法。”

“算法的一部分”和“整个算法”的区别,笔者认为“整个算法”是“算法的一部分”的特例(就象数学中全集是集合的特例),因此不是2个模式的根本区别。

“继承”和“委托”的区别,即“类模式”和“对象模式”的区别,笔者认为这是2个模式的根本区别。

顺便说明,《设计模式》一书中非常强调对象模式和类模式的区别,本文就提供了一个很极端的例子——用对象模式可行而用类模式不可行。

参考文献:

《MIME邮件面面观》 作者:bhw98 出处:www.csdn.net

《设计模式》 作者:Gamma等 译者:李英军等

《Pattern Tips》 作者:温昱 出处:lcspace.nease.net

作者信息:

姓名:温昱

邮箱:xinxiu123@sina.com

网站:lcspace.nease.net

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有