DTO与PB使用小结Word文档格式.docx

上传人:b****3 文档编号:16413438 上传时间:2022-11-23 格式:DOCX 页数:19 大小:170.46KB
下载 相关 举报
DTO与PB使用小结Word文档格式.docx_第1页
第1页 / 共19页
DTO与PB使用小结Word文档格式.docx_第2页
第2页 / 共19页
DTO与PB使用小结Word文档格式.docx_第3页
第3页 / 共19页
DTO与PB使用小结Word文档格式.docx_第4页
第4页 / 共19页
DTO与PB使用小结Word文档格式.docx_第5页
第5页 / 共19页
点击查看更多>>
下载资源
资源描述

DTO与PB使用小结Word文档格式.docx

《DTO与PB使用小结Word文档格式.docx》由会员分享,可在线阅读,更多相关《DTO与PB使用小结Word文档格式.docx(19页珍藏版)》请在冰豆网上搜索。

DTO与PB使用小结Word文档格式.docx

2.易于实现快速开发。

通过使用域DTO可以直接将域模型在层间传输,减少了工作量,可以快速地构建出一个应用;

但是他也有其缺点:

1.将客户端和服务器端域对象耦合在一起。

如果域模型变了,那么相应的DTO也会改变,即使对于Hibernate这种PO、DTO一体的系统来说也会同样导致客户端的代码要重新编译或者修改。

2.不能很好地满足客户端的要求。

客户端可能只需要域对象的20个属性中的一两个,采用域DTO则会将20个属性都传递到客户端,浪费了网络资源。

3.更新域对象很烦琐。

客户端对DTO可能做了很多更新或者很深层次的更新,要探查这些更新然后更新域对象是很麻烦的事情。

域DTO解决了在客户端和服务器端之间传递大量数据的问题,但是客户端往往需要更细粒度的数据访问。

可定制的DTO,使它仅封装客户端需要的数据的任意组合,完全与服务器端的域模型相分离。

定制DTO与域DTO的区别就是它不映射到任何服务器端的域模型。

定制DTO主要用于只读操作,也就是DTO只能用来显示,而不能接受改变。

既然定制DTO对象仅仅是一个数据的集合,和任何服务端对象没有必然的关系,那么对定制DTO进行更新就是没有意义的了。

定制DTO的缺点如下:

1.需要创建大量的DTO。

使用定制DTO会爆炸式地产生大量的对象。

2.客户端DTO的版本必须和服务器端的版本一致。

由于客户端和服务器端都通过定制DTO通信,所以一旦服务器端的DTO增加了字段,那么客户端的代码也必须重新编译,否则会产生类版本不一致的问题。

传统数据访问模式和DTO模式的对比:

传统数据传输,客户端对一个远程接口发出了多个调用,而这些调用所增加的响应时间超出了可接受的程度。

显然这种模式减少了远程调用次数,因调用次数的减少也优化了性能,另外使用DTO还隐藏了内部情况,而且在某些情况下有助于发现其业务含义。

大多数情况下,DTO内的数据来自多个域对象。

因为DTO没有行为,因此它不能从域对象提取数据。

这是对的,因为如果让DTO不知道域对象,您就可以在不同的上下文中重用DTO。

同样,您不希望域对象知道DTO,因为这可能意味着更改DTO将要求更改域逻辑中的代码,这将导致大量维护任务。

使用分析:

在我们设计使用中,DTO本身是一组对象或是数据的容器,它需要跨不同的进程或是网络的边界来传输数据。

这类对象本身应该不包含具体的业务逻辑,并且通常这些对象内部只能进行一些诸如内部一致性检查和基本验证之类的方法,而且这些方法最好不要再调用其它的对象行为。

1.使用编程语言内置的集合对象,它通常只需要一个类,就可以在整个应用程序中满足任何数据传输目的;

而且因为几乎所有的编程语言都内置了集合类型,我们不需要再另外编写这个类的实现代码。

同时,使用内置的集合对象来实现DTO对象的时候,客户端必须按位置序号(在简单数组的情况下)或元素名称(在键控集合的情况下)访问集合内的字段。

此外,集合存储的是同一类型(通常是最基本的Object类型)的对象,这有时会导致在编译时碰到一些无法检测到编码错误,这是采用内置集合对象来实现DTO对象的缺点。

2.通过创建自定义类来实现DTO对象,通过定义显式的get或是set方法来访问数据。

这种方式的主要优点在于能够提供与任何其他对象完全一样的、客户端应用程序可访问的强类型对象,这种对象可以提供编译时的类型检查;

但是主要缺点在于增加了编码的工作量,因为如果应用程序发出许多远程调用的话,我们需要自己编写大量的调用代码。

具体实现中有许多方法试图将上述这两种方法的优点结合在一起。

第一种方法是代码生成技术,该技术可以生成脱离现有元数据(如可扩展标记语言(XML)架构)的自定义DTO类的源代码;

第二种方法是提供更强大的集合,尽管它也是平台内置的一般的集合,但它将关系和数据类型信息与原始数据存储在一起,比如IBM提出的SDO技术或是微软ADO.NET中的DataSet就支持这类方法。

PB的源码结构大致如下:

  PB源码

  {

  PB基础库

  Message抽象层----相当于一个类

  Descriptor抽象层-----相当于属性,定义各类数据类型

  IO子系统

  }

  PB编译器-----生成源码

}

IO子系统最为简单,不依赖于系统其他部分,实现统一的输入输出接口。

  Message抽象层主要由一个抽象的Message类和从Message类里面单独分离出来的Reflection类构成.

  GeneratedMessageReflection是Reflection的派生类,实现了抽象的Message方法。

这里的关键在于

  一个静态的offsets数组,里面存储了任何Message派生类对象里面各个字段相对于对象本身的偏移量。

  利用这个偏移量,GeneratedMessageReflection可以在不知道确切字段名字和类型的情况下实现

  Reflection里面定义的Message方法。

  Descriptor抽象层是系统的核心。

由平行的两组解释器构成。

一组是一系列递归下降的Descriptor类群,另一组是一系列递归下降的DescriptorProto类群。

Descriptor类群描述的是抽象的任意的消息。

  DescriptorProto类型是对消息格式本身进行描述的消息。

其中FileDescriptorProto类群描述了.proto文件的结构,任何.proto文件都可以映射到一个FileDescriptorProto对象上。

FileDescriptorProto对象和FileDescriptor之间可以相互转换。

  PB编译器实际上是一组递归下降的CodeGenerator类群。

Generator的输入是Descriptor类群,输出是具体的派生消息类。

  整个编译器的运作过程如下:

  1.从.proto文件生成FileDescriptorProto对象

  2.从FileDescriptorProto对象生成FileDescriptor对象

3.odeGenerator从FileDescriptor对象生成代码。

以下,以下使用Protobuf和C++开发一个十分简单的例子程序为例。

该程序由两部分组成。

第一部分被称为Writer,第二部分叫做Reader。

Writer负责将一些结构化的数据写入一个磁盘文件,Reader则负责从该磁盘文件中读取结构化数据并打印到屏幕上。

准备用于演示的结构化数据是HelloWorld,它包含两个基本数据:

ID,为一个整数类型的数据;

Str,这是一个字符串。

a.书写.proto文件

首先我们需要编写一个proto文件,定义我们程序中需要处理的结构化数据,在protobuf的术语中,结构化数据被称为Message。

proto文件非常类似java或者C语言的数据定义。

代码清单1显示了例子应用中的proto文件内容。

messagehelloworld{

requiredint32id=1;

//ID

requiredstringstr=2;

//str

optionalint32opt=3;

//optionalfield

一个比较好的习惯是认真对待proto文件的文件名。

比如将命名规则定于如下:

packageName.MessageName.proto 

在上例中,定义了一个消息helloworld,该消息有三个成员,类型为int32的id,另一个为类型为string的成员str。

option是一个可选的成员,即消息中可以不包含该成员。

(required是必须有的成员,repeated定义了一种数组类型数据)

b.编译.proto文件

写好proto文件之后就可以用Protobuf编译器将该文件编译成目标语言了。

本例中我们将使用C++。

假设您的proto文件存放在$SRC_DIR下面,您也想把生成的文件放在同一个目录下,则可以使用如下命令:

protoc-I=$SRC_DIR--cpp_out=$DST_DIR$SRC_DIR/addressbook.proto 

命令将生成两个文件:

lm.helloworld.pb.h,定义了C++类的头文件

lm.helloworld.pb.cc,C++类的实现文件

在生成的头文件中,定义了一个C++类helloworld,后面的Writer和Reader将使用这个类来对消息进行操作。

诸如对消息的成员进行赋值,将消息序列化等等都有相应的方法。

c.编写writer和Reader

如前所述,Writer将把一个结构化数据写入磁盘,以便其他人来读取。

假如我们不使用Protobuf,其实也有许多的选择。

一个可能的方法是将数据转换为字符串,然后将字符串写入磁盘。

转换为字符串的方法可以使用sprintf(),这非常简单。

数字123可以变成字符串”123”。

这样做似乎没有什么不妥,但是仔细考虑一下就会发现,这样的做法对写Reader的那个人的要求比较高,Reader的作者必须了Writer的细节。

比如”123”可以是单个数字123,但也可以是三个数字1,2和3,等等。

这么说来,我们还必须让Writer定义一种分隔符一样的字符,以便Reader可以正确读取。

但分隔符也许还会引起其他的什么问题。

最后我们发现一个简单的Helloworld也需要写许多处理消息格式的代码。

如果使用Protobuf,那么这些细节就可以不需要应用程序来考虑了。

使用Protobuf,Writer的工作很简单,需要处理的结构化数据由.proto文件描述,经过上一节中的编译过程后,该数据化结构对应了一个C++的类,并定义在lm.helloworld.pb.h中。

对于本例,类名为lm:

:

helloworld。

Writer需要include该头文件,然后便可以使用这个类了。

现在,在Writer代码中,将要存入磁盘的结构化数据由一个lm:

helloworld类的对象表示,它提供了一系列的get/set函数用来修改和读取结构化数据中的数据成员,或者叫field。

当我们需要将该结构化数据保存到磁盘上时,类lm:

helloworld已经提供相应的方法来把一个复杂的数据变成一个字节序列,我们可以将这个字节序列写入磁盘。

对于想要读取这个数据的程序来说,也只需要使用类lm:

helloworld的相应反序列化方法来将这个字节序列重新转换会结构化数据。

这同我们开始时那个“123”的想法类似,不过Protobuf想的远远比我们那个粗糙的字符串转换要全面,因此,我们不如放心将这类事情交给Protobuf吧。

Writer的主要代码

#include"

lm.helloworld.pb.h"

…intmain(void){

lm:

helloworldmsg1;

msg1.set_id(101);

msg1.set_str(“hello”);

//Writethenewaddressbookbacktodisk.fstreamoutput("

./log"

ios:

out|ios:

trunc|ios:

binary);

if(!

msg1.SerializeToOstream(&

output)){

cerr<

<

"

Failedtowritemsg."

<

endl;

return-1;

return0;

Msg1是一个helloworld类的对象,set_id()用来设置id的值。

SerializeToOstream将对象序列化后写入一个fstream流。

Reader

…voidListMsg(

constlm:

helloworld&

msg){

cout<

msg.id()<

msg.str()<

}intmain(intargc,char*argv[]){

lm:

{fstreaminput("

in|ios:

if(!

msg1.ParseFromIstream(&

input)){

cerr<

Failedtoparseaddressbook."

}

}ListMsg(msg1);

同样,Reader声明类helloworld的对象msg1,然后利用ParseFromIstream从一个fstream流中读取信息并反序列化。

此后,ListMsg中采用get方法读取消息的内部信息,并进行打印输出操作。

运行结果

运行Writer和Reader的结果如下:

>

writer>

reader101Hello 

Reader读取文件log中的序列化信息并打印到屏幕上。

本文中所有的例子代码都可以在附件中下载。

您可以亲身体验一下。

这个例子本身并无意义,但只要您稍加修改就可以将它变成更加有用的程序。

比如将磁盘替换为网络socket,那么就可以实现基于网络的数据交换任务。

而存储和交换正是Protobuf最有效的应用领域。

和其他类似技术的比较看完这个简单的例子之后,希望您已经能理解Protobuf能做什么了,那么您可能会说,世上还有很多其他的类似技术啊,比如XML,JSON,Thrift等等。

和他们相比,Protobuf有什么不同呢?

简单说来Protobuf的主要优点就是:

简单,快。

这有测试为证,项目thrift-protobuf-compare比较了这些类似的技术,图1显示了该项目的一项测试结果,TotalTime.

图1.性能测试结果

 

TotalTime指一个对象操作的整个时间,包括创建对象,将对象序列化为内存中的字节序列,然后再反序列化的整个过程。

从测试结果可以看到Protobuf的成绩很好。

c.Protobuf的优点

Protobuf有如XML,不过它更小、更快、也更简单。

你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。

你甚至可以在无需重新部署程序的情况下更新数据结构。

只需使用Protobuf对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。

它有一个非常棒的特性,即“向后”兼容性好,人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。

这样您的程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题。

因为添加新的消息中的field并不会引起已经发布的程序的任何改变。

Protobuf语义更清晰,无需类似XML解析器的东西(因为Protobuf编译器会将.proto文件编译生成对应的数据访问类以对Protobuf数据进行序列化、反序列化操作)。

使用Protobuf无需学习复杂的文档对象模型,Protobuf的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf比其他的技术更加有吸引力。

d.Protobuf的不足

Protbuf与XML相比也有不足之处。

它功能简单,无法用来表示复杂的概念。

XML已经成为多种行业标准的编写工具,Protobuf只是Google公司内部使用的工具,在通用性上还差很多。

Protobuf也不适合用来对基于文本的标记文档(如HTML)建模。

另外,由于XML具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上Protobuf不行,它以二进制的方式存储,除非你有.proto定义,否则你没法直接读出Protobuf的任何内容。

高级应用话题

更复杂的Message

到这里为止,我们只给出了一个简单的没有任何用处的例子。

在实际应用中,人们往往需要定义更加复杂的Message。

我们用“复杂”这个词,不仅仅是指从个数上说有更多的fields或者更多类型的fields,而是指更加复杂的数据结构:

嵌套Message

嵌套是一个神奇的概念,一旦拥有嵌套能力,消息的表达能力就会非常强大。

嵌套Message的例子

messagePerson{

requiredstringname=1;

requiredint32id=2;

//UniqueIDnumberforthisperson.optionalstringemail=3;

enumPhoneType{MOBILE=0;

HOME=1;

WORK=2;

}

messagePhoneNumber{

requiredstringnumber=1;

optionalPhoneTypetype=2[default=HOME];

repeatedPhoneNumberphone=4;

在MessagePerson中,定义了嵌套消息PhoneNumber,并用来定义Person消息中的phone域。

这使得人们可以定义更加复杂的数据结构。

ImportMessage

在一个.proto文件中,还可以用Import关键字引入在其他.proto文件中定义的消息,这可以称做ImportMessage,或者DependencyMessage。

比如下例:

importcommon.header;

messageyouMsg{

requiredcommon.info_headerheader=1;

requiredstringyouPrivateData=2;

其中,common.info_header定义在common.header包内。

ImportMessage的用处主要在于提供了方便的代码管理机制,类似C语言中的头文件。

您可以将一些公用的Message定义在一个package中,然后在别的.proto文件中引入该package,进而使用其中的消息定义。

GoogleProtocolBuffer可以很好地支持嵌套Message和引入Message,从而让定义复杂的数据结构的工作变得非常轻松愉快。

动态编译

一般情况下,使用Protobuf的人们都会先写好.proto文件,再用Protobuf编译器生成目标语言所需要的源代码文件。

将这些生成的代码和应用程序一起编译。

可是在某且情况下,人们无法预先知道.proto文件,他们需要动态处理一些未知的.proto文件。

比如一个通用的消息转发中间件,它不可能预知需要处理怎样的消息。

这需要动态编译.proto文件,并使用其中的Message。

Protobuf提供了 

google:

protobuf:

compiler包来完成动态编译的功能。

主要的类叫做importer,定义在importer.h中。

使用Importer非常简单,下图展示了与Import和其它几个重要的类的关系。

图2.Importer类

Import类对象中包含三个主要的对象,分别为处理错误的MultiFileErrorCollector类,定义.proto文件源目录的SourceTree类。

下面还是通过实例说明这些类的关系和使用吧。

对于给定的proto文件,比如lm.helloworld.proto,在程序中动态编译它只需要很少的一些代码。

compiler:

MultiFileErrorCollectorerrorCollector;

DiskSourceTreesourceTree;

google:

Importerimporter(&

sourceTree,&

errorCollector);

sourceTree.MapPath("

"

protosrc);

importer.import(“lm.helloworld.proto”);

首先构造一个importer对象。

构造函数需要两个入口参数,一个是sourceTree对象,该对象指定了存放.proto文件的源目录。

第二个参数是一个errorcollector对象,该对象有一个AddError方法,用来处理解析.proto文件时遇到的语法错误。

之后,需要动态编译一个.proto

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 自然科学 > 天文地理

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1