[转载]让ajax更加友好,实时显示后台处理进度。

mikel阅读(1165)

[转载]让ajax更加友好,实时显示后台处理进度。 – BearRui(AK-47) – 博客园.

ajax应用越来越多,大部分ajax处理都是在前台显示1个”loading…”,然后把数据提交给服务器进行处理,处理完毕后显示”处理完 毕”。我们能否让ajax更加友好点,实时显示服务器处理的进度了?这在一些长时间的请求中尤其重要,比如上传文件、发送邮件、批量处理数据。答案当然是 可以的,不然就不会写这个了,对吧,^_^。

存在的问题:

要解决实现上面的功能,需要解决下面几个问题:

1. 服务器如何在处理一部分数据后传递部分response到浏览器。

2、浏览器如何能处理服务器传递过来部分数据,并保持http连接直到处理完全完毕。

要解决第1个问题,使用flush让response分块进行呈现就可以了,具体请参考我另一遍随笔”flush让页面分块,逐步呈现“;

第2个问题,则需要用到XMLHttpRequest的readyState状态,w3c对 readyState 定 义如下几个值:

UNSENT = 0; // 没有发送请求

OPENED = 1;    // 已经打开http连接

HEADERS_RECEIVED = 2; // 接收到response header

LOADING = 3;          // 真正接收response body

DONE = 4;             // 请求接收完毕

相信状态4大家是天天在用,而我们这里需要用到就是状态3。

实例:

废话少说,代码实例比什么文字解释都管用。我们这里假设服务器的1个处理需要6秒种,每秒种处理1条记录,总共处理6条记录,我们需要服务器每处理完1条 数据,客户端则显示处理进度(包括文字和进度条)。

服务器端代码(下面JSP代码):

01 <%
02 // 下面设置Content-Type:application/x-JavaScript 是为了适应Webkit的浏览器(chrome,safari)
03 response.setHeader("Content-Type","application/x-JavaScript");
04 int count = 6; //  处理6条数据
05 for(int i=0;i<count;i++){
06 // 处理完毕一条,输出结果到客户端
07 out.println(i+1);
08 out.flush();
09 // 这里假设每条数据处理时间为1秒
10 Thread.currentThread().sleep(1000);
11 }
12 %>

html代码:

01 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
02 <html xmlns="http://www.w3.org/1999/xhtml">
03 <head>
04 <style>
05 #divProgress{width:300px;height:24px;position:relative;}
06 #divProgress div{position:absolute;left:0;top:0;height:24px;}
07 #progressBg{background-color:#B9F8F9;z-index:10;}
08 #progressText{z-index:15;text-align:center;width:100%;}
09 </style>
10 </head>
11 <body>
12 <div id="divProgress">
13 <div id="progressBg"></div>
14 <div id="progressText"></div>
15 </div>
16 <br />
17 <button onclick="send()">提交数据</button>
18 <script>
19 var t = document.getElementById("progressText");
20 var bg = document.getElementById("progressBg");
21 function send(){
22 t.innerHTML = "loading...";
23 bg.style.width = "0px";
24
25 var xhr = new window.XMLHttpRequest();
26 if(!window.XMLHttpRequest){
27 try {
28 xhr = new window.ActiveXObject("Microsoft.XMLHTTP");
29 } catch(e) {}
30 }
32 var oldSize=0;
33 xhr.onreadystatechange = function(){
34 if(xhr.readyState > 2){
35 var tmpText = xhr.responseText.substring(oldSize);
36 oldSize = xhr.responseText.length;
37 if(tmpText.length > 0 ){
38 // 设置文本
39 t.innerHTML = tmpText + "/6";
40 // 设置进度条
41 var width = parseInt(tmpText)/6*300;
42 bg.style.width = width+"px";
43 }
44 }
45 if(xhr.readyState == 4){
46 // 请求执行完毕
47 t.innerHTML = "执行完毕";
48 bg.style.width = "300px";
49 }
50 }
51 xhr.send(null);
52 }
53 </script>
54 </body>
55 </html>

运行效果图:

缺点:

看到这里或许你已经蠢蠢欲动,想自己动手试试了。但是注意上面的方法虽好,但也有个缺点,就是浏览器的支持问题。目前IE所有版本的浏览器都不支持 xhr.readyState == 3状态,IE浏览器不支持在response响应完毕前读取responseText属性。  具体可查看MSDN :  XMLHttpRequest Object

基于Webkit的浏览器支持的不是很好,需要设置Content-Type:application/x-javascript才行(经测试发现 Content-Type:text/html在有些情况下正常,有些情况下又不正常,而用application/x-javascript都正常)。

看到了缺点后是否又打击了你的积极性了,其实针对IE,我们不需要做太多处理,IE不支持,就不会显示进度,就变成跟传统的ajax请求一样,一直显示1 个loading直到请求完毕。我们只需要加1个简单的判断,判断如果是ie则不执行xhr.readyState > 2中的代码,如果不加判断,IE下会报JS错误.

DEMO:

demo服务器不太好,而且在国外,随时可能会点击不了,而且有时候运行效果不是很好,大家知晓下,最好是把代码copy到本地进行测试.

请使用firefox或chrome查看demo,ie查看的效果跟一般的ajax没什么不一样.

http://213.186.44.204:8080/ChunkTest/index.html

[作者]:BearRui(AK-47)
[博客]: http://www.cnblogs.com/BearsTaR/
[声明]:本博所有文章版权归作者所有(除特殊说明以外),转载请注明出处.

[转载]名站技术分析 - 浅谈tudou.com首页图片延迟加载的效果

mikel阅读(1456)

[转载]名站技术分析 – 浅谈tudou.com首页图片延迟加载的效果 – BearRui(AK-47) – 博客园.

经常上tudou网,发现tudou首页加载图片的功能很有意思,tudou首页从”娱乐”这个板块往下的所有视频的缩略图并不是在页面打开后就加载 的,而是当用户拖动滚动条到了”娱乐”这个板块,才开始加载图片的。这样做的好处当然是如果有用户不需要查看下面的内容,则免去了下面所有图片的请求,这 对减少服务器的压力还是很有帮助的。

实现:

其实tudou的实现原理很简单,

1.先把所有需要延迟加载的图片的src都设置成同1个小图片的连接(sprite.gif),把真真图片的连接放进图片的alt属性中,look下代 码:

<a class=”inner” target=”new” title=”史上最重街舞选手和最柔软街舞选手” href=”http://www.tudou.com/programs/view/Utmt1_6Z-lU/”>

<img width=”120″ height=”90″ class=”pack_clipImg lazyImg” alt=”http://i01.img.tudou.com/data/imgs/i/051/720/095/p.jpg” src=”http://css.tudouui.com/skin/__g/img/sprite.gif” coords=”_DAA”/>

</a>

2. 绑定window.scroll事件,在该事件里面的重设所有class为lazyImg的图片的src值,在土豆首页找到如下JS:

var o=function(){

var s=TUI.pos.scrollTop(),q=c;

if(q.box[0]){

var r=q.box.offset().top;

if(r-s>0&&r-TUI.pos.windowHeight()<s){

q.init()

}else{

q.stop()

}

}

if(!h||s<590){return true}

TUI.widget.quickPlaylist.load();

h=false

};

o();

$(window).bind(“scroll”,o);

我没有去跟入查看TUI.widget.quickPlaylist.load()方法的实现,tudou的JS都是压缩混淆的,看起来挺累,不过大家知 道原理就可以了。

实例:

上面说了那么多,最后还是来个实例比较实际点,毕竟眼见为实嘛。

01 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
02 <html xmlns="http://www.w3.org/1999/xhtml">
03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
06 </head>
07 <body>
08 能 看的见到图片:<img src="http://at-img4.tdimg.com/board/2010/5/tylc-115X55.jpg"/>
09
10 <div id="lazyBox" style="margin-top:100px;">
11 一开始看不到的图片:
12 <img width="120" height="90" style="border:1px solid blue;" class="lazyImg" alt="http://i01.img.tudou.com/data/imgs/i/051/720/095/p.jpg" src="http://css.tudouui.com/skin/__g/img/sprite.gif" coords="_DAA"/>
13 <img width="120" height="90" style="border:1px solid blue;" class="lazyImg" alt="http://i01.img.tudou.com/data/imgs/i/051/871/396/m20.jpg" src="http://css.tudouui.com/skin/__g/img/sprite.gif" coords="_DBA"/>
14 </div>
15 <div style="height:1000px;">
16
17 </div>
18 <script type="text/JavaScript">
19 var hasShow = false;
20 $(window).bind("scroll",function(){
21 if(hasShow==true){
22 $(window).unbind("scroll");
23 return;
24 }
25 var t = $(document).scrollTop();
26 if(t>50){
27 // 滚动高度超过50,加载图片
28 hasShow = true;
29 $("#lazyBox .lazyImg").each(function(){
30 $(this).attr("src",$(this).attr("alt"));
31 });
32 }
33 });
34 </script>
35 </body>
36 </html>

把上面代码copy到本地运行下就可以看到效果了。

[作者]:BearRui(AK-47)
[博客]: http://www.cnblogs.com/BearsTaR/
[声明]:本博所有文章版权归作者所有(除特殊说明以外),转载请注明出处.

[转载]名站技术分析 — facebook奇特的页面加载技术

mikel阅读(1095)

[转载]名站技术分析 — facebook奇特的页面加载技术 – BearRui(AK-47) – 博客园.

没事使用代理上了下facebook,注册进入个人首页后,习惯性的查看源代码,发现了1个很有意思的现象,首页内容不少,但源代码中HTML 的代码却很少,但去多出了很多段的JavaScript代码,这些js代码都是用于动态生成html的,facebook为什么需要这样做了?出于职业习 惯,研究研究:

一、html代码。

先看看首页查看的源代码,因为源代码比较大,所以把图片压缩了下,可能看不太清楚,只需要注意图中红色是html代码,其余黑压压一片的就全部是JS代 码:

二、JS代码

看到黑压压的JS代码是不是被吓一跳,下面就截取一段JS来分析(其余段的JS都是类似的),facebook源代码中充斥了类似于下面的JS代码:

01 <script>
02 big_pipe.onPageletArrive({
03 "id":"pagelet_welcome_box","phase":1,"is_last":false,"append":false,"bootloadable":[],
04 "css":["lDRwi","eon+N"],
05 "js":["F+B8D","IdQlc"],
06 "resource_map":[],"requires":[],"provides":[],
07 "onload":["window.__UIControllerRegistry[\"c4c13a3ed2dd1e0e349b72\"] = new UIPagelet(\"c4c13a3ed2dd1e0e349b72\", \"\\\/pagelet\\\/generic.php\\\/WelcomeBoxPagelet\\\/\", {}, {});; ;"],
08 "onafterload":[],"onpagecache":[],"onafterpagecache":[],"refresh_pagelets":[],"invalidate_cache":[],
09 "content":{
10 "pagelet_welcome_box":"<div id=\"c4c13a3ed2dd1e0e349b72\"><div class=\"UIImageBlock clearfix fbxWelcomeBox\"> ...这里省略N多HTML"
11 },
12 "page_cache":true
13 });
14 </script>

让我们再看看big_pipe.onPageletArrive函数到底做了什么了?我们只关注参数中的id,js,css,content4个参数,可 以看出js和css都是进行过编码,下面是解码后我们关注的代码:

01 <script>
02 big_pipe.onPageletArrive({
03 "id":"pagelet_welcome_box",
04 "css":{
05 name: "css/c5mv8gd5gwoc4kk0.pkg.css"
06 permanent: true
08 type: "css"
09 },
10 "js":{
11 name: "js/19khsprwvtvokwow.pkg.js"
12 permanent: false
14 type: "js"
15 },
16 "content":{
17 "pagelet_welcome_box":"<div id=\"c4c13a3ed2dd1e0e349b72\"><div class=\"UIImageBlock clearfix fbxWelcomeBox\"> ...这里省略N多HTML"
18 }
19 });
20 </script>

看到还原后的JS,你应该猜出onPageletArrive函数是干嘛的吧,其实onPageletArrive最主要实现就是把”content”中 的html内容插入到对应id(上面的”pagelet_welcome_box”)的html元素中,并下载对应的css和JS。

三、chunk、flush

看到上面的分析后,大家一定奇怪,facebook为什么要生成那么多段JS,再用js去动态插入html代码,这不是脱了裤子放屁,多此一举吗?还不 如直接生成html代码了。facebook当然不会那么笨了,让我们先监控下facebook的http请求,监控图如下:

注意上图中红色部分,原来facebook使用了chunk对页面进行分块输出。这就比较容易理解了,facebook首页的js代码段不是1次就全部 输出的,而是一段一段进行输出的。

什么是chunk和如何使用chunk,请参考我的另1篇博文:flush让页面分块,逐步呈现

总结

facebook使用chunk技术让页面分块输出成很多JS段,这样做的好处就是服务器和客户端可以并行进行处理,不用等服务器全部处理完毕,客户端才 进行处理。

举个博客园首页的列子,博客园首页分为下面几块(“推荐博客排行”,”首页随笔列表”,”最新新闻”…),

我们一般对该http请求处理如下:

1. 浏览器发送http请求

2. 服务器处理请求(从缓存读取前50个推荐博客,从数据库读取”首页随笔列表”,从数据库读取”最新新闻”),生成首页的html代码。

3. 服务器发送html代码给客户端

4、浏览器接收到响应,处理html(下载css,js,image,执行js等等)

可以看出传统的http请求4个过程中,每个过程都必须等待前1个过程完成后才能执行,这样就存在很大的资源浪费。

facebook的对该http请求的处理如下:

1. 浏览器发送http请求

2. 服务器处理请求(从缓存读取前50个推荐博客,生成”推荐博客”的js代码段,flush输出该代码段,

服务器继续读取”首页随笔列表”,并生成输入js代码段。

服务器继续读取”最新新闻”,并生成输入js代码段。

3. 浏览器接收到js代码段,下载该代码段所需的js和css。插入html代码。

在这个处理流程中,最大的特点就是2,3是并行进行处理的,服务器处理完一部分数据就把已经处理好的数据交给浏览器进行呈现处理,自己再继续处理其他的数 据。

PS:文章看完了,有些同学可能会想,为什么不像博客园一样,前台全 部用ajax来异步读取”推荐博客” ,“最新新闻”的数据了,这样做就不会出现因为要处理太多数据而让客户端等待太久的问题了。我觉得对于facebook这种并发量巨大的网站,使用这种方 法无疑会产生太多的请求,比如facebook首页用了14个chunk,如果使用ajax,则需要多14个request请求,这将增加不少服务器的压 力吧。

相关文章: 名 站技术分析 — 浅谈tudou.com首页图片延迟加载的效果

[作者]:BearRui(AK-47)
[博客]: http://www.cnblogs.com/BearsTaR/
[声明]:本博所有文章版权归作者所有(除特殊说明以外),转载请注明出处.

[转载]web高性能开发系列随笔

mikel阅读(1043)

[转载]web高性能开发系列随笔 – BearRui(AK-47) – 博客园.

在BlogJava里写了一些关于高性能WEB开发的随笔,因为都是跟前端技术相关(html,http,js,css等),所以也贴到博客园来,吸收下 人气。
1、 HTTP服务器.

2、性能测试工具推荐

3、 图 片篇.

4、 如何加载JS,JS应该放在什么位置.

5、 为什么要减少请求数,如何减少请求数.

6、 减少请求,响应的数据量.

7、JS、 CSS的合并、压缩、缓存管理

8、页 面呈现、重绘、回流。

9、该 如何加载google-analytics(或其他第三方)的JS.

10、疯 狂的HTML压缩.

11、flush 让页面分块,逐步呈现

12、了 解CSS的查找匹配原理,让CSS更简洁、高效

[作者]:BearRui(AK-47)
[博客]: http://www.cnblogs.com/BearsTaR/
[声明]:本博所有文章版权归作者所有(除特殊说明以外),转载请注明出处.

[转载]高性能WEB开发(11) - flush让页面分块,逐步呈现

mikel阅读(1092)

[转载]高性能WEB开发(11) – flush让页面分块,逐步呈现 – BearRui(AK-47) – 博客园.

在处理比较耗时的请求的时候,我们总希望先让用户先看到部分内容,让用户知道系统正在进行处理,而不是无响应。一般大家在处理这种情况,都使用 ajax,先把html输出到客户端,然后再用ajax取加载比较耗时的资源。用ajax麻烦的地方是增加了请求数,而且需要写额外的js代码、和js调 用的请求接口。

正对这种情况,还有一种处理方法,就是让response分块编码进行传输。response分块编码,可以先传输一部分不需要处理的html代码到客 户端,等其他耗时代码执行完毕后再传输另外的html代码。

分块编码(chunked encoding)

chunked encoding 是http1.1 才支持编码格式(当然目前没有哪个浏览器不支持1.1了),chunked encoding 与一般的响应区别如下:

正常的响应:

HTTP/1.1 200 OK

Cache-Control: private, max-age=60

Content-Length: 75785

Content-Type: text/html; charset=utf-8

..其他response headers

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”

chunked encoding 响应:

HTTP/1.1 200 OK

Cache-Control: private, max-age=60

Content-Length: 75785

Content-Type: text/html; charset=utf-8

Transfer-Encoding: chunked

..其他response headers

chunk #1(这里通常是16进制的数字,标志这个块的大小)

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”….

chunk #2

<div …..

chunk #3

….</body></html>

实例(JSP)

一个简单的页面,分为头部(header)和内容(部分),假设内容部分需要读取数据库,花费3秒时间,然后显示csdn的logo。header部分显 示cnblogs的logo。代码如下:

01 <body>
02 <div id="head" style="border:1px solid #ccc;">
03 cnblogs logo <img src="http://images.cnblogs.com/logo_small.gif" />
04 </div>
05 <br />
06 <div id="content" style="border:1px solid blue;">
07 <%
08 // 睡眠3秒
09 Thread.currentThread().sleep(3000);
10 %>
11 csdn logo<br />
13 </div>
14 </body>

演示地址:http://213.186.44.204:8080/ChunkTest/nochunk.jsp (服务器比较差,请大家温柔点)

打开这个演示地址发现很正常的页面,在3秒后才开始下载显示2个logo,资源加载瀑布图如下:

现在把代码改成如下,加上flush,让response把之前的html分块输出:

<div id=”head” style=”border:1px
solid #ccc;”>

01 cnblogs logo <img src="http://images.cnblogs.com/logo_small.gif" />
02 </div>
03 <%
04 out.flush(); // flush response,分块输出
05 %>
06 <br />
07 <div id="content" style="border:1px solid blue;">
08 <%
09 // 睡眠3秒
10 Thread.currentThread().sleep(3000);
11 %>
12 csdn logo<br />
14 </div>

演示地址:http://213.186.44.204:8080/ChunkTest/chunk.jsp

打开这个演示地址,是不是发现cnblogs logo先下载显示出来,3秒后csdn logo才显示,资源加载图如下:

从这个图发现,cnblogs的logo在jsp页面还没执行完就开始下载了,这就是分块输出的效果。

监控工具:

如何知道我们是否成功使用了chunk encoding了 ,只要用工具查看response header 中是否包含了Transfer-Encoding: chunked,如果包含了,则是分块了。但要想监控分块的详细信息,据我所知,目前只有httpwatch支持,可以查看我们到底分了多少块,但是数量 好像都多显示了1个,如下图:

有需要请查看:高性能WEB开发系列

[作者]:BearRui(AK-47)
[博客]: http://www.cnblogs.com/BearsTaR/
[声明]:本博所有文章版权归作者所有(除特殊说明以外),转载请注明出处.

[转载][原创].NET 业务框架开发实战之六 DAL的重构

mikel阅读(1042)

[转载][原创].NET 业务框架开发实战之六 DAL的重构 – ASP.NET 架构 – 博客园.

.NET 业务框架开发实战之六 DAL的重构
前言:其实这个系列还是之前的”.NET 分布式架构开发实战 “,之所以改了名字,主要是因为文章的标题带来了不少的歧义:系列文章中本打算开发一个简化业务发的流程的Framework,然后用这个 Framework再来实战,开发一个分布式的应用。改了名字。给大家带来了不便,敬请见谅。

本篇的议题如下:
1. 确定DAL的接口的定义。

之前在开发DAL中,提出了一些思想,也设计了一些接口。现在就把DAL的一些设计完善起来。说是“完善”,并不是说把所有的代码都实现,而是把该定 义的接口,方法敲定下来。Richard认为,设计一个架构或者Framework的时候,开始是接口的定义,定义好各层之间交互的接口,然后才是具体代 码的实现。
因为在设计Framework的时候,首先要考虑这个Framework的使用者是谁,希望他们怎么样来使用开发出来的这个 Framework。在这里,Richard很明白:Framework的使用者就是自己公司里的开发人员。而且还要使得开发的使用尽量的方便,不要到处 去配置一些文档,最好就是把Framework引入进来,稍微配一下就使用。

在Richard设计的Framework中,就DAL而言,如果希望DAL返回DataTable,DataReader等给BLL,那么需 要配置的仅仅只是指明数据库的连接字符串;如果希望DAL返回的数据实体给BLL,那么就得把一张张的表映射成为实体,然后让这些实体继承 IDataEntity接口就行了(生成实体可以用ORM工具,或者自己手写代码)。

Richard思考了之前对DAL的设计,在此他做了一些改进。
首先就是对于IDataContext的重新设计和理解:之前的设计是定义了IDataContext,然后用不同的方式实现这个接 口,如LinqDataContext.Provider就是用Linq的方法来返回结果(DataResult)。现在Richard认为 IDataContext其实就是用来操作数据库的,所以返回的结果就应该是操作数据之后的结果,如Update操作就返回受影响的行数或者是否更新成 功。至于是否要把一些额外的信息包装返回给BLL,就不是IDataContext的实现者的事情了。而且Richard还考虑到了需要在一定程度上支持 原生的ADO.NET,起码给ADO.NET预留接口。

基于此,Richard就把IDataContext定义为一个接口声明,然后再定义了IDataEntityContext,和 IDataTableContext来继承IDataContext,他们的关系图如下:

其中IDataEntityContext使用Linq和Entity Framework来实现,而IDataTableContext就是用ADO.NET的方式来实现。

IDataEntityContext接口的和系列文章中定义的一些方法差不多,但是做了修改。其中有一点要提的就是:ICriteria就是所有条 件对象要实现的接口(查询对象也是条件对象的一种)。例如,可以根据相应的条件删除,更新数据。

代码

/// <summary>
/// 所有的 数据实体执行者实现这个借口
/// </summary>
public interface IDataEntityContext:IDataContext
{
TEntity Add
<TEntity>(TEntity entity) where TEntity : IDataEntity;
List
<TEntity> Add<TEntity>(List<TEntity> entityList) where TEntity : IDataEntity;

bool Update<TEntity>(TEntity entity) where TEntity : IDataEntity;
bool Update<TEntity>(List<TEntity> entityList) where TEntity : IDataEntity;
bool Update(ICriteria condiftion, object value);

bool Delete<TEntity>(TEntity entity) where TEntity : IDataEntity;
bool Delete<TEntity>(List<TEntity> entityList) where TEntity : IDataEntity;
bool Delete(ICriteria condition);

int GetCount(ICriteria condition);
List
<TEntity> Query<TEntity>(ICriteria condition);
List
<TEntity> Query<TEntity>(ICriteria condition, int pageIndex, int pageSize, ref int entityCount) where TEntity : IDataEntity;
List
<object> Query(ICriteria condiftion);
}

另外就是多了一个 List<object> Query(ICriteria condiftion);方法,之所以有这个方法,Richard考虑到,可能开发人员想要直接自己写SQL语句去执行,如select avg(Count),sum(Name) from Customer…,开发人员可以写任意的语句,所以返回一个实体类不现实,就返回一个List<object>。

还有一点就是关于查询对象的改进:以前仅仅只是定义了查询对象的接口,现在用ICriteria 接口中定义来条件对象,而且还可以在条件对象声明是在对数据操作是否采用事务或者缓存。

代码

/// <summary>
/// 所有的 条件对象都要从这个接口继承
/// </summary>
public interface ICriteria
{
string Name { get; set; }
bool IsCache { get; set; }
bool IsTransaction { get; set; }
}

之后Richard又定义了一个IDataProvider,接口,声明如下 :

代码

/// <summary>
/// 数据提 供者要实现的借口
/// </summary>
public interface IDataProvider
{
DataResult
<TEntity> Add<TEntity>(TEntity entity) where TEntity : IDataEntity;
DataResult
<TEntity> Add<TEntity>(List<TEntity> entityList) where TEntity : IDataEntity;

DataResult<TEntity> Update<TEntity>(TEntity entity) where TEntity : IDataEntity;
DataResult
<TEntity> Update<TEntity>(List<TEntity> entityList) where TEntity : IDataEntity;
bool Update(ICriteria condiftion, object value);

DataResult<TEntity> Delete<TEntity>(TEntity entity) where TEntity : IDataEntity;
DataResult
<TEntity> Delete<TEntity>(List<TEntity> entityList) where TEntity : IDataEntity;
bool Delete(ICriteria condiftion);

int GetCount(ICriteria condition);

DataResult<TEntity> GetOne<TEntity>(ICriteria condition) where TEntity : IDataEntity;
DataResult
<TEntity> GetList<TEntity>(ICriteria condition) where TEntity : IDataEntity;
DataResult
<TEntity> GetPageData<TEntity>(ICriteria condition, int pageIndex, int pageSize, ref int entityCount) where TEntity : IDataEntity;
List
<object> GetCustomData(ICriteria condiftion);
}

之所以要定义这个接口,其实 Richard就是想让实现了IDataContext的类踏踏实实的去做底层的数据操作,至于数据操作之后的结果以什么形式给BLL,不用 IDataContext的实现者来关心,而是用IDataProvider的实现者来关心。
在IDataProvider的实现者在底层就是调用了IDataContext的实现者的方法,然后在IDataProvider 中,对外提供了一些更加友好和方便使用的方法,最后在BLL中直接依赖的就是IDataProvider,而不是IDataContext。
另外,对于IDataProvider返回的DataResult也做了一些修改:如果返回的是数据实体,即 使用的是IDataEntityContext来提供底层的数据操作,那么DataResult<TEntity>是没有问题的;但是如果使 用的是IDataTableContext,那么返回DataResult<TEntity>就不行了,因为 IDataTableContext查询方法可能返回的DataTable,或者DataReader.所以,在设计中叶预留了一个接口:让 IDataProvider返回的结果实现IDataResult接口,那么ataResult<TEntity>继承这个接口,主要用来返 回数据实体,如下:

DAL的设计就到这里,下一篇文章就开始讲述对业务层的一些思考。

版权为小洋和博客园 所有,转载请标明出处给作者。

http://www.cnblogs.com/yanyangtian

代码下载

[转载]在线视频聊天(客服)系统开发那点事儿

mikel阅读(1183)

[转载]在线视频聊天(客服)系统开发那点事儿 – 菩提树下的杨过.Net – 博客园.

07年的时候,我所在的公司有一个(在业内游戏开发方面有些名气的)Flash程序员应公司要求,利用FMS开发了一套在线视频导购系统,当时我觉 得很牛叉,用户不用安装任何插件,也不用安装什么聊天软件,就可以直接跟销售员在线交流,遇到对产品不清楚的地方,直接让销售员通过摄像头演示一下产品的 用法,沟通ok后,就直接确定购买。

当时我完全不懂flash,而且微软的silverlight也推出了,我一直希望MS能推出更牛叉更容易的解决方案,让我不用学习新语言也能做出 这样类似的应用(当时SL还不支持摄像头),但是一直等啊等(等到花儿也谢了),SL到4.0才支持摄像头,但却没有推出服务端的解决方案,所以目前在这 方面貌似仍然是Adobe领先。

后来换了一家公司也有同类应用(是用As2.0开发的),正好去年底Flash开发人员有事离开了公司,于是在没人接手的情况下,我被迫开始学习 As3.0和FMS,因为As3.0与As2.0几乎完全不兼容(以前的AS2.0代码基本上全看不懂),于是我尝试用As3.0把这套系统重新写了一 遍。

下面是基本结构图,其实说穿了这玩意儿也极其简单

大致原理:

需要做二个flash:一个用于“视频导购员”,即下图中的ChatServer.swf(以下称为服务端),另一个是给“普通访客”用的,即下图 中的ChatClient.swf(以下称为客户端)

二个flash运行时,都先连接到FMS服务器,同时”服务端flash”将用户名等认证信息传输到FMS服务器,FMS服务器再通过ASPX去请 求数据库查询,以验证”导购(客服)员”的信息是否有效,验证通过后FMS服务器允许连接,此时导购员可将自己的的视频与音频向所有连接到自己的访客广 播。

访客可以发送文字信息给导购员,导购员可以通过语音或文字回复。

注:之所以不让访客也能使用摄像头或耳麦,是为了防止有些BT客户播放一些恶作剧的声音或者做一些不雅的动作干扰导购员。同时这样也节省了带宽,否 则的话,就变成一个单纯的视频多人聊天室了 (再演化下去就变成前些年曾盛极一时的~裸~聊@系$统了,呵呵,当然我们都是走正道的人,这种事儿咱不干, 所以我把这个用于电子商务购物平台上的在线视频导购)

这是运行中的基本界面(尚未美化,难看了一点):

这类系统的基本功能:
01.服务端(Flash)能推送摄像头视频和麦克风音频到客户端(Flash).
02.服务端能显示访客列 表,并能从列表中选择需要交谈的对象,实现一对一的视频/语音广播(当然技术上也可以让所有客户听到导购员的声音,这样显得更真实,会让导航员看上去确实 比较忙,能造点气氛,呵呵)
03.服务端能同时接受所有访客的文字消息及系统消息.
04.服务端对指定访客要有屏蔽(黑名单)功能(防止 恶意骚扰)
05.服务端允许设置快速回复,以提高工作效率。

06.服务端允许暂停视频/音频直播,方便偶尔离开的情况,休息回来后,可继续直播。
06.客户端能调节音频的音量大小.
07.客 户端能在线直接截取视频图象并保存.
08.客户端能随时发送文字消息给服务端(前提是未被列为黑名单的情况下)

09.客户端能显示所有在线的导购员列表,并能切换选择不同的导购员视频/音频。

10.客户端能随时接收系统广播。

另外,对了方便运营商管理和监控,以避免发生一些不良现象(比如个人素质不好的导购员,可能会做出一些不雅的动作或姿态),还需要另做一个管理端 Flash,用于实时监控所有在列的导购员视频(就象小区门卫的监控窗口一样),如果发现不良行为,可以强制让导购员下线。

如果需要保存文字聊天记录或记录导购员及访客的登录时间,也可以在FMS服务器上保存每一条详细记录。

主要技术要点:

1.FMS开发中,Application目录下每一个文件夹即为一个应用,所以最简单的解决办法:有多少个导购员,就建多少个文件夹。

2.多个访客连接某一位导购员,其实就是让这些访客与导购员都连接到FMS的某一个文件夹(应用),只不过main.asc中根据不同的身份做出不 同的反馈;访客切换不同的导购员,相当于重新连接新的Application.

3.服务端文本聊天记录的保存,其实只要把每次发送的文字消息trace一下,就会自动记录在FMS的log记录中。

4.导购在线时间的统计,其实也是在main.asc的onConnect、disConnect事件中可以处理。

5.管理端对所有导购员的监控,其实就是在一个Flash中同时创建N个connection,每个conn连接到对应的导购员应用(文件夹)。

6.黑名单或强制下线功能,其实就是在fms服务端disconnect相应的client连接.

7.FMS与数据库的交互可以通过ASP.NET来完成,鉴于as对xml的支持程度极为友好,我个人倾向于让fms去加载一个RESTful的 wcf来获取数据(wcf返回xml格式),需要保存数据时,也可以让fms把数据post或get到某一个RESTful风格的wcf即可.

可能存在的问题:

在某些情况下,管理层可能需要对导购员在线的时间做统计,把这个做为考核绩效的一项指标。这样就存在一个问题,导购员可以一直把摄像头开着,然后人 跑掉了(或者在线看电影而不是在工作),但系统仍然在不停的计时。

为了解决这个bug,可以在服务端Flash中利用Camera的onActivity事件做一些处理,每当摄像头有活动时,该事件会被触发,如果 长时间画面未动,多半就是人跑掉了,可以向FMS服务器自动发送指令,停止计时。如果要做得更精确,flash社区里有一些很NB的大牛们,创造了视频像 素追踪技术,可以检测某个小范围内的像素(BitmapData)变化情况,感兴趣的朋友可以百度,google.

性能及扩展问题:

windows系统下,如果某个目录下的子文件夹数量过多(比如1w个),将会严重影响IO性能(最典型的表现就是进入该目录时,系统响应极慢), 也就是说如果导购员很多,就得分到不同的服务器,这个其实很好解决,多架设几台服务器分散即可,比如1-100个导购员,放在1号服务器,101-200 个导购员放在2号服务器…,当然如果您的money足够多,业内有很多专业做cdn加速和负载均衡的公司,可以实现集群/CDN的的解决方案(不过那 花费可就hign了去了)。

另外,Adobe官方也有相应的集群解决方案:http://files.cnblogs.com/yjmyzz/Flash%2bMedia%2bServer%2b3%e6%8a%80%e6%9c%af%e6%8c%87%e5%8d%97.pdf 这 是“Flash+Media+Server+3技术指南”,里面有一部分提到了集群部署问题,感兴趣的朋友可以下回去研究。

相关基础技术可参见下面的文章:

Flash/Flex学习笔记(10):FMS 3.5之Hello World!

Flash/Flex学习笔记(12):FMS 3.5之如何做视频实时直播

Flash/Flex学习笔记(53):利用FMS快速创建一个文本聊天室

另外FMS3.5安装以后的文档也是极好的参考资料.

后记:

在评论中看到有部分朋友对于这种架构的开发成本(主要是FMS产品的费用)有所担心,其实我很奇怪:大家都在用D版的windows Xp/win7,D版的Office,D版的vs.net,D版的TFS,甚至D版的SQLServer… 好象很少有人担心微软上门来收费,为何就一个FMS,就担心成这样? 🙂

好吧,我承认FMS是收费软件,这确实不能算是这种架构的优势,但其实Adobe是很开放的(不象某水果公司讲的那样),很多标准和协议都是公开 的,所以开源界根据这些标准也有人推出了FMS的替换产品RED5,以下是百度知道上摘抄过来的:

Red5的主要功能和Macromedia公司的FMS类似,提供基于Flash的流媒体服务的一款基于Java的开源流媒体服务器。它由 Java语言编写,使用RTMP作为流媒体传输协议,这与FMS完全兼容。它具有流化FLV、MP3文件,实时录制客户端流为FLV文件,共享对象,实时 视频播放、Remoting等功能。用Red5替换FMS后,客户端不用更改可正常运行。

注意高亮部分,也就是说,如果你拿fms的免费版本做开发,技术成熟以后,如果出于成本考虑,换成red5,flash端是不用做任何修改的,仅服 务端可能需要重新做些调整即可。

另外关于开发工具的选择,其实flash有很多第三方的开发工具,都不要钱的,并非只有Flash CS与Flex Builder/Flash Builder可选。

Flash不仅仅只是一个嵌在网页中的小动画,其实…它也是一个很舞台很大的平台。

作 者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]经过一年时间的沉淀 再次回首 TCP Socket服务器编程

mikel阅读(1178)

[转载]经过一年时间的沉淀 再次回首 TCP Socket服务器编程 – 美丽人生 – 博客园.

——————

前言

——————

开发了这么多年,发现最困难的程序开发就是通讯系统。

其他大部分系统,例如CRM/CMS/权限框架/MIS之类的,无论怎么复杂,基本上都能够本地代码本地调试,性能也不太重要。(也许这个就 是.net的企业级开发的战略吧)

可是来到通讯系统,一切变得困难复杂。原因实在太多了,如:

  • 性能永远是第一位:有时候一个if判断都要考虑性能,毕竟要损耗一个CPU指令,而在通讯系统服务 器,每秒钟都产生上百万级别的通讯量,这样一个if就浪费了1个毫秒了。
  • 系统环境极其恶劣:所有我 们可以想象的恶意攻击、异常输入等都要考虑;
  • 网络说断就断:在socket环境下,客户端可以以各 种理由断开链接,而且服务器根本不会知道,连一个流水作业的业务逻辑都无法保证正常执行,因此需要设计各种辅助的协议、架构去监督。
  • 各 种网络链接问题:例如代理、防火墙等等。。。

经过了1年的跌跌撞撞,我总算收获了点有用的经验,本文先从设计角度介绍一些我在Socket编程中的经验,下一篇在放出源代码。

——————

现有的Socket编程资源

——————

1. 首选推荐开源的XMPP框架,也就是Google的Gtalk的开源版本。里面的架构写的非常漂亮。特点就是:简洁、清晰。

2. 其次推荐LumaQQ.net,这套框架本身写的一般般,但是腾讯的服务器非常的猛,这样必然导致客户端也要比较猛。通过学习这套框架,能够了解腾讯的 IM传输协议设计,而且他们的协议是TCP/UDP结合,一举两得。

3. 最后就是DotMsn。这个写的实在很一般般,而且也主要针对了MSN的协议特点。是能够学习到一点点的框架知识的,不过要有所鉴别。

——————

Socket的选择

——————

在Java,到了Java5终于出现了异步编程,NIO,于是各种所谓的框架冒了出来,例如MINA, xsocket等等;而在.NET,微软一早就为我们准备好了完善的Socket模型。主要包括:同步Socket、异步Socket;我还听说 了.net 3.x之后,异步的Socket内置了完成端口。综合各种模型的性能,我总结如下:

1. 如果是短链接,使用同步socket。例如http服务器、转接服务器等等。

2. 如果是长链接,使用异步socket。例如通讯系统(QQ / Fetion)、webgame等。

3. .net的异步socket的连接数性能在 7500/s(每秒并发7500个socket链接)。而听说完成端口在1.5w所有。但是我到目前还没有正式见过所谓的完成端口,不知道到底有多牛逼。

4. 我听说了java的NIO性能在5000/s所有,我们项目内部也进行了链接测试,在4000~5000比较稳定,当然如果代码调优之后,能提高一点点。

——————

TCP Socket协议定义

——————

本文从这里开始,主要介绍TCP的socket编程。

新手们(例如当初的我),第一次写socket,总是以为在发送方压入一个”Helloworld”,接收方收到了这个字符串,就“精通”了 Socket编程了。而实际上,这种编程根本不可能用在现实项目,因为:

1. socket在传输过程中,helloworld有可能被拆分了,分段到达客户端),例如 hello   +   world,一个分段就是一个包(Package),这个就是分包问题

2. socket在传输过成功,不同时间发送的数据包有可能被合并,同时到达了客户端,这个就是黏包问题。例如发送方发送了 hello+world,而接收方可能一次就接受了helloworld.

3. socket会自动在每个包后面补n个 0x0 byte,分割包。具体怎么去补,这个我就没有深入了解。

4. 不同的数据类型转化为byte的长度是不同的,例如int转为byte是4位(int32),这样我们在制作socket协议的时候要特别小心了。具体可 以使用以下代码去测试:

代码

public void test()
{
int myInt = 1;
byte[] bytes = new byte[1024];
BinaryWriter writer
= new BinaryWriter(new MemoryStream(bytes));
writer.Write(myInt);
writer.Write(
j);
writer.Close();
}

尽管socket环境如此恶劣,但是TCP的链接也至少保证了:

  • 包发送顺序在传输过程中是不会改变的,例如发送方发送 H E L L,那么接收方一定也是顺序收到H E L L,这个是TCP协议承诺的,因此这点成为我们解决分包、黏包问题的关键。
  • 如果发送方发送的是helloworld, 传输过程中分割成为hello+world,那么TCP保证了在hello与world之间没有其他的byte。但是不 能保证helloworld和下一个命令之间没有其他的byte。

因此,如果我们要使用socket编程,就一定要编写自己的协议。目前业界主要采取的协议定义方式是:包头+包体长度+包体。 具体如下:

1. 一般包头使用一个int定义,例如int = 173173173;作用是区分每一个有效的数据包,因此我们的服务器可以通过这个int去切割、合并包,组装出完整的传输协议。有人使用回车字符去分割 包体,例如常见的SMTP/POP协议,这种做法在特定的协议是没有问题的,可是如果我们传输的信息内容自带了回车字符串,那么就糟糕了。所以在设计协议 的时候要特别小心。

2. 包体长度使用一个int定义,这个长度表示包体所占的比特流长度,用于服务器正确读取并分割出包。

3. 包体就是自定义的一些协议内容,例如是对像序列化的内容(现有的系统已经很常见了,使用对象序列化、反序列化能够极大简化开发流程,等版本稳定后再转入手 工压入byte操作)。

一个实际编写的例子:比如我要传输2个整型 int = 1, int = 2,那么实际传输的数据包如下:

173173173               8                  1         2

|——包头——|—-包体长度—-|——–包体——–|

这个数据包就是4个整型,总长度 = 4*4  = 16。

说说我走的弯路:

我曾经偷懒,使用特殊结束符去分割包体,这样传输的数据包就不需要指名长度了。可是后来高人告诉我,如果使用特殊结束符去判断包,性能会损失很大, 因为我们每次读取一个byte,都要做一次if判断,这个性能损失是非常严重的。所以最终还是走主流,使用以上的结构体。

——————

Socket接收的逻辑概述

——————

针对了我们的数据包设计+socket的传输特点,我们的接收逻辑主要是:

1. 寻找包头。这个包头就是一个int整型。但是写代码的时候要非常注意,一个int实际上占据了4个byte,而可悲的是这4个byte在传输过程中也可能 被socket 分割了,因此读取判断的逻辑是:

  • 判断剩余长度是否大于4
  • 读取一个int,判断是否包头,如果是就跳出循环。
  • 如果不是包头,则倒退3 个byte,回到第一点。
  • 如果读取完毕也没有找到,则有可能包头被分割了,因此当前已读信息压入接收缓存,等待下一个包到达后合并判 断。

2. 读取包体长度。由于长度也是一个int,因此判断的时候也要小心,同上。

3. 读取包体,由于已知包体长度,因此读取包体就变得非常简单了,只要一直读取到长度未知,剩余的又回到第一条寻找包头。

这个逻辑不要小看,就这点东西忙了我1天时间。而非常奇怪的是,我发现C#写的socket,似乎没有我说的这么复杂逻辑。大家可以看看 LumaQQ.net / DotMsn等,他们的socket接收代码都非常简单。我猜想:要么是.net的socket进行了优化,不会对int之类的进行分割传输;要么就是作 者偷懒,随便写点代码开源糊弄一下。

——————

Socket服务器参数概述

——————

我在开篇也说了,Socket服务器的环境是非常糟糕了,最糟糕的就是客户端断线之后服务器没有收到通知。 因为socket断线这个也是个信息,也要从客户端传递到我们socket服务器。有可能网络阻塞了,导致服务器连断开的通知都没有收到。

因此,我们写socket服务器,就要面对2个环境:

1. 服务器在处理业务逻辑中的任何时候都会收到Exception, 任何时候都会因为链接中断而断开。

2. 服务器接收到的客户端请求可以是任意字符串,因此在处理业务逻辑的时候,必须对各种可能的输入都判断,防止恶意攻击。

针对以上几点,我们的服务器设计必须包含以下参数:

1. 客户端链接时间记录:主要判断客户端空连接情况,防止连接数被恶意占用。

2. 客户端请求频率记录:要防止客户端频繁发送请求导致服务器负荷过重。

3. 客户端错误记录:一次错误可能导致服务器产生一次exception,而这个性能损耗是非常严重的,因此要严格监控客户端的发送协议错误情况。

4. 客户端发送信息长度记录:有可能客户端恶意发送非常长的信息,导致服务器处理内存爆满,直接导致宕机。

5. 客户端短时间暴涨:有可能在短时间内,客户端突然发送海量数据,直接导致服务器宕机。因此我们必须有对服务器负荷进行监控,一旦发现负荷过重,直接对请求 的socket返回处理失败,例如我们常见的“404”。

6. 服务器短时间发送信息激增:有可能在服务器内部处理逻辑中,突然产生了海量的数据需要发送,例如游戏中的“群发”;因此必须对发送进行队列缓存,然后进行 合并发送,减轻socket的负荷。

——————

后记

——————

本文从架构设计分析了一个socket服务器的设计要点。如果您有其他见解,欢迎留言与讨论。


我们的最新动态 (Bamboo@pixysoft.net)

  • 1.大城小格服务器升级到G级别.空间3G.数据库1G.为下一步高级应用..[2010-7-4]
  • 2. 基于socket的通讯架构成功集成到信息流服务器.替换第三方的即时通..[2010-6-15]
  • 3.Socket服 务器第一阶段开发完成.即时通讯即将全面移植到内部即时通..[2010-6-14]
  • 我们每天都在努力着!

作者:美丽人生
技术支持:reborn_zhang@hotmail.com

[转载]策略模式-5

mikel阅读(991)

[转载]策略模式-5 – 云飞龙行 – 博客园.

3.4  策略模式结合模板方法模式

在实际应用策略模式的过程中,经常会出现这样一种情况,就是发现这一系列算法的实现上存在公共功能,甚至这一系列算法的实现步骤都是一样的,只是在某些局 部步骤上有所不同,这个时候,就需要对策略模式进行些许的变化使用了。
对于一系列算法的实现上存在公共功能的情况,策略模式可以有如下三种实现方式:

  • 一个是在上下文当中实现公共功能,让所有具体的策略算法回调这些方法。
  • 另外一种情况就是把策略的接口改成抽象类,然后在里面实现具体算法的公共功能。
  • 还有一种情况是给所有的策略算法定义一个抽象的父类,让这个父类去实现策略的接口,然后在这个父类里面去实现公共的功能。

更进一步,如果这个时候发现“一系列算法的实现步骤都是一样的,只是在某些局部步骤上有所不同”的情况,那就可以在这个抽象类里面定义算法实现的骨架,然 后让具体的策略算法去实现变化的部分。这样的一个结构自然就变成了策略模式来结合模板方法模式了,那个抽象类就成了模板方法模式的模板类。
在上一章我们讨论过模板方法模式来结合策略模式的方式,也就是主要的结构是模板方法模式,局部采用策略模式。而这里讨论的是策略模式来结合模板方法模式, 也就是主要的结构是策略模式,局部实现上采用模板方法模式。通过这个示例也可以看出来,模式之间的结合是没有定势的,要具体问题具体分析。
此时策略模式结合模板方法模式的系统结构如下图5所示:

图5  策略模式结合模板方法模式的结构示意图
还是用实际的例子来说吧,比如上面那个记录日志的例子,如果现在需要在所有的消息前面都添加上日志时间,也就是说现在记录日志的步骤变成了:第一步为日志 消息添加日志时间;第二步具体记录日志。
那么该怎么实现呢?
(1)记录日志的策略接口没有变化,为了看起来方便,还是示 例一下,示例代码如下:

/**

* 日志记录策略的接口

*/

public interface LogStrategy {

/**

* 记录日志

* @param msg 需记录的日志信息

*/

public void log(String msg);

}

(2)增加一个实现这个策略接口的抽象类,在里面定义记录日志的算法骨架,相当于模板方法模式的模板,示例代码如下:

/**

* 实现日志策略的抽象模板,实现给消息添加时间

*/

public abstract class LogStrategyTemplate implements LogStrategy{

public final void log(String msg) {

//第一步:给消息添加记录日志的时间

DateFormat df = new SimpleDateFormat(

“yyyy-MM-dd HH:mm:ss SSS”);

msg = df.format(new java.util.Date())+” 内容是:”+ msg;

//第二步:真正执行日志记录

doLog(msg);

}

/**

* 真正执行日志记录,让子类去具体实现

* @param msg 需记录的日志信息

*/

protected abstract void doLog(String msg);

}

(3)这个时候那两个具体的日志算法实现也需要做些改变,不再直接实现策略接口了,而是继承模板,实现模板方法了。这个时候记录日志到数据库的类, 示例代码如下:

/**

* 把日志记录到数据库

*/

public class DbLog extends LogStrategyTemplate{

//除了定义上发生了改变外,具体的实现没变

public void doLog(String msg) {

//制造错误

if(msg!=null && msg.trim().length()>5){

int a = 5/0;

}

System.out.println(“现在把 ‘”+msg+”‘ 记录到数据库中”);

}

}

同理实现记录日志到文件的类如下:

/**

* 把日志记录到数据库

*/

public class FileLog extends LogStrategyTemplate{

public void doLog(String msg) {

System.out.println(“现在把 ‘”+msg+”‘ 记录到文件中”);

}

}

(4)算法实现的改变不影响使用算法的上下文,上下文跟前面一样,示例代码如下:

/**

* 日志记录的上下文

*/

public class LogContext {

/**

* 记录日志的方法,提供给客户端使用

* @param msg 需记录的日志信息

*/

public void log(String msg){

//在上下文里面,自行实现对具体策略的选择

//优先选用策略:记录到数据库

LogStrategy strategy = new DbLog();

try{

strategy.log(msg);

}catch(Exception err){

//出错了,那就记录到文件中

strategy = new FileLog();

strategy.log(msg);

}

}

}

(5)客户端跟以前也一样,示例代码如下:

public class Client {

public static void main(String[] args) {

LogContext log = new LogContext();

log.log(“记录日志”);

log.log(“再次记录日志”);

}

}

运行一下客户端再次测试看看,体会一下,看看结果是否带上了时间。
通过这个示例,好好体会一下策略模式和模板方法模式的组合使用,在实用开发中是很常见的方式。

3.5  策略模式的优缺点

  • 定义一系列算法
    策略模式的功能就是定义一系列算法,实现让这些算法可以相互替换。所以会为这一系列算法定义公共的接口,以约束一系列算法要实现的功能。如果这一系列算法 具有公共功能,可以把策略接口实现成为抽象类,把这些公共功能实现到父类里面,对于这个问题,前面讲了三种处理方法,这里就不罗嗦了。
  • 避免多重条件语句
    根据前面的示例会发现,策略模式的一系列策略算法是平等的,可以互换的,写在一起就是通过if-else结构来组织,如果此时具体的算法实现里面又有条件 语句,就构成了多重条件语句,使用策略模式能避免这样的多重条件语句。
    如下示例来演示了不使用策略模式的多重条件语句,示例代码如下:
public class OneClass {

/**

* 示范多重条件语句

* @param type 某个用于判断的类型

*/

public void oneMethod(int type){

//使用策略模式的时候,这些算法的处理代码就被拿出去,

//放到单独的算法实现类去了,这里就不再是多重条件了

if(type==1){

//算法一示范

//从某个地方获取这个s的值

String s = “”;

//然后判断进行相应处理

if(s.indexOf(“a”) > 0){

//处理

}else{

//处理

}

}else if(type==2){

//算法二示范

//从某个地方获取这个a的值

int a = 3;

//然后判断进行相应处理

if(a > 10){

//处理

}else{

//处理

}

}

}

}

  • 更好的扩展性
    在策略模式中扩展新的策略实现非常容易,只要增加新的策略实现类,然后在选择使用策略的地方选择使用这个新的策略实现就好了。
  • 客户必须了解每种策略的不同
    策略模式也有缺点,比如让客户端来选择具体使用哪一个策略,这就可能会让客户需要了解所有的策略,还要了解各种策略的功能和不同,这样才能做出正确的选 择,而且这样也暴露了策略的具体实现。
  • 增加了对象数目
    由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。
  • 只适合扁平的算法结构
    策略模式的一系列算法地位是平等的,是可以相互替换的,事实上构成了一个扁平的算法结构,也就是在一个策略接口下,有多个平等的策略算法,就相当于兄弟算 法。而且在运行时刻只有一个算法被使用,这就限制了算法使用的层级,使用的时候不能嵌套使用。
    对于出现需要嵌套使用多个算法的情况,比如折上折、折后返卷等业务的实现,需要组合或者是嵌套使用多个算法的情况,可以考虑使用装饰模式、或是变形的职责 链、或是AOP等方式来实现。

3.6  思考策略模式

1:策略模式的本质
策略模式的本质:分离算法,选择实现
仔细思考策略模式的结构和实现的功能,会发现,如果没有上下文,策略模式就回到了最基本的接口和实现了,只要是面向接口编程的,那么就能够享受到接口的封 装隔离带来的好处。也就是通过一个统一的策略接口来封装和隔离具体的策略算法,面向接口编程的话,自然不需要关心具体的策略实现,也可以通过使用不同的实 现类来实例化接口,从而实现切换具体的策略。
看起来好像没有上下文什么事情,但是如果没有上下文,那么就需要客户端来直接与具体的策略交互,尤其是当需要提供一些公共功能,或者是相关状态存储的时 候,会大大增加客户端使用的难度。因此,引入上下文还是很必要的,有了上下文,这些工作就由上下文来完成了,客户端只需要与上下文交互就可以了,这样会让 整个设计模式更独立、更有整体性,也让客户端更简单。
但纵观整个策略模式实现的功能和设计,它的本质还是“分离算法,选择实现”,因为分离并封装了算法,才能够很容易的修改和添加算法;也能很容易的动态切换 使用不同的算法,也就是动态选择一个算法来实现需要的功能了。
2:对设计原则的体现
从设计原则上来看,策略模式很好的体现了开-闭原则。策略模式通过把一系列可变的算法进行封装,并定义出合理的使用结构,使得在系统出现新算法的时候,能 很容易的把新的算法加入到已有的系统中,而已有的实现不需要做任何修改。这在前面的示例中已经体现出来了,好好体会一下。
从设计原则上来看,策略模式还很好的体现了里氏替换原则。策略模式是一个扁平结构,一系列的实现算法其实是兄弟关系,都是实现同一个接口或者继承的同一个 父类。这样只要使用策略的客户保持面向抽象类型编程,就能够使用不同的策略的具体实现对象来配置它,从而实现一系列算法可以相互替换。

3:何时选用策略模式
建议在如下情况中,选用策略模式:

  • 出现有许多相关的类,仅仅是行为有差别的情况,可以使用策略模式来使用多个行为中的一个来配置一个类的方法,实现算法动态切换
  • 出现同一个算法,有很多不同的实现的情况,可以使用策略模式来把这些“不同的实现”实现成为一个算法的类层次
  • 需要封装算法中,与算法相关的数据的情况,可以使用策略模式来避免暴露这些跟算法相关的数据结构
  • 出现抽象一个定义了很多行为的类,并且是通过多个if-else语句来选择这些行为的情况,可以使用策略模式来代替这些条件语句

3.7  相关模式

  • 策略模式和状态模式
    这两个模式从模式结构上看是一样的,但是实现的功能是不一样的。
    状态模式是根据状态的变化来选择相应的行为,不同的状态对应不同的类,每个状态对应的类实现了该状态对应的功能,在实现功能的同时,还会维护状态数据的变 化。这些实现状态对应的功能的类之间是不能相互替换的。
    策略模式是根据需要或者是客户端的要求来选择相应的实现类,各个实现类是平等的,是可以相互替换的。
    另外策略模式可以让客户端来选择需要使用的策略算法,而状态模式一般是由上下文,或者是在状态实现类里面来维护具体的状态数据,通常不由客户端来指定状 态。
  • 策略模式和模板方法模式
    这两个模式可组合使用,如同前面示例的那样。
    模板方法重在封装算法骨架,而策略模式重在分离并封装算法实现。
  • 策略模式和享元模式
    这两个模式可组合使用。
    策略模式分离并封装出一系列的策略算法对象,这些对象的功能通常都比较单一,很多时候就是为了实现某个算法的功能而存在,因此,针对这一系列的、多个细粒 度的对象,可以应用享元模式来节省资源,但前提是这些算法对象要被频繁的使用,如果偶尔用一次,就没有必要做成享元了。

[转载]前端开发必须知道的JS(二) 闭包及应用

mikel阅读(901)

[转载]前端开发必须知道的JS(二) 闭包及应用 – JayChow – 博客园.

前端开发必须知道的JS(一) 原型和继承一文中说过下面写篇闭包, 加之最近越来越发现需要加强我的闭包应用能力,所以此文不能再拖了。本文讲的是函数闭包,不涉及对象闭包(如用with实现)。如果你觉得我说的有偏差, 欢迎拍砖,欢迎指教。

一. 闭包的理论

首先必须了解以下几个概念:

执行环境

每调用一个函数时(执行函数时),系统会为该函数创建一个封闭的局部的运行环境,即该函数的执行环境。函数总是在自己的执行环境中执行,如读写 局部变量、函数参数、运行内部逻辑。创建执行环境的过程包含了创建函数的作用域,函数也是在自己的作用域下执行的。从另一个角度说,每个函数执行环境都有 一个作用域链,子函数的作用域链包括它的父函数的作用域链。关于作用域、作用域链请看下面。

作用域、作用域链、调用对象

函数作用域分为词法作用域和动态作用域。

词法作用域是函数定义时的作用域,即静态作用域。当一个函数定义时,他的词法作用域就确定了,词法作用域说明的是在函数结构的嵌套关系下,函数 作用的范围。这个时候也就形成了该函数的作用域链。作用域链就是把这些具有嵌套层级关系的作用域串联起来。函数的内部[[scope]]属性指向了该作用 域链。

动态作用域是函数调用执行时的作用域。当一个函数被调用时,首先将函数内部[[scope]]属性指向了函数的作用域链,然后会创建一个调用对 象,并用该调用对象记录函数参数和函数的局部变量,将其置于作用域链顶部。动态作用域就是通过把该调用对象加到作用域链的顶部来创建的,此时的 [[scope]]除了具有定义时的作用域链,还具有了调用时创建的调用对象。换句话说,执行环境下的作用域等于该函数定义时就确定的作用域链加上该函数 刚刚创建的调用对象,从而也形成了新的作用域链。所以说是动态的作用域,并且作用域链也随之发生了变化。再看这里的作用域,其实是一个对象链,这些对象就 是函数调用时创建的调用对象,以及他上面一层层的调用对象直到最上层的全局对象。

譬如全局环境下的函数A内嵌套了一个函数B,则该函数B的作用域链就是:函数B的作用域—>函数A的作用域—>全局window的 作用域。当函数B调用时,寻找某标识符,会按函数B的作用域—>函数A的作用域—>全局window的作用域去寻找,实际上是按函数B的调用 对象—>函数A的调用对象—>全局对象这个顺序去寻找的。也就是说当函数调用时,函数的作用域链实际上是调用对象链。

闭包

在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这些动态数据(看完下面的应用就会很好的体会 这句话)。闭包的定义:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

闭包就是嵌套在函数里面的内部函数,并且该内部函数可以访问外部函数中声明的所有局部变量、参数和其他内部函数。当该内部函数在外部函数外被调 用,就生成了闭包。(实际上任何函数都是全局作用域的内部函数,都能访问全局变量,所以都是window的闭包)

譬如下面这个例子:

01 <script type="text/JavaScript">
02 function f(x) {
03 var a = 0;
04 a++;
05 x++;
06 var inner = function() {
07 return a + x;
08 }
09 return inner;
10 }
11 var test = f(1);
12 alert(test());
13 </script>

垃圾回收机制:如果某个对象不再被引用,该对象将被回收。

再结合前面所讲的一些概念,在执行var test=f(1)时创建了f的调用对象,这里暂且记作obj,执行完后虽然退出了外部执行环境,但内部函数inner被外部函数f外面的一个变量 test引用。由于外部函数创建的调用对象obj有一个属性指向此内部函数,而现在这个内部函数又被引用,所以调用对象obj会继续存在,不会被垃圾回收 器回收,其函数参数x和局部变量a都会在这个调用对象中得以维持。虽然调用对象不能被直接访问,但是该调用对象已成为内部函数作用域链中的一部分,可以被 内部函数访问并修改,所以执行test()时,可以正确访问x和a。所以说, 当执行了外部函数时,生成了闭包,被引用的外部函数的变量将继续存在。

二. 闭包的应用

应用1:

这个是我在用js模拟排序算法过程遇到的问题。我要输出每一次插入排序后的数组,如果在循环中写成

setTimeout(function() { $(“proc”).innerHTML += arr + “<br/>”; }, i * 500);

会发现每次输出的都是最终排好序的数组,因为arr数组不会为你保留每次排序的状态值。为了保存会不断发生变化的数组值,我们用外面包裹一层函数来 实现闭包,用闭包存储这个动态数据。下面用了2种方式实现闭包,一种是用参数存储数组的值,一种是用临时变量存储,后者必须要深拷贝。所有要通过闭包存储 非持久型变量,均可以用临时变量或参数两种方式实现。


应用2:

这个是无忧上的例子(点击这 里查看原帖),为每个<li>结点绑定click事件弹出循环的索引值。起初写成

id.onclick = function(){
alert(i);
}

id.onclick = function(){alert(i);}

发现最终弹出的都是4,而不是想要的 1、2、3,因为循环完毕后i值变成了4。为了保存i的值,同样我们用闭包实现:


(ps:var a = (function(){})(); 与 var a =new function(){}效果是一样的,均表示自执行函数。)

应用3:

下面的code是缓存的应用,catchNameArr。在匿名函数的调用对象中保存catch的值,返回的对象由于被CachedBox变量 引用导致匿名函数的调用对象不会被回收,从而保持了catch的值。可以通过CachedBox.getCatch(“regionId”);来操作,若 找不到regionId则从后台取,catchNameArr 主要是为了防止缓存过大。

01 <script type="text/javascript">
02 var CachedBox = (function() {
03 var cache = {}, catchNameArr = [], catchMax = 10000;
04 return {
05 getCatch: function(name) {
06 if (name in cache) {
07 return cache[name];
08 }
09 var value = GetDataFromBackend();
10 cache[name] = value;
11 catchNameArr.push(name);
12 this.clearOldCatch();
13 return value;
14 },
15 clearOldCatch: function() {
16 if (catchNameArr.length > catchMax) {
17 delete cache[catchNameArr.shift()];
18 }
19 }
20 };
21 })();
22 </script>

同理,也可以用这种思想实现自增长的ID。

01 <script type="text/javascript">
02 var GetId = (function() {
03 var id = 0;
04 return function() {
05 return id++;
06 }
07 })();
08 var newId1 = GetId();
09 var newId2 = GetId();
10 </script>

应用4:

这个是无忧上月MM的例子(点击这里查看原帖),用闭包实现程序的暂停执行功能,还蛮创意


把这个作用延伸下,我想到了用他来实现window.confirm。


看了上面的这些应用,再回到前面的一句话:在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这 些动态数据。这就是闭包的作用。也就说遇到需要存储动态变化的数据或将被回收的数据时,我们可以通过外面再包裹一层函数形成闭包来解决。

当然,闭包会导致很多外部函数的调用对象不能释放,滥用闭包会使得内存泄露,所以在频繁生成闭包的情景下我们要估计下他带来的副作用。

毕了。希望能对大家有所帮助。

作者:JayChow
出处:http://ljchow.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,请在文章页面明显位置给出原文链接。