[图像]图像相似度算法的C#实现及测评

mikel阅读(979)

  近日逛博客的时候偶然发现了一个有关图片相似度的Python算法实现。想着很有意思便搬到C#上来了,给大家看看。

闲言碎语

  才疏学浅,只把计算图像相似度的一个基本算法的基本实现方式给罗列了出来,以至于在最后自己测评的时候也大发感慨,这个算法有点不靠谱。不管怎么样,这个算法有时候还是有用的,所以还是列出来跟大家伙一起分享分享~~

  PS:图像处理这一块博大精深,个人偶尔发现了点东西拿来分享。说的不好的地方,写得太糟的地方,诸位准备扔砖头还望淡定,淡定~~

基本知识介绍

颜色直方图

                颜色直方图是在许多图像检索系统中被广泛采用的颜色特征,它所描述的是不同色彩在整幅图像中所占的比例,而并不关心每种色彩所处的空间位置,即无法描述图像中的对象或物体。颜色直方图特别适用于描述那些难以进行自动分割的图像。

灰度直方图

  灰度直方图是灰度级的函数,它表示图像中具有每种灰度级的像素的个数,反映图像中每种灰度出现的频率。灰度直方图的横坐标是灰度级,纵坐标是该灰度级出现的频率,是图像的最基本的统计特征。

   本文中即是使用灰度直方图来计算图片相似度,关于算法那一块也不赘言了,毕竟图像学图形学,直方图我是门儿都不懂,我也不准备打肿脸充胖子,只想实现一个最基本的算法,然后从最直观的角度看看这个算法的有效性,仅此而已。

 

算法实现

                诸位看官休怪笔者囫囵吞枣,浅尝辄止的学习态度。额毕竟是因兴趣而来,于此方面并无半点基础(当然,除了知道RGB是啥玩意儿——这还幸亏当年计算机图形学的老师是个Super美女,因此多上了几节课的缘故),更谈不上半点造诣,看官莫怪莫怪,且忍住怒气,是走是留,小生不敢有半点阻拦~~

大致步骤如下:

1,  将图像转换成相同大小,以有利于计算出相像的直方图来

2,  计算转化后的灰度直方图

3,  利用XX公式,得到直方图相似度的定量度量

4,  输出这些不知道有用没用的相似度结果数据

代码实现

步骤1 将图像转化成相同大小,我们暂且转化成256 X 256吧。

 

public Bitmap Resize(string imageFile, string newImageFile)

        {

            img = Image.FromFile(imageFile);

            Bitmap imgOutput = new Bitmap(img, 256, 256);

            imgOutput.Save(newImageFile, System.Drawing.Imaging.ImageFormat.Jpeg);

            imgOutput.Dispose();

            return (Bitmap)Image.FromFile(newImageFile);

      }

 

  这部分代码很好懂,imageFile为原始图片的完整路径,newImageFile为强转大小后的256 X 256图片的路径,为了“赛”后可以看到我们转化出来的图片长啥样,所以我就把它保存到了本地了,以至于有了上面略显丑陋的代码。

步骤2,计算图像的直方图

public int[] GetHisogram(Bitmap img)

        {

            BitmapData data = img.LockBits( new System.Drawing.Rectangle( 0 , 0 , img.Width , img.Height ), ImageLockMode.ReadWrite , PixelFormat.Format24bppRgb );

            int[ ] histogram = new int[ 256 ];

            unsafe

            {

                                byte* ptr = ( byte* )data.Scan0;

                                int remain = data.Stride – data.Width * 3;

                                for( int i = 0 ; i < histogram.Length ; i ++ )

                                        histogram[ i ] = 0;

                                for( int i = 0 ; i < data.Height ; i ++ )

                                {

                                        for( int j = 0 ; j < data.Width ; j ++ )

                                        {

                                                int mean = ptr[ 0 ] + ptr[ 1 ] + ptr[ 2 ];

                                                mean /= 3;

                                                histogram[ mean ] ++;

                                                ptr += 3;

                                        }

                                        ptr += remain;

                                }

            }

                        img.UnlockBits( data ); 

            return histogram;

    }

这段就是惊天地泣鬼神的灰度直方图计算方法,里面的弯弯绕还是留给诸位自己去掺和。

步骤3,计算直方图相似度度量

这一步骤的法宝在于这个:

Sim(G,S)= HowTo其中G,S为直方图,N 为颜色空间样点数

为了大家少敲两行字儿,也给出一堆乱七八糟的代码:

 

//计算相减后的绝对值

        private float GetAbs(int firstNum, int secondNum)

        {

            float abs = Math.Abs((float)firstNum – (float)secondNum);

            float result = Math.Max(firstNum, secondNum);

            if (result == 0)

                result = 1;

            return abs / result;

        }

 

        //最终计算结果

        public float GetResult(int[] firstNum, int[] scondNum)

        {

            if (firstNum.Length != scondNum.Length)

            {

                return 0;

            }

            else

            {

                float result = 0;

                int j = firstNum.Length;

                for (int i = 0; i < j; i++)

                {

                    result += 1 – GetAbs(firstNum[i], scondNum[i]);

                    Console.WriteLine(i + "—-" + result);

                }

                return result/j;

            }

    }

 

步骤4,输出

  这个……诸位爱怎么输出就怎么输出吧。直接Console也好,七彩命令行输出也罢,亦或者保存到文本文件中留作纪念啦啦,诸位“好自为之”~~

算法测评

  真对不住大家,忘了跟大家说,我也不是一个专业的算法测评人员,但是作为一个半拉子测试人员免不了手痒痒想要看看这个算法到底有多大能耐,就拿出几张图片出来验验货吧。

以下是算法测评结果。以下部分内容话带调侃,绝无恶意,开开玩笑,娱乐大众~~

 

路人甲

路人乙

图像相似度

恶搞点评

小白

小白

100%

里面什么都没有!?

恭喜你,如果你看不出来这是两张白底图片,那么你还真是小白,因为你连自家人都认不出来啊~~

小黑

小黑

100%

天下乌鸦一般黑,这个算法在这一点上立场还算坚定,表现不错~

Win7

win7

100%

碰到Win7也不动心,意志坚定地给出了100%的正确答案。

 

这算法比我意志坚定多了,我可是win7刚出来个7000就装了,还一直用到现在,不过确实好用~~

我

你

88.84%

明明很不一样的“我”跟“你”摆在那里,怎么就相似度这么高咧??

 

难道,“我”,“你”都认不出来??

 

哦,我忘了,这两张图片的大背景是一样的,难怪……

win7

Mac

16.08%

MSApple这么水火不相容?

【均使用默认桌面~~

Rose

Jack

50.64%

终于了解了JackRose不能在一起的真正原因:

不是爱的不够深,也不是泰坦尼克号沉了,用老妈的话说“没有‘夫妻’相”

—— 还是老妈这个过来人老道~~

小黑

小白

99.21%

哇,太不可思议了,竟然是这样。

这算法这样“黑”“白”不分??

 

我得向JackRose的忠实Fans道歉了,上面的话是一时失言~~祝他们俩白头偕老,下辈子千万别做船了,坐船也不出海,出海也不去北极,……

 

 

  经过我略显玩世不恭的测评活动,说实话,我对这个算法是相当的失望,尤其是最后一次对比中的结果,目前情绪低落中。这倒不是说这算法的一无是处,应该是我或者某些前辈用错了地方,个人觉得算法使用的局限性太大,也或许是我的期望值太高了吧。

后记

  开始看到这玩意儿的时候觉得这玩意儿很简单啊,可是一想不对劲,没有这么容易的事情,要不GoogleMS这些大牛们做了这么久还没有像样的玩意儿出来。果不其然,为了多了解一点相关的内容,我不得不Google了一下,觉得那些术语完全不知所云,看不懂啊;看来我得祭出我一般不使用的大杀器了——百度一搜。嘿,还真找出来了一堆东西,比Google上面的看起来容易多了,可是打开链接进去瞅瞅,发现还是非我当前能力之所及。没学到东西,但是好歹还是了解了一点皮毛上的皮毛。

全文完

  诸位看官若觉得讲得没有意义,浪费了你的时间,那就权当作冷笑话听听缓解一下紧张的神经~~

参考链接:

http://blog.csdn.net/lanphaday/archive/2008/04/24/2325027.aspx

http://www.baidu.com

http://www.google.com

[SQL]一步一步优化SQL语句(二):物理查询处理

mikel阅读(1093)

我在上一篇一步一步优化SQL语句(一):逻辑查询的各个阶段中详细介绍了SQL server中逻辑查询的各个阶段,几位园子的朋友读了以后发出了这样的疑问:
select *
from tab1, tab2
where tab1.id = tab2.id and tab1.col1 = 123 and tab2.col1 = 'abc'
照你所述的执行顺序,先要tab1和tab2进行笛卡尔乘积,再按照tab1.col1 = 123 and tab2.col1 = 'abc'进行筛选。这样的话,效率岂不是很低,数据库有这么愚蠢吗?
我想很多人都会有这个疑问,包括我在最初学习的时候也提出过这样的问题。那么,我的这篇文章就结合这个问题来讨论一下SQLServer的物理查询处理。 首先我们必须明白逻辑处理和物理处理和区别,逻辑处理是指执行一个查询应该产生什么样的结果,那么逻辑查询的各个阶段就是这个查询从逻辑上执行的先后顺 序,依照这个先后顺序就能得到正确的结果,正如我们做四则混合运算一样,先乘除后加减才能得到正确结果。所以说逻辑查询只关心产生一个我们期望的、正确的 结果,它并不关心产生这个结果需要多少的资源消耗。而物理处理就是怎么得到这个结果,这个时候才会考虑性能问题。下面我们就讨论下怎么执行这个物理处理 的。

当一个查询到达数据库引擎的时候,数据库引擎需要做的是执行这个查询的查询计划,那么这个时候就存在两种情况,一种可能是这个查询的查询计划已经在 缓存中,这种情况就直接执行这个查询计划。另外一种情况就是在缓存中找不到该查询的查询计划。没有怎么办?生成一个!怎么生成?
执行计划是在编译阶段生成的,编译需要经过三个步骤:分析、代数化(algebrization)、查询优化,看见没有这里的查询优化过程就能解决上面的朋友提出的先笛卡尔集在筛选造成性能低的问题。下面我就对这三个步骤作一个介绍。

第一步:分析是检查语法并把SQL批处理转化成分析树的过程,如select * t1 where id in(1,2,3,4,5,6,7)在被分析树分析后就展开成了select * t1 where id=1 or id=2 or id=3 or id=4 or id=5 or id=6 or id=7 ,除此之外还有检查语法是否正确的功能。

第二步:接下的过程是代数化(algebrization),这个阶段使用SQLServer 2005的新组件algebrizer,algebrizer组件的主要功能是绑定,因此代数化过程通常称为绑定。这个阶段是将第一步的分析树作为输入, 生成被称为查询处理器树的输出,用于查询优化。其实这个阶段主要做几个事情,一:运算符平展,简单的讲就是把二元运算符组合成N元运算符,这里必须给出一 个示例才能很好的解释这个二元转换成N元如第一步所示in操作展开成了一连串的or运算符,而分析器认为这些or都是二元的,也就是说它认为第一个or 的左孩子是id=1,右孩子是 (id=2 or id=3 or id=4 or id=5 or id=6 or id=7 )这个表达式,而右孩子又被认为是二元的,如此一来就必须进行一个递归过程。而运算符平展过程则将这种二元运算组合成n元运算符,就避免了递归的过程。 二:名称解析,这个过程其实就是检查这个查询中出现的表或者是表的列是不是在数据库中真实存在。以及在该查询过程中是不是可见的。三:类型派生,有点抽 象,举个例子就能理解了,比如union查询吧,union左右两边查询结果对应位置的数据类型应该是一致的。四:聚合绑定和组分绑定,执行完这个步骤后 查询处理器树便生成了。

第三步:查询优化,这个过程由查询优化器组件来完成的。查询中应该以何种顺序访问表,使用哪种方法和使用哪个索引,应该由哪个联接算法等都是由查询 优化器组件来决定的,但是这个决定也不是随意的,它必须满足的前提条件是保证最后得到的结果集必须是正确的,也就是说该结果集必须遵循逻辑处理的各个阶段 所得到的结果集相同。优化器会尝试该查询的许多变体,一查找成本最低的计划。如果优化器分析该查询的元数据得知只有一个可执行的计划,那么它就不会再尝试 寻求更好的计划,这个步骤叫做细微计划优化。如果没有找到细微计划优化,SQLserver将执行一些简化,简化就是对自身语法作一些转换,比如在联接前 计算表的where筛选器,如前一篇描述的,逻辑查询中where筛选总是在联接之后计算,但是先计算where筛选器在联接同样能得到的正确的结果,而 这样的效率往往是更高的,所以在物理处理中where往往在join前执行的,开篇提到的那个问题只是读者未理解逻辑处理和物理处理的差别而已。

到此为止,物理处理的各个步骤也做了一个简要的叙述,总结下,无论是存储过程还是即席查询都是执行的一个查询计划的副本,如果这个查询计划不存在的 话就必须经过编译生成一个执行计划,在编译阶段必须经过分析,绑定(代数化),查询优化这些过程,最终得到我们需要查找的结果。关于查询优化组件具体是怎 么优化查询处理器树的,我会在以后的篇幅作详细介绍。

[C#]ASP.NET MVC + ADO.NET EF 项目实战(一):应用程序布局设计

mikel阅读(739)

ASP.NET MVC + ADO.NET EF 项目实战(一):应用程序布局设计

什么叫上下文?

在你设计一个方法的时候,无法直接从方法参数或实例成员(字段或属性)获得的所有信息都是上下文。例如:

  • 当前用户是谁?
  • 刚才提供操作的数据库连接实例从哪里拿到?
  • 这个方法从哪个 View 或者哪个 Controller 调用的?

当然,在方法体中获得上下文最终还是要靠方法参数或实例成员。

在MVC中有大量的上下文信息,例如:

  • ControllerContext
  • ViewContext
  • ModelBindingContext
  • ExceptionContext
  • ActionExcutingContext
  • ActionExcutedContext
  • AuthorizationContext
  • ResultExcutingContext
  • ResultExcutedContext

这些上下文通过单一的参数提供了丰富的运行时信息。

实体上下文放到哪里?

除了MVC的上下文外,还有一个重要的上下文就是 ADO.NET EF的实体上下文,通常派生自System.Data.Objects.ObjectContext,都是由IDE自动生成的。这个上下文承载了数据库连 接,需要通过IDisposable来释放连接。多数情况下,我们这样使用:

using(MyEntities context = new MyEntities())
{
…… 在这里写入代码
}

如果在一次页面生命周期内只使用一次实体上下文这样处理是非常合适的,但是事实上不都是这样。更多的时候可能需要临时对实体进行一个小的访问,例如获得一个当前用户的显示名,通过这种方式访问就代价太大了。

我们知道,这个上下文可以存放到HttpContext里。在HttpContext的所有容器中,只有Items是最合适的,因为这个属性的存续 期在后台页面对象释放后就结束了。当然,被释放时也不会执行IDisposable的Dispose方法。我们仍然需要在Global.asax中捕捉 EndRequest事件。但是奇妙的是:在ASP.NET MVC Application中不能使用event方式来捕捉,只能手工写Application_EndRequest方法

什么是一次Model、二次Model和Form Model?

Model一共分为三种:

  • 直接数据库实体映射实例,如Product(产品)
  • 为View的呈现提供服务的包装对象,如ProductInfo(产品信息)
  • 为Post回传提供服务的包装对象,如ProductForm(产品属性值)

第一种类型Model的特点是非常浓缩,几乎没有冗余,通过复杂的关系进行组合,通常需要通过多个不同类型的实例进行组合来表达一个完整的有意义的 场景。例如,一个产品信息可能包含产品名称、产品类别、该产品所有的规格型号以及每种规格型号的参数、单价等。虽然ADO.NET EF提供了获取组合属性的能力,但不能处理多层次,并且不能对加载过程进行控制。所以,需要专门定义一些Model对这一组Model进行包装。如果把原 始的模型称做“一次Model”,则可以把这个包装对象称做“二次Model”。

页面上收集到的Form信息,通过三种方式传递到Controller(以登录为例):

  • 每个信息项一个参数:public ActionResult Login(string userName, string password){…}
  • 一个单一的名值对参数:public ActionResult Login(FormCollection formCollection){…}
  • 一个单一的包装对象:public ActionResult Login(LoginInfo info){…}

第一种方式不利于重构。当需要加入一个参数时,必须修改Action的签名。而且也无法令Controller把值传递到View。第二种方式不利 于设计时纠错,因为FormCollection中的值不是强类型的。所以,我们通常都会采用第三种方式。虽然ADO.NET EF对象可以直接作为Form Model,并且有BindAttribute对属性与Form值进行定制化的绑定,但是不够灵活,如果一个Form组合跨多个一次Model类型,则根 本无法处理。所以我们有必要专门定义一个Model给View使用。我们不妨称之为“Form Model”。

业务逻辑放到什么地方?

MVC是一种“古老”的设计模式,提供了非常自然的分层方式,这也是为什么利于单元测试的原因。除了MVC这些“主层”以外,BLL可以算是一个“亚层”。那么,我们把BLL放到什么地方最合适?

BLL需要完全可见Model层,同时也需要一些上下文信息。例如,我们至少从我们刚才描述的论题中发现,需要从HttpContext的 Items中获得实体上下文。有些时候,我们还需要将用户的一些登录信息缓存到HttpContext中,如果用户的登录信息非常复杂的话,仅仅依靠 HttpContext.User.Identity.Name每次去抓取未必很合算。我的习惯是把和这个用户相关的信息组合到一个大的Model对象 中,并把这个对象的实例 存放到HttpContext.Cache中。如果有任何变化,释放这个Cache项即可。

所以,对于业务逻辑的位置你可以有两个选择:

  • 放到 Model 下,再建立一个“上下文提供器接口”,由 Model 借助上下文来独立处理。
  • 放到 Controller 下,直接使用 Controller 提供的上下文来进行处理。

第一种方式不依赖Controller,解耦彻底,非常灵活,更易于测试。但是需要付出一定的成本,调用栈会稍深一点,还需要劳神处理到 Controller与BLLContext间的关系。第二种方式解耦不够彻底,但非常简捷,比较适合 Controller 与 Model 不必彻底解耦的小型项目。有意思的是:ASP.NET MVC Application模板所生成的AccountController采用的就是第二种方式。

ADO.NET EF仅影响ASP.NET MVC的Model层。在Model层中除了EDMX自动生成的一次Model外,我们还需要建立大量的二次Model和Form Model。当然,从提升内聚度考虑,所有的业务逻辑方法都在这些Model中定义,特别是,可以利用partial类和扩展方法这两种手段加入业务逻 辑。

[SQL]一步一步优化SQL语句(一):逻辑查询的各个阶段

mikel阅读(954)

大家好,我是忆然,前段时间我在学习ItZik Ben-Gan、Lubor Kollar、Dejan Sarka所著的《SQL Server

2005 技术内幕:T-SQL查询》一书,在此我把一些学习的心得跟大家分享

在查询中逻辑查询和物理查询有着本质的区别,SQL不同于其它编程的最明显的特征就是处理代码的顺序,

虽然总是最先写Select 但是几乎总在最后执行,那到底是怎么一个执行顺序呢

作者给出了如下的sql查询语句执行顺序

(8) select (9) distinct (11) <top_specification> <select_list>

(1)from <left_table>

(3) <join_type> join <right_table>

(2) on <join _condition>

(4) where <where_condition>

(5)group by <group_by_list>

(6) with {cube|rollup}

(7)having(having_condition)

(10) order by <order_by_condition>

从这个顺序中我们不难发现,所有的 查询语句都是从select开始执行的,在执行过程中,每个步骤都会为

下一个步骤生成一个虚拟表,这个虚拟表将作为下一个执行步骤的输入。

 

第一步:首先对from子句中的前两个表执行一个笛卡尔乘积,此时生成虚拟表 vt1

第二步:接下来便是应用on筛选器,on 中的逻辑表达式将应用到 vt1 中的各个行,筛选出满足on逻辑表

达式的行,生成虚拟表 vt2

第三步:如果是outer join 那么这一步就将添加外部行,left outer jion 就把左表在第二步中过滤的添

加进来,如果是right outer join 那么就将右表在第二步中过滤掉的行添加进来,这样生成虚拟表 vt3

第四步:如果 from 子句中多余两个表,那么就将vt3和第三个表连接,执行笛卡尔乘积。生成虚拟表,该

过程就是一个重复1-3的步骤。

第五步:应用where筛选器,对上一步生产的虚拟表引用where筛选器,生成虚拟表vt4,在这有个比较重要

的细节不得不不说下,对于包含outer join的子句查询,就有一个让人感到困惑的问题,到底在on筛选器

还是用where筛选器指定逻辑表达式呢?on和where的最大区别在于,如果在on应用逻辑表达式那么在第三

步outer join中还可以把移除的行再次添加回来,而where的移除的最终的。举个简单的例子,有一个学生

表(班级,姓名)和一个成绩表(姓名,成绩),我现在需要返回一个x班级的全体同学的成绩,但是这个班级

有几个学生缺考,也就是说在成绩表中没有记录。为了得到我们预期的结果我们就需要在on子句指定学生

和成绩表的关系(学生.姓名=成绩.姓名)那么我们是否发现在执行第二步的时候,对于没有参加考试的学

生就不会再vt2中出现,应为他们被on的逻辑表达式过滤掉了,但是我们用left outer join就可以把左表(

学生)中没有参加考试的学生找回来,应为我们想返回的是x班级的所有学生,如果在on中应用学生.班级

='x'的话,那么在left outer join 中就会将不是x班级的学生找回来,所以只能在where筛选器器重用用

学生.班级='x' 应为它的过滤是最终的。

第六步:group by 子句将<group_by_condition>中的唯一的值组合成为一组,得到虚拟表vt5。如果应用

了group by,那么后面的所有步骤都只能得到的vt5的列或者是聚合函数(count、sum、avg等)。原因在

于最终的结果集中只为每个组包含一行。

第七步:应用cube或者rollup选项,为vt5生成超组,生成vt6.

第八步:应用having筛选器,生成vt7。having筛选器是第一个也是为唯一一个应用到已分组数据的筛选器

第九步:处理select列表。将vt7中的在select中出现的列筛选出来。生成vt8.

第十步:应用distinct子句,vt8中移除相同的行,生成vt9。事实上如果应用了group by子句那么

distinct是多余的,原因同样在于,分组的时候是将列中唯一的值分成一组,同时只为每一组返回一行记

录,那么所以的记录都将是不相同的。

第十一步:应用order by子句。按照order_by_condition排序vt9,此时返回的一个游标,而不是虚拟表。

sql是基于集合的理论的,集合不会预先对他的行排序,它只是成员的逻辑集合,成员的顺序是无关紧要的

。对表进行排序的查询可以返回一个对象,这个对象包含特定的物理顺序的逻辑组织。这个对象就叫游标

。正因为返回值是游标,那么使用order by 子句查询不能应用于表表达式。排序是很需要成本的,除非你

必须要排序,否则最好不要指定order by,最后,在这一步中是第一个也是唯一一个可以使用select列表

中别名的步骤。

第十二步:应用top选项。此时才返回结果给请求者即用户。

带出为止我们将一个sql查询语句真正的执行过程梳理了一遍,对于使用查询语句多年的我来说,无疑对以

前的不少问题得出了解答。希望你也能从中受益。我将在后面介绍SQLServer 2005中新加入的逻辑处理阶

段。

[Flex]FMS3系列(四):在线视频录制、视频回放

mikel阅读(702)

      使用Flash/Flex+FMS实现在线视频录制、视频回放的很简单的。通过阅读API文档后基本都可以实现这个功能,本文也意在抛砖引玉,希望对刚入手这块的朋友有所帮助。

      首先建立好Flash(ActionScript 3.0)文件,从组件(可使用Ctrl+F7打开)库中拖拽相应的组件到Flash舞台上,如下图:

            

      界面布局好后我们通过程序设置组见的显示文本以及为按扭添加事件监听,新建一个ActionScript类文件,编写代码如下:

 1         public function PublishPlay():void
 2         {
 3             lbName.text="请输入视频文件名:";
 4             btnPublish.label="开始录制";
 5             btnPublish.addEventListener(MouseEvent.CLICK,onPublishClick);
 6             btnStop.label="停止录制";
 7             btnStop.addEventListener(MouseEvent.CLICK,onStopHandler);
 8             btnPlay.label="视频回放";
 9             btnPlay.addEventListener(MouseEvent.CLICK,onPlayHandler);
10             
11             video=new Video();
12             cam = Camera.getCamera();
13             mic = Microphone.getMicrophone();
14             if(cam==null)
15             {
16                 trace("没检测到视频摄像头");
17             }
18             else
19             {
20                 video.attachCamera(cam);
21             }
22             addChild(video);
23         }

 

      以上代码同时实现了将视频显示到flash界面上,通过Camera的静态方法getCamrea()方法可以直接获取到视频摄像头的数据。其中用到的video,cam和mic变量为预先定义好的,如下:

1     private var nc:NetConnection;
2     private var ns:NetStream;
3     private var video:Video;
4     private var cam:Camera;
5     private var mic:Microphone;

 

      接下来就需要连接到FMS服务器实现视频录制功能了,通过NetConnection类实现与FMS服务器的连接,并通过流将视频数据发布到FMS服务器。

 1         private function onPublishClick(evt:MouseEvent):void
 2         {
 3             nc=new NetConnection();
 4             nc.addEventListener(NetStatusEvent.NET_STATUS,onPublishStatusHandler);
 5             nc.connect("rtmp://localhost/PulishedStreams");
 6         }
 7         
 8         private function onPublishStatusHandler(evt:NetStatusEvent):void
 9         {
10             if(evt.info.code=="NetConnection.Connect.Success")
11             {
12                 ns=new NetStream(nc);
13                 ns.addEventListener(NetStatusEvent.NET_STATUS,onPublishStatusHandler);
14                 ns.client=new CustomClient();
15                 ns.attachCamera(cam);
16                 ns.attachAudio(mic);
17                 ns.publish(tbName.text,"record");
18             }
19         }

 

      在录制视频的时候视频命名是取的文本输入框的值作为视频名,OK,现在测试Flash(Ctrl+Enter),通过点击 按扭开始录制视频。通过查看FMS服务器的文件目录可以看到,刚刚测试录制的视频存放于FMS服务器应用下的streams/_definst_目录下。 详见下图所示:

            

      录制功能完成了,通过测试也可以成功的录制视频。最后我们通过程序来播放刚刚录制是视频。关于播放视频在上一篇文章《FMS3系列(三):创建基于FMS的流媒体播放程序,看山寨帮的山寨传奇》中已介绍怎么实现,这里就直接帖出代码不做解释。

 1 private function onPlayHandler(evt:MouseEvent):void
 2         {
 3             nc=new NetConnection();
 4             nc.addEventListener(NetStatusEvent.NET_STATUS,onPlayStatusHandler);
 5             nc.connect("rtmp://localhost/PulishedStreams");
 6         }
 7         
 8         private function onPlayStatusHandler(evt:NetStatusEvent):void
 9         {
10             if(evt.info.code=="NetConnection.Connect.Success")
11             {
12                 ns=new NetStream(nc);
13                 ns.addEventListener(NetStatusEvent.NET_STATUS,onPlayStatusHandler);
14                 ns.client=new CustomClient();
15                 
16                 video = new Video();
17                 video.attachNetStream(ns);
18                 ns.play(tbName.text,0);
19                 addChild(video);
20             }
21         }

 

      通过本文的基础上可以很方便的扩展出在线拍照等多种应用,有兴趣的朋友可以去试验下。下面是本文完整的示例代码。


 1package
 2{
 3    import flash.net.*;
 4    import flash.events.*;
 5    import flash.media.*;
 6    import flash.display.*;
 7    import fl.controls.*;
 8    
 9    public class PublishPlay extends Sprite
10    {
11        private var nc:NetConnection;
12        private var ns:NetStream;
13        private var video:Video;
14        private var cam:Camera;
15        private var mic:Microphone;
16        
17        public function PublishPlay():void
18        {
19            lbName.text="请输入视频文件名:";
20            btnPublish.label="开始录制";
21            btnPublish.addEventListener(MouseEvent.CLICK,onPublishClick);
22            btnStop.label="停止录制";
23            btnStop.addEventListener(MouseEvent.CLICK,onStopHandler);
24            btnPlay.label="视频回放";
25            btnPlay.addEventListener(MouseEvent.CLICK,onPlayHandler);
26            
27            video=new Video();
28            cam = Camera.getCamera();
29            mic = Microphone.getMicrophone();
30            if(cam==null)
31            {
32                trace("没检测到视频摄像头");
33            }

34            else
35            {
36                video.attachCamera(cam);
37            }

38            addChild(video);
39        }

40        
41        private function onStatusHandler(evt:NetStatusEvent):void
42        {
43            trace(evt.info.code);
44            if(evt.info.code=="NetConnection.Connect.Success")
45            {
46                ns=new NetStream(nc);
47                ns.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
48                ns.client=new CustomClient();
49            }

50        }

51        
52        private function onPublishClick(evt:MouseEvent):void
53        {
54            nc=new NetConnection();
55            nc.addEventListener(NetStatusEvent.NET_STATUS,onPublishStatusHandler);
56            nc.connect("rtmp://localhost/PulishedStreams");
57        }

58        
59        private function onPublishStatusHandler(evt:NetStatusEvent):void
60        {
61            if(evt.info.code=="NetConnection.Connect.Success")
62            {
63                ns=new NetStream(nc);
64                ns.addEventListener(NetStatusEvent.NET_STATUS,onPublishStatusHandler);
65                ns.client=new CustomClient();
66                ns.attachCamera(cam);
67                ns.attachAudio(mic);
68                ns.publish(tbName.text,"record");
69            }

70        }

71        
72        private function onStopHandler(evt:MouseEvent):void
73        {
74            nc.close();
75        }

76        
77        private function onPlayHandler(evt:MouseEvent):void
78        {
79            nc=new NetConnection();
80            nc.addEventListener(NetStatusEvent.NET_STATUS,onPlayStatusHandler);
81            nc.connect("rtmp://localhost/PulishedStreams");
82        }

83        
84        private function onPlayStatusHandler(evt:NetStatusEvent):void
85        {
86            if(evt.info.code=="NetConnection.Connect.Success")
87            {
88                ns=new NetStream(nc);
89                ns.addEventListener(NetStatusEvent.NET_STATUS,onPlayStatusHandler);
90                ns.client=new CustomClient();
91                
92                video = new Video();
93                video.attachNetStream(ns);
94                ns.play(tbName.text,0);
95                addChild(video);
96            }

97        }

98    }

99}

 

版权说明

  本文属原创文章,欢迎转载,其版权归作者和博客园共有。  

  作      者:Beniao

 文章出处:http://beniao.cnblogs.com/  或  http://www.cnblogs.com/

[Flex]基于flex4技术从零开发flex博客系统: 4 数据存储

mikel阅读(780)

第四课:存储数据

关于”基于flex4技术从零开发flex博客系统”这个系列,我已经写过三课。今天在Google中搜索了一下标题, 发现有许多网站已经偷偷转载了。转载没有问题,但是拜托,请附上原文链接好不好,这不仅是对作者的尊重,也是对读者的负责。昨天我对前三课进行了一些相当 程度的更新,由于这些转载者的小气,他们的读者是不可能获取更新了。不过没有关系,有意继续关注的朋友,只要在Google中搜索”基于flex4技术从零开发flex博客系统“就可以找到这个系列的最新版本,相信我的博客凭借原创以及等于5的Google PR值,会一直稳居搜索排行之首。

通过前三课我艰苦卓绝的努力,客户端与服务端通讯已经没有问题了。这对于一个没有学过flex4,没有用过java的初学者,已经相当不容易了。到目前为止,开发博客系统的准备工作,已经仅剩最后一项了:数据存储。

Google App Engine没有数据库的概念,不过app engine提供了JDO存储接口,google充许开发者直接定义、存储、查询、修改实体(entity)。

一,数据定义

我在sban.flexblog package下添加一个名为Greeting的实体类,这个一个POJO(Plain Old Java Object),意即简单朴素的java对象。Greeting.java类的代码如下:

package sban.flexblog;

import java.util.Date;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

/**
 * @author sban.li
 *
 */

@PersistenceCapable(identityType=IdentityType.APPLICATION)
public class Greeting {

        @PrimaryKey
        @Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
        private Long id;
       
        @Persistent
        private String user;
       
        @Persistent
        private String greetingContent;
       
        @Persistent
        private Date date;
       
        public Greeting(String user, String content, Date date)
        {
                this.user = user;
                this.greetingContent=content;
                this.date = date;
        }
       
        public String getUser()
        {
                return this.user;
        }
       
        public Long getId()
        {
                return this.id;
        }
       
        public String getGreetingContent()
        {
                return this.greetingContent;
        }
       
        public Date getDate()
        {
                return this.date;
        }
}

关于Pojo实体定义的简要说明:
1,@PrimaryKey用于定义实体的主键
2,@Persistent用于标识该变量名称将被存储
3,valueStrategy=IdGeneratorStrategy.IDENTITY设置id为自增模式,这意味着实例化该实体类时不用给id赋值
4,identityType=IdentityType.APPLICATION是干什么用的,不清楚,暂时不用管它

二,数据存储

实体定义好了,如何存储呢?这要用到javax.jdo.PersistenceManager,在sban.flexblog.managers package下新建一个类,名为PMFactory.java,代码如下:

package sban.flexblog.managers;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

/**
 * @author sban.li
 *
 */

public class PMFactory {

        private static final PersistenceManagerFactory pmfInstance = JDOHelper
                        .getPersistenceManagerFactory("transactions-optional");

        private PMFactory() {
        }

        public static PersistenceManagerFactory getInstance() {
                return pmfInstance;
        }

}

该工厂用于返回一个PersistenceManagerFactory的单例,其中transactions-optional参数是在src/META-INF/jdoconfig.xml中定义的:

<persistence-manager-factory name="transactions-optional">

   </persistence-manager-factory>

但是PersistenceManagerFactory并不是数据的管理者,PersistenceManager才是,现在我们可以用 PMFactory.getInstance().getPersistenceManager()方法获取PersistenceManager了。

我们需要提供一个新接口,这个接口充许用户提交用户名与greeting内容参数,并存储到google app engine。修改HelloWorld.java,添加如下代码:

private PersistenceManager pm = PMFactory.getInstance()
                        .getPersistenceManager();

        public Boolean greet2(String user, String content) {
                Boolean result = true;
                Greeting greeting = new Greeting(user, content, new Date());

                try {
                        pm.makePersistent(greeting);
                } catch (Exception e) {
                        result = false;
                } finally {
                        pm.close();
                }

                return result;
        }

pm.makePersistent用于存储Greeting实例对象。使用之后记得要记得调用pm.close。该接口需要两个参数,一为用户名称,一为greeting内容。如果存储成功,将返回true。

三,客户端代码调用

好了,server端已经准备就绪,现在客户端可以调用了。修改Index.mxml,修改greetViaRemoting方法,代码如下:

<FxApplication xmlns="http://ns.adobe.com/mxml/2009" initialize="configRemoting()">

        <Script>
                <![CDATA[
                        import mx.controls.Alert;
                        import flash.net.URLLoader;
                        import flash.net.URLRequest;
                        import mx.rpc.events.ResultEvent;
                        import mx.rpc.events.FaultEvent;
                        import mx.rpc.AbstractOperation;
                        import mx.rpc.remoting.RemoteObject;
                        import mx.messaging.ChannelSet;
                        import mx.messaging.channels.AMFChannel;
                        import mx.collections.ArrayCollection;
                        import mx.managers.CursorManager;
                       
                        [Bindable]
                        private var _greetingData : ArrayCollection;
                        private var _remotingObj : RemoteObject = new RemoteObject("GenericDestination");
                       
                        private function configRemoting() : void
                        {
                                _remotingObj.source = "sban.flexblog.HelloWorld";
                                _remotingObj.endpoint = "weborb.wo";
                        }
                       
                        private function greetViaRemoting() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("greet2");
                                op.addEventListener(ResultEvent.RESULT,
                function(event : ResultEvent) : void
                                        {
                                                Alert.show( event.result.toString() );
                                        };
             );
                                op.send(vNameTxt.text,vContentTxt.text);
                        }
                ]]>

        </Script>
       
        <layout>
                <BasicLayout />
        </layout>
       
        <VGroup width="100%">
                <HGroup>
                        <Label text="user:" />
                        <FxTextInput id="vNameTxt" text="sban" />
                </HGroup>
                <HGroup>
                        <Label text="content:" />
                        <FxTextInput id="vContentTxt" text="greeting content" />
                </HGroup>
               
                <HGroup>
                        <FxButton id="vSendBtn" label="remoting greet" click="greetViaRemoting()" />
                </HGroup>
               
        </VGroup>
       
        <TextBox text="by sban" color="gray" bottom="10" right="10" />
       
</FxApplication>

其中configRemoting将在Application initialize时调用,用于初始化RemoteObject对象。BasicLayout用于声明当前Application使用absolate布局,详见new language tag Private and layout in flex4

编译,运行,点击greeting,弹出如下提示框:
alert-ok.png

四,有问题,需要调试了

貌似没有问题,貌似数据已经存进Google了。但是第二次单击提交,就有问题了,Alert了两次。为什么点击一次button,弹出了两个提示框?难道接口被调用了二次?

为了找到原因所在,我必须要采用Debug模式了。在本地采用Debug模式之前,需修改Flex Project的属性里的output folder url为http://localhost:8080/,并把RemoteObject的endpoint设为http: //localhost:8080/weborb.wo。但是在正式布署到Google app engine时,需记得移除这些设置。

通过Debug,我发现greet2接口确实仅调用了一次,但是Alert.show却执行了两次。这说明AbstractOperation是一 个引用对象,第一次调用_remotingObj.getOperation(”greet2″)返回的对象与第二次调用返回的对象是同一个对象,我们在 同一个对象上添加了两次ResultEvent事件监听。handler做为内嵌函数变量,虽然前后名称相同,但因是局部函数变量,所以内部ID并不相 同,是两个完全不同的对象。结果便是,每单击一次,Alert便会多一次。

为了验证我的想法,我查看了RemoteObject的sdk源码,在其及其父类AbstractService中,有如下相关代码:

override public function getOperation(name:String):AbstractOperation
    {
        var op:AbstractOperation = super.getOperation(name);
        if (op == null)
        {
            op = new Operation(this, name);
            _operations[name] = op;
            op.asyncRequest = asyncRequest;
        }
        return op;
    }

    public function getOperation(name:String):AbstractOperation
    {
        var o:Object = _operations[name];
        var op:AbstractOperation = (o is AbstractOperation) ? AbstractOperation(o) : null;
        return op;
    }

由上面源码可以看出,在第一次调用时,flex会实例化一个Operation,并缓存在_operations之中。第二次,便是直接取出了。
思考1:读者可以思考一下,为什么flex要在父类中取出AbstractOperation引用,而在子类中实例化Operation并存储。

签于以上原因,我修改了我的greetViaRemoting的代码,如下:

private function greetViaRemoting() : void
{
        var op : AbstractOperation = _remotingObj.getOperation("greet2");
        var handler : Function = function(event : ResultEvent) : void
                {
                        op.removeEventListener(ResultEvent.RESULT, handler);
                        Alert.show( event.result.toString() );
                };
       
        op.addEventListener(ResultEvent.RESULT, handler);
        op.send(vNameTxt.text,vContentTxt.text);
}

修改之后,便没有重复弹出的问题了。

sban:在flex开发中,使用之后的事件监听一定要记得移除,特别对于通过内嵌函数添加的事件监听。反复的移除与添加事件监听,并不会 影响程序性能,相反如果只添加而不移除,才会让程序运行愈加沉重。这就好比做人,财富、名誉、资历、爱情等等这些身外之物,多了并不是负担,但要是每一样 都放在心里,就累了。

有些学者(学习者)可能说了,貌似数据存储成功了,但是我并不知道到底有没有真正存进Google云啊,怎么查看我存储的数据呢?这些存储如何更 新,如何删除?可惜Google并没有直接给我们开放直接访问App Engine数据库的权限。在我想继续描述app engine是如何让开发者查询、修改、删除数据时,我发现这一课已经写的相当长了。我的肩膀有点酸了,我想我应该去健身了。读者们也需要体息了。

本课最终源码:见下一课源码
sban 2009/4/26 于北京
本文为sban原创,作者保留所有权利,如需转载请保留作者原文链接
flex选修课系列:基于flex4技术从零开发flex博客系统

[Flex]基于flex4技术从零开发flex博客系统 : 5 数据存储之管理Greeting

mikel阅读(646)

第五课:存储数据之管理Greeting

Google App Engine for java自今年4月7日开放申请,至今不足二十日,但关于GAE for java的博文已经出现了不少。继数据存储之后,这一课要研究如何通过客户端管理Greeting,即CURD的实现。

一,列表显示
在sban.flexblog.HelloWorld.java中添加一个获取所有Greeting的接口:

public List<Greeting> getAllGreetings() {
                List<Greeting> result;

                try {
                        result = (List<Greeting>) pm.newQuery(Greeting.class).execute();
                } finally {
                        pm.close();
                }

                return result;
        }

在上例中,pm.newQuery(Greeting.class)负责查询所有已存的Greeting信息。List数据在Flex客户端将被映射成ArrayList。

修改Index.mxml客户端代码,修改之后最终代码如下:

<?xml version="1.0" encoding="utf-8"?>
<FxApplication xmlns="http://ns.adobe.com/mxml/2009" initialize="onInit()">

        <Script>
                <![CDATA[
                        import mx.controls.Alert;
                        import flash.net.URLLoader;
                        import flash.net.URLRequest;
                        import mx.rpc.events.ResultEvent;
                        import mx.rpc.events.FaultEvent;
                        import mx.rpc.AbstractOperation;
                        import mx.rpc.remoting.RemoteObject;
                        import mx.messaging.ChannelSet;
                        import mx.messaging.channels.AMFChannel;
                        import mx.collections.ArrayCollection;
                        import mx.managers.CursorManager;
                        import mx.events.IndexChangedEvent
                        
                        [Bindable]
                        private var _greetingData : ArrayCollection;
                        
                        [Bindable]
                        private var _greeting : Object;
                        
                        private var _remotingObj : RemoteObject = new RemoteObject("GenericDestination");
                        
                        private function onInit() : void
                        {
                                configRemoting();
                                getAllGreetings();
                        }
                        
                        private function configRemoting() : void
                        {
                                _remotingObj.source = "sban.flexblog.HelloWorld";
                                _remotingObj.endpoint = "weborb.wo";
                        }
                        
                        private function greetViaRemoting() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("greet2");
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                                getAllGreetings();
                                                Alert.show( event.result.toString() );
                                        };
                                
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send(vNameTxt.text,vContentTxt.text);
                        }
                        
                        private function getAllGreetings() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("getAllGreetings");
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                        
                                                _greetingData = ArrayCollection(event.result);
                                        };
                                        
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send();
                        }
                ]]>

        </Script>
        
        <layout>
                <BasicLayout />
        </layout>
        
        <VGroup width="100%">
                <HGroup>
                        <Label text="user:" />
                        <FxTextInput id="vNameTxt" text="sban" />
                </HGroup>
                <HGroup>
                        <Label text="content:" />
                        <FxTextInput id="vContentTxt" text="greeting content" />
                </HGroup>
                
                <HGroup>
                        <FxButton id="vSendBtn" label="remoting greet"click="greetViaRemoting()" />
                </HGroup>
                
                <FxList id="vList1" dataProvider="{_greetingData}" width="60%"itemRenderer="GreetingItemRenderer">
                        <layout>
                                <VerticalLayout />
                        </layout>
                </FxList>
                
        </VGroup>
        
        <TextBox text="by sban" color="gray" bottom="10" right="10" />
        
</FxApplication>

1,用一个Bindable的、ArrayList类型的_greetingData,用于存储Greeting数据列表。
2,getAllGreetings方法用于从远程接口sban.flexblog.HelloWorld.getAllGreetings中读取数据,其在OnInit中被调用。
3,FxList用于显示列表数据,使用了VerticalLayout布局,数据纵向依次排列。如果是HorizontalLayout布局,但是横向排列。关于布局,详见new language tag Private and layout in flex4
4,FxList的itemRenderer,即GreetingItemRenderer,是一个自定义的ItemRenderer,是一个mxml格式的skin文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<ItemRenderer xmlns="http://ns.adobe.com/mxml/2009">
        
    <states>
                <State name="normal"/>
                <State name="hovered"/>
                <State name="selected"/>
        </states>

        <TextBox text="ID:{data.id},User:{data.user}, 
                Greeting:{data.greetingContent}, 
                Date:{data.date}"
 
                verticalCenter="0" left="3" right="3" top="6" bottom="4" />

</ItemRenderer>

这个文件的逻辑很简单,就是把Greeting的数据信息在一个TextBox中显示出来。有关于在flex4中编写ItemRender的skin组件件详见:use skin as dataContainer’s itemRenderer in flex4 gumbo

运行一下,没有问题,但是FxList中内容不能点选。
fxlist-no-selection.png

这是由于我们没有在GreetingItemRenderer中定义点选的状态引起的,修改GreetingItemRenderer的代码,最终代码如下:

<ItemRenderer xmlns="http://ns.adobe.com/mxml/2009">
        
    <states>
                <State name="normal"/>
                <State name="hovered"/>
                <State name="selected"/>
        </states>
        
        <Rect left="0" right="0" top="0" bottom="0">
                <fill>
                        <SolidColor color="0xffffff" 
                                color.selected="0×666666" color.hovered="0×999999" />

                </fill>
        </Rect>

        <TextBox color="0×000000" color.selected="0xffffff" 
                text="ID:{data.id},User:{data.user}, 
                Greeting:{data.greetingContent}, 
                Date:{data.date}"
 
                verticalCenter="0" left="3" right="3" top="6" bottom="4" />

</ItemRenderer>

此时,FxList便可以点选了。当点选时,背景为深灰色,文字为白色,鼠标rollOver时,背景为浅灰。这个效果是由GreetingItemRenderer中的selected与hovered状态决定的。关于flex4中的新状态(State)详见:new state in flex4。运行效果如下图所示:
fxlist-selection-enabled.png

二,可恶的2030错误
在运行时,我发现会遇到以下Error:

Error: Error #2030: 遇到文件尾。

这个Error是flex客户端抛出的。但是根源却在server端,在类HelloWorld.java的getAllGreetings方法内,我们使用了List做为返回数据类型。在这里List仅是接口,具体数据载体是一个ArrayList。而ArrayList这个鬼类型,有容量与列表长度之分,初始创建时ArrayList有10个容量,当容量不足时,会自动增长2/3。这便造成,在接口的返回结果中,有一些列表元素是null,这是造成“遇到文件尾”的罪魁祸首。真不知这个bug应该归罪于WebORB还是flex。

修改getAllGreetings方法,最终代码如下:

   @SuppressWarnings(value="unchecked")
        public List<Greeting> getAllGreetings() {
                List<Greeting> result;

                try {
                        result = (List<Greeting>) pm.newQuery(Greeting.class).execute();
                        while (result.get(result.size()  1) == null) {
                                result.remove(result.size()  1);
                        }
                } finally {
                        pm.close();
                }

                return result;
        }

@SuppressWarnings用于告诉Eclipse不要再提示unchecked警告,不然总会有一个黄色的小灯泡在哪里挂着。

sban:储如2030、2032等等这类与IO、文件等相关的IOError,在flex客户端接口调试中是最常见,也是爆错信息最简单的一类Error。这类Error多是server端代码引起的。

二,删除与更新
在sban.flexblog.HelloWorld这个类中,添加有关删除一个Greeting,删除所有Greeting,编辑单个Greeting,以及获取单个Greeting的接口方法,该类最终代码如下:

package sban.flexblog;

import java.util.Date;
import java.util.List;
import javax.jdo.PersistenceManager;

import sban.flexblog.managers.PMFactory;

/**
 * @author sban.li
 *
 */

public class HelloWorld {
        public String greet(String name) {
                return "Hi " + name + ", this message comes from remoting. Now:"
                                + new Date().toString();
        }

        private PersistenceManager pm = PMFactory.getInstance()
                        .getPersistenceManager();

        public Boolean greet2(String user, String content) {
                Boolean result = true;
                Greeting greeting = new Greeting(user, content, new Date());

                try {
                        pm.makePersistent(greeting);
                } catch (Exception e) {
                        result = false;
                } finally {
                        pm.close();
                }

                return result;
        }
        
        public Greeting getGreetingById(Long id)
        {
                return (Greeting) pm.getObjectById(Greeting.class, id);
        }
        
        public Boolean editGreeting(Long id, String content)
        {
                Boolean result = true;
                
                try {
                        Greeting greeting = getGreetingById(id);
                        greeting.setGreetingContent(content);
                } catch (Exception e) {
                        result = false;
                } finally {
                        pm.close();
                }
                
                return result;
        }

        public Boolean deleteById(Long id) {
                Boolean result = true;

                try {
                        Greeting greeting = getGreetingById(id);
                        pm.deletePersistent(greeting);
                } catch (Exception e) {
                        result = false;
                } finally {
                        pm.close();
                }

                return result;
        }
        @SuppressWarnings(value="unchecked")
        public Boolean deleteAllGreetings() {
                Boolean result = true;

                try {
                        List<Greeting> greetings = (List<Greeting>) pm.newQuery(
                                        Greeting.class).execute();
                        pm.deletePersistentAll(greetings);
                } catch (Exception e) {
                        result = false;
                } finally {
                        pm.close();
                }

                return result;
        }

        @SuppressWarnings(value="unchecked")
        public List<Greeting> getAllGreetings() {
                List<Greeting> result;

                try {
                        result = (List<Greeting>) pm.newQuery(Greeting.class).execute();
                        while (result.get(result.size()  1) == null) {
                                result.remove(result.size()  1);
                        }
                } finally {
                        pm.close();
                }

                return result;
        }
}

1,pm.deletePersistent用于删除单个对象
2,pm.deletePersistentAll用于删除一个集合
3,pm.getObjectById用于获取单个对象
4,更新Greeting对象时,只需设置该对象属性,在pm.close之前,数据会自动存储。需在sban.flexblog.Greeting中,添加如下setter:

public void setGreetingContent(String v)
        {
                this.greetingContent = v;
        }

修改Index.mxml客户端代码,最终如下:

<?xml version="1.0" encoding="utf-8"?>
<FxApplication xmlns="http://ns.adobe.com/mxml/2009" initialize="onInit()">

        <Script>
                <![CDATA[
                        import mx.controls.Alert;
                        import flash.net.URLLoader;
                        import flash.net.URLRequest;
                        import mx.rpc.events.ResultEvent;
                        import mx.rpc.events.FaultEvent;
                        import mx.rpc.AbstractOperation;
                        import mx.rpc.remoting.RemoteObject;
                        import mx.messaging.ChannelSet;
                        import mx.messaging.channels.AMFChannel;
                        import mx.collections.ArrayCollection;
                        import mx.managers.CursorManager;
                        import mx.events.IndexChangedEvent
                        
                        [Bindable]
                        private var _greetingData : ArrayCollection;
                        
                        [Bindable]
                        private var _greeting : Object;
                        
                        private var _remotingObj : RemoteObject = new RemoteObject("GenericDestination");
                        
                        private function onInit() : void
                        {
                                configRemoting();
                                getAllGreetings();
                        }
                        
                        private function configRemoting() : void
                        {
                                _remotingObj.source = "sban.flexblog.HelloWorld";
                                _remotingObj.endpoint = "weborb.wo";
                        }
                        
                        private function greetViaRemoting() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("greet2");
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                                getAllGreetings();
                                                Alert.show( event.result.toString() );
                                        };
                                
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send(vNameTxt.text,vContentTxt.text);
                        }
                        
                        private function getAllGreetings() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("getAllGreetings");
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                        
                                                _greetingData = ArrayCollection(event.result);
                                        };
                                        
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send();
                        }
                        
                        private function deleteAllGreetings() : void
                        {
                                var op : AbstractOperation = _remotingObj.getOperation("deleteAllGreetings");
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                                getAllGreetings();
                                                //Alert.show(event.result.toString());
                                        };
                                        
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send();
                        }
                        
                        private function deleteItem() : void
                        {
                                if(vList1.selectedItem)
                                {
                                        var itemId : Number = vList1.selectedItem.id;
                                        
                                        var op : AbstractOperation = _remotingObj.getOperation("deleteById");
                                        var handler : Function = function(event : ResultEvent) : void
                                                {
                                                        op.removeEventListener(ResultEvent.RESULT, handler);
                                                        getAllGreetings();
                                                        //Alert.show(event.result.toString());
                                                };
                                                
                                        op.addEventListener(ResultEvent.RESULT, handler);
                                        op.send(itemId);
                                }
                        }
                        
                        private function selectionChangedHandler(event : IndexChangedEvent) : void
                        {
                                var itemId : Number = vList1.selectedItem.id;
                                var op : AbstractOperation = _remotingObj.getOperation("getGreetingById");
                                
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                                
                                                _greeting = event.result;
                                        };
                                        
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send(itemId);
                        }
                        
                        private function edit() : void
                        {
                                var content : String = vContentEditTxt.text;
                                var op : AbstractOperation = _remotingObj.getOperation("editGreeting");
                                
                                var handler : Function = function(event : ResultEvent) : void
                                        {
                                                op.removeEventListener(ResultEvent.RESULT, handler);
                                                getAllGreetings();
                                        };
                                        
                                op.addEventListener(ResultEvent.RESULT, handler);
                                op.send(_greeting.id, content);
                        }
                ]]>

        </Script>
        
        <layout>
                <BasicLayout />
        </layout>
        
        <VGroup width="100%">
                <HGroup>
                        <Label text="user:" />
                        <FxTextInput id="vNameTxt" text="sban" />
                </HGroup>
                <HGroup>
                        <Label text="content:" />
                        <FxTextInput id="vContentTxt" text="greeting content" />
                </HGroup>
                
                <HGroup>
                        <FxButton id="vSendBtn" label="remoting greet"click="greetViaRemoting()" />
                        <FxButton id="vDelAllBtn" label="deoete all greetings"click="deleteAllGreetings()" />
                        <FxButton id="vdelItemBtn" label="delete selected item"click="deleteItem()" />
                </HGroup>
                
                <FxList id="vList1" selectionChanged="selectionChangedHandler(event)" 
                        dataProvider="{_greetingData}" width="60%"itemRenderer="GreetingItemRenderer">

                        <layout>
                                <VerticalLayout />
                        </layout>
                </FxList>
                
                <Form borderStyle="solid">
                        <FormItem label="ID:">
                                <Label text="{_greeting.id}" />
                        </FormItem>
                        <FormItem label="User:">
                                <Label text="{_greeting.user}" />
                        </FormItem>
                        <FormItem label="Content:">
                                <FxTextInput id="vContentEditTxt"text="{_greeting.greetingContent}" />
                        </FormItem>
                        <ControlBar>
                                <FxButton id="vSubmitBtn" label="submit" click="edit()" />
                        </ControlBar>
                </Form>
                
        </VGroup>
        
        <TextBox text="by sban" color="gray" bottom="10" right="10" />
        
</FxApplication>

为了让逻辑简单,在编辑时,只充许修改Greeting的内容。这块逻辑比较简要,不再一一述说。读者们可自行下载源码运行查看。我把Index.mxml改名为Greeting.mxml,布署到了http://flex-blog.appspot.com/Greeting.html。附运行截图一张:
flex4-lesson4-5-screenshot.png

布署时要删除war/WEB-INF/appengine-generated目录下的文件,否则可能导致上传不成功。

本课最终源码:flex4-lessons4-5.zip
sban 2009/4/26 于北京
本文为sban原创,作者保留所有权利,如需转载请保留作者原文链接
flex选修课系列:基于flex4技术从零开发flex博客系统

[Flex]flex选修课之第三课:使用Remoting服务

mikel阅读(1087)

第三课:使用Remoting服务

在flex中有多种对象可用于与server交互,有URLLoader、HttpService、WebService、RemotingObject、Socket,NetConnection等:
1,URLLoader与HttpService多种于加载静态文本文件。
2,WebService基于soap协议,效率虽不及Remoting,但可移植性好,与平台、语言完全解藕。
3,Remoting采用amf通讯协议,通讯效率被认为是WebServive的10倍。目前已有多种成熟的服务端框架可供选用,.Net有fluorineFx,php有amfphp,java有blazeDSWebORB同时有支持多个语言的版本,如同pureMVC一般。python、ruby等也均实现了amf。

无论使用哪一种server端语言,Remoting均采用统一的配置。加上目前多种语言已实现amf通讯协议,移植已不成问题。因此,Remoting应该是flex目前最好的与server端交互的方式。

一,在Google App中使用WebORB
既如此,我想把我的hello world程序修改一下,用Remoting与server交互。我采用WebORB做为remoting server,配置步骤如下:

1,下载build of WebORB for Google App Engine解压缩至任何地方
2,把WEB-INF/classes目录的两个文件weborb-acl.xml, weborb-config.xml拷贝到gapp_flexblog/src目录下
3,把WEB-INF/lib目录下的三个jar文件拷贝到war/WEB-INF/lib目录下
4,把WEB-INF/flex目录直接拷贝到war/WEB-INF目录下
5,把WEB-INF/web.xml的内容与war/WEB-INF/web.xml进行merge,完成后内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

 

<web-app>

<!– parameter used by the ActiveMQ broker initializer. The parameter
       contains the path to start the broker on –>
   
  <context-param>
      <param-name>ActiveMQBrokerPath</param-name>
      <param-value>tcp://localhost:61616?trace=true</param-value>
  </context-param>

  <filter>
    <filter-name>fileuploadfilter</filter-name>
    <filter-class>weborb.util.upload.MultiPartFilter</filter-class>
    <init-param>
        <param-name>deleteFiles</param-name>
        <param-value>true</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>fileuploadfilter</filter-name>
    <url-pattern>*.wo</url-pattern>
  </filter-mapping> 
  
  <listener>
      <listener-class>weborb.thirdparty.ActiveMQStarter</listener-class>
   </listener> 

  <listener>
      <listener-class>weborb.ORBServletContextListener</listener-class>
  </listener>   
  
  <!– Servlets –>
  <servlet>
    <servlet-name>greetServlet</servlet-name>
    <servlet-class>sban.flexblog.server.GreetingServiceImpl</servlet-class>
  </servlet>
  
  <servlet>
    <servlet-name>helloWorld</servlet-name>
    <servlet-class>sban.flexblog.server.HelloWorldServlet</servlet-class>
  </servlet>
  
  <servlet>
    <servlet-name>weborb</servlet-name>
    <servlet-class>weborb.ORBServlet</servlet-class>
    <load-on-startup> 1 </load-on-startup>
  </servlet>
  
  <servlet>
    <servlet-name>download</servlet-name>
    <servlet-class>weborb.DownloadServlet</servlet-class>
    <load-on-startup> 1 </load-on-startup>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>download</servlet-name>
    <url-pattern>/codegen.wo</url-pattern>
  </servlet-mapping>       
  
  <servlet-mapping>
    <servlet-name>weborb</servlet-name>
    <url-pattern>*.wo</url-pattern>
  </servlet-mapping>
  
  <servlet-mapping>
    <servlet-name>greetServlet</servlet-name>
    <url-pattern>/gapp_flexblog/greet</url-pattern>
  </servlet-mapping>
  
  <servlet-mapping>
    <servlet-name>helloWorld</servlet-name>
    <url-pattern>/gapp_flexblog/hello</url-pattern>
  </servlet-mapping>
  
  <welcome-file-list>     
    <welcome-file>index.html</welcome-file>      
  </welcome-file-list>
  
</web-app>

welcome-file-list是欢迎页面列表。

 

二,使用remoting

在sban.flexblog命名空间下,添加一个HelloWorld.java文件,代码如下:

package sban.flexblog;

 

public class HelloWorld {
        public String greet(String name)
        {
                return "Hi " + name + ", this message comes from remoting.";
        }
}

呵呵,这个类非常简单,也不用继承HttpServlet什么的。好了,现在来写我们的客户端代码。把原Index.mxml文件修改内容如下:

<?xml version="1.0" encoding="utf-8"?>
<FxApplication xmlns="http://ns.adobe.com/mxml/2009">

 

        <Script>
                <![CDATA[
                        import mx.controls.Alert;
                        import flash.net.URLLoader;
                        import flash.net.URLRequest;
                        import mx.rpc.events.ResultEvent;
                        import mx.rpc.events.FaultEvent;
                        import mx.rpc.AbstractOperation;
                        import mx.rpc.remoting.RemoteObject;
                        
                        private function greetViaRemoting() : void
                        {
                                var ro : RemoteObject = new RemoteObject("GenericDestination");
                                ro.source = "sban.flexblog.HelloWorld";
                                ro.endpoint = "http://localhost:8080/weborb.wo";
                                
                                var op : AbstractOperation = ro.getOperation("greet");
                                op.addEventListener(
                                        ResultEvent.RESULT, 
                                        function(event : ResultEvent) : void
                                        {
                                                Alert.show( event.result.toString() ) 
                                        }
                                );
                                op.addEventListener(FaultEvent.FAULT,
                                        function(event : FaultEvent) : void
                                        {
                                                Alert.show(event.fault.toString());
                                        }
                                );
                                
                                op.send(vNameTxt.text);
                        }
                ]]>
        </Script>
        
        <VGroup>
                <FxTextInput id="vNameTxt" text="sban" />
                <HGroup>
                        <FxButton id="vSendBtn2" label="remoting greet"click="greetViaRemoting()" />
                </HGroup>
        </VGroup>
        
</FxApplication>

修改app_flexblog_client的输出目录为gapp_flexblog的输出目录,即${DOCUMENTS}\gapp_flexblog\war。两个项目现在合二为一了,重新编译一下。这样以后再测试项目,就不用运行两个project了。
helloworld-entry

helloworld-alert

ok,上面弹出窗所示,正是服务端返回的内容。单出eclipse工具栏中的Deploy Google App按纽。发布完毕,运行,不好,出现了error:

endpoing-error.jpg

这是由于我们把endpoint写成了本地测试地址(http://localhost:8080/weborb.wo)所致。既然现在Flex程 序的输出目录与server端输出目录是相同的,我们可以把endpoing修改为”/weborb.wo”,修改后局部代码如下:

var ro : RemoteObject = new RemoteObject("GenericDestination");
ro.source = "sban.flexblog.HelloWorld";
ro.endpoint = "/weborb.wo";

编译,再次布署,访问,现在已经没有问题了:

remote-ok.jpg

helloworld-alert

远程也可以了,但是,
1,为什么RemotingObject的destination要用GenericDestination?为什么endpoint是/weborb.wo?
2,WEB-INF/flex目录下的四个文件的作用是什么?
3,为什么要拷贝weborb的三个jar文件到lib目录下?

等等类似的与Remoging有关的问题,准备下一课一探究竟。既然使用Remoting,我希望对server端,对client端的配置及工作原理都是一清二楚的。

 

上一课:flex4 lesson 2 : dynamic hello world interacting with server
本课源码:flex4-lesson3.zip

[设计模式]我的实用设计模式之四-Simple Factory,Factory Method和Abs

mikel阅读(740)

Simple Factory

先从Simple Factory开始讲起,假设模拟一个电玩店的试玩系统,这个电玩店专卖出售PS3的游戏机和提供试玩服务,当一个用户想试玩的时候,需要选择一种游戏类 型进行试玩,系统会选择生成其中一个游戏盘的对象:竞赛游戏(PS3RacingGameDisk),射击游戏 (PS3ShootingGameDisk)以及格斗游戏(PS3FightingGameDisk),这些游戏盘子类都分别继承自同一个游戏盘抽象类 AbstractGameDisk。

图1

    public abstract class AbstractGameDisk
    {
        
public string Name { getset; }
        
public abstract void Load();
    }
    
public class PS3RacingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load PS3 racing game.");
        }
    }
    
public class PS3ShootingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load PS3 shooting game.");
        }
    }
    
public class PS3FightingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load PS3 fighting game.");
        }
    }
    
public enum GameDiskType
    {
        RACING,
        SHOOTING,
        FIGHTING
    }
    
public class PS3Player
    {
        
public void PlayAGame(GameDiskType type)
        {
            
//Get a console
            
//Get a joystick
            
//create a game disk
            AbstractGameDisk disk; 
            
switch (type)
            {
                
case GameDiskType.RACING:
                    disk 
= new PS3RacingGameDisk();
                    
break;
                
case GameDiskType.SHOOTING:
                    disk 
= new PS3ShootingGameDisk();
                    
break;
                
case GameDiskType.FIGHTING:
                    disk 
= new PS3FightingGameDisk();
                    
break;
                
default:
                    disk 
= null;
                    
break;
            }
            
//insert disk to console
            
//load game 
            
//play and enjoy it
        }
    }

代码1

从上述代码看,如果我们需要增加新的游戏盘,例如角色扮演游戏(RolePlayGameDisk),那么生成游戏盘部分(见注释create a game disk处)需要增加case分支,这里的对具体游戏盘对象实例化存在着变化的需求。根据设计原则 "封装变化(Encapsulate what varies)" 对这一对象实例化的需求进行封装。 引入一个新的类来封装和处理对象生成的需求,这个类叫做PS3DiskFacotry。 

    public class PS3Player
    {
        
public void PlayAGame(GameDiskType type)
        {
            
//Get a console
            
//Get a joystick
            
//create a game disk
            AbstractGameDisk disk = PS3DiskFactory.CreateGameDisk(type); 
            
            
//insert disk to console
            
//load game 
            
//play and enjoy it
        }
    }
    
public sealed class PS3DiskFactory
    {
        
public static AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
switch (type)
            {
                
case GameDiskType.RACING:
                    
return new PS3RacingGameDisk();
                
case GameDiskType.SHOOTING:
                    
return new PS3ShootingGameDisk();
                
case GameDiskType.FIGHTING:
                    
return new PS3FightingGameDisk();
                
default:
                    
return null;
            }
        }
    }

代码2
从上面的代码看PS3DiskFactory专门负责游戏盘(AbstractGameDisk的具体子类)的实例化过程,当有新的 游戏盘增加时,也就是实例化过程的需求发生变化时,全部变化会单独发生在PS3DiskFactory里面,也就是可变化的需求被封装到一个类里面了,这 就是Simple Factory的实现。

图2
由于对实例化需求的变化的封装,从图可见PS3DiskManager和具体的游戏盘类 (PS3RacingGameDisk,PS3ShootingGameDisk和PS3FightingGameDisk等)彻底的解 耦,PS3DiskManager只是依赖于他们共同的抽象类AbstractGameDisk和工厂类PS3DiskFactory。Simple Factory的作用就是用来封装“对象实例化可变化的需求”。

Factory Method

随着这个电玩店的发展,店里开始支持Wii游戏机销售和试玩,原先的系统需要更新符合这一新需求。参考Simple Factory的实现,我们可以很快速的实现Wii的需求。

    public class WiiPlayer
    {
        
public void PlayAGame(GameDiskType type)
        {
            
//Get a console
            
//Get a joystick
            
//create a game disk
            AbstractGameDisk disk = WiiDiskFactory.CreateGameDisk(type); 
            
            
//insert disk to console
            
//load game 
            
//play and enjoy it
        }
    }
    
public sealed class WiiDiskFactory
    {
        
public static AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
switch (type)
            {
                
case GameDiskType.RACING:
                    
return new WiiRacingGameDisk();
                
case GameDiskType.SHOOTING:
                    
return new WiiShootingGameDisk();
                
case GameDiskType.FIGHTING:
                    
return new WiiFightingGameDisk();
                
default:
                    
return null;
            }
        }
    }
 
 
public class WiiRacingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load Wii racing game.");
        }
    }
    
public class WiiShootingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load Wii shooting game.");
        }
    }
    
public class WiiFightingGameDisk : AbstractGameDisk
    {
        
public override void Load()
        {
            Console.WriteLine(
"Load Wii fighting game.");
        }
    }

代码3

图3
第一眼看是不是很容易实现了新的需求?Copy & Paste,稍稍修改一下就完了。可是有没有发现两个Player类除了对GameDisk的实例化以外,其他的一模一样。我们引进第二个设计原则 "面向抽象编程,而不是面向具体编程(Depend on Abstractions, not on Concretions)" ,增加对各个具体Player的抽象类AbstractPlayer.Client面向的是Player的抽象类而不是具体的Player。

    public abstract class AbstractPlayer
    {
        
public void PlayAGame(GameDiskType type)
        {
            
//Get a console
            
//Get a joystick
            
//create a game disk
            AbstractGameDisk disk = CreateGameDisk(type);
            
//insert disk to console
            
//load game 
            
//play and enjoy it
        }
        
protected abstract AbstractGameDisk CreateGameDisk(GameDiskType type);
    }
    
public class PS3Player : AbstractPlayer
    {
        
protected override AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
return PS3DiskFactory.CreateGameDisk(type);
        }
    }
    
public class WiiPlayer : AbstractPlayer
    {
        
protected override AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
return WiiDiskFactory.CreateGameDisk(type);
        }
    }

代码4

图4
引入对具体各个Players的抽象类AbstractPlayer后,在程序中可以声明AbstractPlayer的引用,而具体 的Player对象的实例化过程留给具体的Player类(PS3Player或者WiiPlayer)来实现。AbstractPlayer声明 CreateGameDisk方法负责实例化AbstractGameDisk的子类对象,具体的Player类负责实例化具体的 AbstractGameDisk的子类。这个Method(CreateGameDisk)的行为就是一个Factory,所以称为Factory Method。下面是一个典型的Factory Method的UML图。

图5
从图5和图4可以看,AbstractPlayer就是一个Creator,而PS3Player和WiiPlayer是一个 ConcreteCreator,CreateGameDisk()就是FactoryMethod(),AbstractPlayer只是定义实例化方 法,但是不知道具体如何实例化对象,实例化那个具体的对象,这些都是由PS3Player和WiiPlayer负责的。在上述的实现,PS3Player 和WiiPlayer是借助于Simple Factory来实例化对象。

Factory Method是一个推迟实例化的过程,在抽象类(Creator)定义实例化的行为(FactoryMethod),然后由具体的子类 (ConcreteCreator)决定具体实例化的对象。其实在OO中,抽象类负责定义行为(在C#中为Method或者Property),子类负责 实现行为,运行时动态调用不同行为称为 多态(polymorphism)。但是构造函数不能实现多态,Factory Method就是解决对象实例化的多态问题。Factory Method的别名也叫Virtual Constructor,为什么这样叫了,因为在C++里面实现多态都要定义Virtual Function(虚函数),但是Constructor是不能定义为virtual的,Factory Method恰恰解决这个问题,所以也就Virtual Constructor了。

Abstract Factory

上面讲述的Simple Factory和Factory Method解决了游戏盘(GameDisk)的对象的实例化过程,如果在Player中其他使用到的引用(例如游戏主机GameConsole和手柄 Joystick)都需要实现实例化不同的对象。那么就不仅仅需要CreateGameDisk(),而且需要CreateGameConsole()和 CreateJoystick()来实例化具体的对象。抽象类AbstractPlayer组合这些Factory Methods,实现如下。

    public abstract class AbstractPlayer
    {
        
public abstract AbstractGameDisk CreateGameDisk(GameDiskType type);
        
public abstract AbstractGameConsole CreateGameConsole();
        
public abstract AbstractJoystick CreateJoystick();
    }
    
public class PS3Player : AbstractPlayer
    {
        
public override AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
return PS3DiskFactory.CreateGameDisk(type);
        }
        
public override AbstractGameConsole CreateGameConsole()
        {
            
return new PS3Console();
        }
        
public override AbstractJoystick CreateJoystick()
        {
            
return new PS3Joystick();
        }
    }
    
public class WiiPlayer : AbstractPlayer
    {
        
public override AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
return WiiDiskFactory.CreateGameDisk(type);
        }
        
public override AbstractGameConsole CreateGameConsole()
        {
            
return new WiiConsole();
        }
        
public override AbstractJoystick CreateJoystick()
        {
            
return new WiiJoystick();
        }
    }
    
public abstract class AbstractGameDisk
    {
        
public string Name { getset; }
        
public abstract void Load();
    }
    
public abstract class AbstractGameConsole
    {
        
public void InsertGameDisk(AbstractGameDisk disk) { }
        
public void PluginJoystick(AbstractJoystick joystick) { }
    }
    
public abstract class AbstractJoystick
    {
    }
    
public class PS3Console : AbstractGameConsole
    {
    }
    
public class PS3Joystick : AbstractJoystick
    {
    }
    
public class WiiConsole : AbstractGameConsole
    {
    }
    
public class WiiJoystick : AbstractJoystick
    {
    }

代码5
AbstractPlayer不再负责PlayAGame的功能,只是声明了一系列产品的实例化的Methods。AbstractPlayer还是Factory Method。 我们需要实现PlayAGame的功能,基于设计原则"组合优于继承(Favor Object composition over inheritance)",我们定义一个新的类(Player),然后把工厂类(AbstractPlayer)作为一个引用组合到这一个类里面,这就是Abstract Factory模式的实现。

 

    public enum GameDevice
    {
        PS3,
        WII
    }
    
public class Player
    {
        
private AbstractPlayer playerFactory;
        
public Player(GameDevice device)
        {
            
switch (device)
            {
                
case GameDevice.PS3:
                    playerFactory 
= new PS3Player();
                    
break;
                
case GameDevice.WII:
                    playerFactory 
= new WiiPlayer();
                    
break;
            }
        }
        
public void PlayAGame(GameDiskType type)
        {
            
//Get a console
            AbstractGameConsole console = CreateGameConsole();
            
//Get a joystick
            AbstractJoystick joystick = CreateJoystick();
            console.PluginJoystick(joystick);
            
//create a game disk
            AbstractGameDisk disk = CreateGameDisk(type);
            
//insert disk to console
            console.InsertGameDisk(disk);
            
//load game 
            
//play and enjoy it
        }
        
private AbstractGameDisk CreateGameDisk(GameDiskType type)
        {
            
return playerFactory.CreateGameDisk(type);
        }
        
        
private AbstractGameConsole CreateGameConsole()
        {
            
return playerFactory.CreateGameConsole();
        }
        
private AbstractJoystick CreateJoystick()
        {
            
return playerFactory.CreateJoystick();
        }
    }

代码6 

图6

以下为一个经典Abtract Factory的实现,用于对比参考

图7

Player(Client)完全不知道GameConsole,Joystick和GameDisk到底如何实例化的,这些都又具体工厂 (PS3Player或者WiiPlayer)来负责一系列相关联的产品(也就是对象)的实例化,这一系列相关联的产品称为产品族(product family)。Player根据GameDevice借助AbstractPlayer实例化产品族,产品族下的所有产品具体协同工作,而Player 只是依赖于产品组的产品的抽象类而不是具体类,也就是说,产品族的替换不会影响Player类的PlayAGame()。这一特性适合可替换产品族的设 计,例如不同桌面主题的设计和不同数据库访问组件的设计,ADO.net的数据库访问层就是基于Abstract Factory模式设计的。

Player的构造函数中选择具体工厂也是用了条件选择(switch),这里可以通过Simple Factory来对具体工厂的实例化。

Abstract Factory有几个特点:
1.每次产生一系列相关联的产品,例如 WiiGameConsole,WiiJoystick和WiiRacingGameDisk等等,他们之间是协调工作,例如CreateGame有 InsertGameDisk方法,表示WiiGameConsole和WiiRacingGameDisk协调工作。
2.不同产品族直接的产品不可以相互替换,例如PS3Joystick不能用于WiiGameConsole。
3. 使用Abstract Factory一般在产品族相当固定的情景下,例如XBox游戏机也是有GameConsole,Joystick和GameDisk,那么实现 XBoxPlayer等产品族就可以支持XBox游戏机,但是如果需要更改产品族的产品,例如某新型游戏机不使用GameDisk而是使用 HardDisk来Load游戏的话,那么现有的设计就不能支持这一新型游戏机。

这些我对Simple Factory,Factory Method和Abstract Factory的想法,欢迎指教。
   

Jake's Blog in 博客园 — 精简开发 无线生活

[JQuery]扩展JQuery Ajax请求错误机制,实现服务器端消息回馈。

mikel阅读(769)

JQuery使我们在开发Ajax应用程序的时候提高了效率,减少了许多兼容性问题,但时间久了,也让我们离不开他。比如简单的JQuery Ajax请求的封装让我们忘却了最原始的XmlHttpRequest对象和他的属性,方法,也让我们远离事情的真相。

在Ajax项目中,经常遇到需要服务器端返回错误的消息提示,或者消息码之类的数据。查过一些帮助,解决方案,很多网站是返回错误的消息JSON数据或者 脚本,这种方式当我们用JQuery.ajax()的时候出现了问题,jQuery.ajax()回调函数success(data)的data参数可能 是 xmlDoc, jsonObj, html, text, 等等…这取决于我们dataType设置和MIME.很多时候我们的错误处理都是统一的处理,不管你请求的是XML,JSON…。不光不好统一, 还容易出现解析错误等等情况。

参考了Prototyp框架的做法,做了一个jQuery的错误扩展。

原理:Prototype思路是把服务器处理结果状态信息写在Header里面,这种方式既保证了reponse body的干净,同时适应XML,JSON,HTML,Text的返回情况。

   服务器端只需要 Response.AddHeader("Error-Json""{code:2001,msg:'User settings is null!',script:''}");

实现:为了不影响原有的jQuery.ajax方法,同时不破坏jquery库源文件,做了如下扩展,代码很简单都能看懂:

;(function($){
    
var ajax=$.ajax;
    $.ajax
=function(s){
        
var old=s.error;
        
var errHeader=s.errorHeader||"Error-Json";
        s.error
=function(xhr,status,err){
            
var errMsg = window["eval"]("(" + xhr.getResponseHeader(errHeader) + ")");
            old(xhr,status,errMsg
||err);
        }

        ajax(s);
    }

})(jQuery);

 

使用方法:

  服务器端:我们是对错误进行扩展,如果要让jquery.ajax破获错误,必须要服务器端返回非200的错误码,由于Opera浏览器下面对 400以上的错误码,都无法获得请求的Header,建议如果要支持Opera,最好返回30*错误,这是Opera能接受Header的错误范围。没有 做包装,可以再单独把Catch内容出来。

try {
                context.Response.Write(GetJson(context));
                
throw new Exception("msg");
            }

            
catch {
                context.Response.ClearContent();
                context.Response.StatusCode 
= 300;
                context.Response.AddHeader(
"Error-Json""{code:2001,msg:'User settings is null!',script:''}");
                context.Response.End();
            }

  客户端:

  

$.ajax({
            url: 
this.ajaxUrl,
            type: 
"POST",
            success: callback,
            error: 
function(xhr,status,errMsg){
        alert(errMsg.code
+"<br/>"+errMsg.msg);
            }

        }
);

也许不是最好的,但觉得用起来很方便,忘了个写新增参数errorHeader:"Error-Json",这个header头key根据你后台设定配置。