[C#]揭示常见的重构误区

mikel阅读(805)

揭示常见的重构误区

作者 Danijel Arsenovski译者 张逸 发布于 2008年11月3日 下午10时49分

社区
.NET,
Agile
主题
编程,
工件和工具,
敏捷技术
标签
重构

公正地说,.NET社区对于重构技术的研究起步太晚。直到今天,.Net开发的旗舰产品Visual Studio仍然无法在C#中突破重构的界限(http://www.martinfowler.com /articles/refactoringRubicon.html)。Visual Basic以及最新的C++情况略好,但却需要你下载和安装一个免费的重构插件Refactor!,它是Developer Express为VB或C++开发的。

之后的所有替代品都不再是免费的晚餐。虽然这些产品完全配得上你的投入,然而当我们开始关注那些诸如“代码质量”等虽非必要却极为深奥的要素,并达 成一致意见时,这些产品却难以成为开发者的主流工具。即使不使用工具,你仍然可以进行重构,但手工方式会由于太过复杂而会将开发者拒之门外。无怪 乎.Net社区对重构的引入会大大地滞后,因为我们对于重构的所有问题及其作用,依旧混乱不堪。

本文试图列出一些我经常遇到的使用重构的误区。这些误区与某些传统的对编程的偏执一样,总是会成为吸取技术精华的壁垒。紧接着,我还会列举某些先入 为主的误解,试图阐释其起源,并给出有力的证据驳斥这些论点。我希望本文能为每个人澄清对重构本质的怀疑,让他们学会成为一个重构者,或者在他的团队中建 立并推广这种实践。

“如果没有坏,就不要修复它”

这句话老少相传,可谓工程智慧的真实写照,然而,“如果没有坏,就不要修复它”只会滋生得过且过的情绪。重构有足够充分的理由来摒弃这种思想。

在你的编程生涯的早期,一定明白“千里之堤,溃于蚁穴”的道理,并为之而付出过深深地代价。即使一个细微的变化,都会导致软件在最糟糕的时刻以令人 莫名惊 诧的方式而停止运转。一朝被蛇咬,十年怕井绳,故而你害怕任何变化的发生,只要变化不是必须的。然而这只能够苟延残喘维持一时。一旦形势急转直下,出现的 错误不得不解决,新的功能需求也不能一拖再拖了。此时你面对的代码即使是相同的,但实际却已养成了大患。

那些接受了“如果没有坏,就不要修复它”信条的开发人员认定重构是可有可无的,甚至会干扰既定的开发目标。实际上,这种试图维持“现状”的顺从姿态,来源于某种心理,他们认为对代码的恐惧,以及无法掌控的事实都是合理的。

许多经验丰富的程序员之所以认可这种观点,是因为对一些不必要工作“偷工减料”,乃人之常情,是合情合理的。譬如说,如果应用程序已经具备优越的性 能,就无需为了性能对处理器周期耗费心机。类似的投机性设计,通常会用来搪塞那些具有前瞻性的编程观点,诸如“我们可能会在将来的某一天需要这一特性”。

从这个意义上讲,重构需要随时进行。在重构时,你需要消除冗余代码,避免投机性设计或预先优化。

而对于重构的老手来讲,这样的软件完全是“金玉其外,败絮其中”。如果设计有瑕疵,例如拙劣的代码和糟糕的结构,那么在软件外部是看不到这些问题 的。然而 即使应用程序在某个时候能够正常运行,我们仍然需要对此进行重构,对设计进行优化。从这个意义上讲,重构坚持在一些不太明显,但却具有决定性作用的特征上 作文章,例如设计、简单性,以及改善源代码的可读性,便于理解。

重构可以帮助你赢回对代码的支配权。这对于那些业已脱离控制的代码库而言并非易事,如果不诉诸于重构,那么唯一的解决之道就是彻底地重写。

重构不是新生事物

换而言之,这种误解可以这样说:“重构无非就是一种新瓶装旧酒的说辞罢了。”这意味着你对于如下种种早已熟谙于心:编写好的代码,面向对象设计,编码风格,最佳实践,如此等等,不一而足。重构无非是某些人的故作玄虚之语,编造出来用以兜售自己的新书,如此而已。

对,重构从一诞生之初,就从未放言像面向对象或面向方面编程那般会成为一种划时代的全新模式。它仅仅是从根本上改变你编码的方式:它定义了一些规 则,使得利用工具(例如点击按钮)完成代码的复杂转换成为可能。你不应将代码看作是不易修改的僵化的结构。相反,你应该看到自己有能力维持代码总是恰当好 处,有效地应对新的挑战,而无需害怕修改代码。

重构是高科技

编程并不易为。这是一项复杂活动,需要大量的知识积累。某些知识可能很难掌握。VB程序员如果要掌握Visual Basic .NET,必须具备熟练运用面向对象语言的能力。对于多数人而言,这是一大困惑。而其好处则在于学习一门新的技能绝对是物有所值的。

重构的伟大之处就在于它的简单。只需了解很小的一套简单规则,你就可以“盛装出发”了。再加上一个好的工具,迈开重构的第一步简直就是轻而易举。与 当前一 个高级程序员应该了解的其他技术相比,例如UML或设计模式,我得说重构有着最简单的学习曲线,就像VB和其他编程语言相比一样。

学习重构很快就会迎来你的收获季节。当然,与世间万事万物相同,知识的学习总是“一份耕耘,一份收获”的。

重构会导致性能低下

复杂点儿的说法是“因为重构通常会引入大量的细粒度元素(如方法和类),这种间接的设计会导致性能的损失。”

让我们把时钟往回调一小段,你会发现这样的观点似曾相识,当初在质疑面向对象编程时,就发出过类似奇怪的声音。

事实的真相是代码结构重构与否,在性能上的区别微乎其微,所以常常可以忽略不计,除非是某些特别的系统。

经验证明,性能总是受制于某一段确定的代码。在优化阶段修复它们,可以获得你需要的性能等级。能够轻易地识别关键代码是重中之重。减少代码的重复与数量,从而使得代码易于理解,一旦发生变化,也只会影响到单独的模块,这样的重构极大地改善了优化的过程。

当我们发现在一段日子里,CPU好似上足了马力一般,使用率不停上升,而代码的其他特性,例如可维护性、质量、可伸缩性以及可靠性又使得我们不得不将性能置诸脑后。而现在,我们再也不能以这样的理由为编写出性能糟糕的代码寻求托辞了,当然,我们也不能矫枉过正。

面临这样的境况,你可以看看本文提供的一些数据,也算是我的一点小小经验。我会使用两个代码示例。第一个例子代码结构简陋,且只有一个单独的 Main方 法。第二个例子的Main方法则被放到一个模块中,其中定义的一个类Circle包含了几个细粒度的方法。最初,我使用这些示例仅仅是为了演示非结构化代 码与结构化代码的编码风格,因而缺乏一些用于量化的代码。

我计算了执行一个简单几何公式(求圆周长)的时间,为了避免编写一些涉及计算敏感应用的代码,我增加了一些数据库查询代码。为了量化值的准确性,我 将执行放入到一个循环中,重复执行10000次。由于我并没有打算获得极端精确的值,因此我使用了 System.Diagnostics.Stopwatch类用以捕捉耗时值,毕竟在这个案例中,Stopwatch的值已经足够精确了。

代码示例1:非结构化代码

Option Explicit On
Option Strict On
Imports System.Diagnostics
Imports System.Data.SqlClient
Namespace RefactoringInVb.Chapter9
Structure Point
Public X As Double
Public Y As Double
End Structure
Module CircleCircumferenceLength
Sub Main()
Dim center As Point
Dim pointOnCircumference As Point
'read center coordinates
Console.WriteLine("Enter X coordinate" + _
"of circle center")
center.X = CDbl(Console.In.ReadLine())
Console.WriteLine("Enter X coordinate" + _
"of circle center")
center.Y = CDbl(Console.In.ReadLine())
'read some point on circumference coordinates
Console.WriteLine("Enter X coordinate" + _
"of some point on circumference")
pointOnCircumference.X = CDbl(Console.In.ReadLine())
Console.WriteLine("Enter X coordinate" + _
"of some point on circumference")
pointOnCircumference.Y = CDbl(Console.In.ReadLine())
'calculate and display the length of circumference
Console.WriteLine("The lenght of circle" + _
"circumference is:")
'calculate the length of circumference
Dim radius As Double
Dim lengthOfCircumference As Double
Dim i As Integer
'use stopWatch to measure transcurred time
Dim stopWatch As New Stopwatch()
stopWatch.Start()
'repeat calculation for more precise measurement
For i = 1 To 10000
'add some IO
Dim connection As IDbConnection = New SqlConnection( _
"Data Source=TESLATEAM;" + _
"Initial Catalog=RENTAWHEELS;" + _
"User ID=RENTAWHEELS_LOGIN;" + _
"Password=RENTAWHEELS_PASSWORD_123")
connection.Open()
Dim command As IDbCommand = New SqlCommand( _
"Select GETDATE()")
command.Connection = connection
Dim reader As IDataReader = command.ExecuteReader()
reader.Read()
reader.Close()
connection.Close()
radius = ((pointOnCircumference.X - center.X) ^ 2 + _
(pointOnCircumference.Y - center.Y) ^ 2) ^ (1 / 2)
lengthOfCircumference = 2 * 3.1415 * radius
Next
stopWatch.Stop()
Console.WriteLine(stopWatch.Elapsed)
Console.WriteLine(lengthOfCircumference)
Console.Read()
End Sub
End Module
End Namespace

代码示例2:结构化代码

Option Explicit On
Option Strict On
Imports System.Data.SqlClient
Namespace RefactoringInVb.Chapter11
Public Structure Point
Public X As Double
Public Y As Double
End Structure
Module CircleCircumferenceLength
Sub Main()
Dim circle As Circle = New Circle
circle.Center = InputPoint("circle center")
circle.PointOnCircumference = InputPoint( _
"point on circumference")
Console.WriteLine("The length of circle " + _
"circumference is:")
Dim circumference As Double
Dim i As Integer
'use stopWatch to measure transcurred time
Dim stopWatch As New Stopwatch()
stopWatch.Start()
'repeat calculation for more precise measurement
For i = 1 To 10000
circumference = circle.CalculateCircumferenceLength()
Next
stopWatch.Stop()
Console.WriteLine(stopWatch.Elapsed)
Console.WriteLine(circumference)
WaitForUserToClose()
End Sub
Public Function InputPoint(ByVal pointName As String) As Point
Dim point As Point
Console.WriteLine("Enter X coordinate " + _
"of " + pointName)
point.X = CDbl(Console.In.ReadLine())
Console.WriteLine("Enter Y coordinate " + _
"of " + pointName)
point.Y = CDbl(Console.In.ReadLine())
Return point
End Function
Private Sub WaitForUserToClose()
Console.Read()
End Sub
End Module
Public Class Circle
Private centerValue As Point
Private pointOnCircumferenceValue As Point
Public Property Center() As Point
Get
Return centerValue
End Get
Set(ByVal value As Point)
centerValue = value
End Set
End Property
Public Property PointOnCircumference() As Point
Get
Return pointOnCircumferenceValue
End Get
Set(ByVal value As Point)
pointOnCircumferenceValue = value
End Set
End Property
Public Function CalculateCircumferenceLength() As Double
QueryDatabase()
Return 2 * 3.1415 * CalculateRadius()
End Function
Private Function CalculateRadius() As Double
Return ((Me.PointOnCircumference.X - Me.Center.X) ^ 2 + _
(Me.PointOnCircumference.Y - Me.Center.Y) ^ 2) ^ (1 / 2)
End Function
Private Sub QueryDatabase()
Dim connection As IDbConnection = New SqlConnection( _
"Data Source=TESLATEAM;" + _
"Initial Catalog=RENTAWHEELS;" + _
"User ID=RENTAWHEELS_LOGIN;" + _
"Password=RENTAWHEELS_PASSWORD_123")
connection.Open()
Dim command As IDbCommand = New SqlCommand( _
"Select GETDATE()")
command.Connection = connection
Dim reader As IDataReader = command.ExecuteReader()
reader.Read()
reader.Close()
connection.Close()
End Sub
End Class
End Namespace

经过几次执行,可以看到两个例子的耗时相当接近。在我的机器上,它们的值在2.2到2.4秒之间。值得称许的一点细微差别是:非结构化示例的最小耗时为1.9114800,而结构化示例的则为2.0398497。在我看来,二者并无太大差异。

重构破坏好的面向对象设计

具有优美结构以及经过重构的代码,在菜鸟的眼里,是笨拙而粗陋的。方法是如此的短小,在他们看来简直言之无物。类显得不够重量级,仅仅包含屈指可数的几个成员。这样的代码看起来简直就等于没有嘛。

像类和方法那样,若要管理大量的元素,则意味着需要处理的复杂度会加大。

这样的观点常会引人误解。事实上,复杂度总是相同的。但重构后的代码会显得条理更清晰,结构更合理。

重构无法提供短期利益

一个占据主流的论调是重构可以使得你的程序更快。迄今为止,就我所知,并没有相关的研究(这是我强烈呼吁的)可以证明我的看法,但我的经验告诉我存 在这样的情形。这是唯一合乎逻辑的。由于你在整体上具有少量的代码,极少的重复以及清晰的意图,因此重构带来的益处很快就能彰显无遗,除非你处理的是一些 可有可无,也无任何实际意义的小规模代码。

重构只适宜敏捷团队

在敏捷方法学中,重构是被频繁提及的其中一项关键技术,因而通常的解释是,重构只有在遵循敏捷原则的团队中才能如鱼得水。

重构是敏捷团队不可或缺的

即使你的团队采取的方法别出蹊径,但在大多数时候,总是由你来负责管理编码方式,这时就是运用重构的时机。 其他的团队成员或管理方式可能会忽略这样一个事实,就是你需要不停地在IDE中使用“Refactor”选项。不管你的团队采取何种方法,都没有任何东西 可以阻止你对代码进行重构。如果你遵循小步重构,并在编码过程中定期执行,就能达到最佳的重构效果。某些实践例如严格的代码所有权或瀑布过程,可能会与重 构背道而驰。如果你能够证明重构从编程的视角来看是有意义的,你就可以开始构建你的支撑库,首先从你的伙伴开始,然后推广到整个团队。

重构可以在开发过程中作为独立的阶段实施,并由独立的团队执行

经理们通常对此深以为然。将重构视为一个独立的阶段,然后将其放在诸如实现阶段和测试阶段期间,从管理学的角度来看,容易给人一种错觉,认为重构就是甘特图的一根线条,可以轻易地将其压缩时间甚至移走。

事实上,为了成功地执行重构,你需要完整地理解整个问题域,了解需求、设计甚至实现阶段的细节。倘若你从一开始就没有将实施重构的人看作团队的一份子,也没有花时间与客户交流,分析需求和思考设计,你将很难改善最初的团队构建的内容。

遵循某种模型,则代码可以通过它在编码之后得到精化,就像工业生产过程中提炼出的某种物质那样,总会带来一些好处。若是不能切实地理解代码的意义, 则你真正能够对重构保有信心的只能是一些细微的改进。若在此种情形下,妄图通过重构使代码焕然一新,结果很有可能是南辕北辙,适得其反。代码若与问题域紧 密相关,就有可能使得事情变得更加糟糕,最终还会为你的应用程序引入bugs。

没有单元测试重构照样能够工作

我想,一些简单的重构可以在没有单元测试的情形下进行。重构工具与编译器自身可以提供一定的安全保障,不至于引入一些简单的人为错误。你也可以采用 传统方 式对代码进行测试,例如使用调试器或者执行功能测试。但这些手动的测试方法却是乏味而不值得信赖的。重构时,代码比以前对修改更为敏感与脆弱。若要避免不 必要的问题,则应添加NUnit单元测试放到项目中。在你执行每一小步重构时,就能够及时发现错误。

不要依赖于注释的观点未必正确

我敢担保这会导致某些看起来有趣的混乱与疑惑。毫无疑问,你已经千百次地被告知,在编写代码时一定要添加注释。作为一种好的编程实践,这种思想会帮 助别人理解你的代码。这通常意味着编码的方式是优秀的、有序的、专业的。因此,如果现在有人居然胆敢告诉你注释未必是一桩好事儿,你一定会大吃一惊。

添加注释的动机通常与代码重构一致。你应该竭力提高代码的可读性。在早期,编程工具受制于标识符的长度,故而注释成为了传达编程涵义的唯一选择。立 足于重 构,则要求代码是自解释性的,即选择正确的方法、类、变量以及其他标识符。同时,你应该避免为了相同的目的使用注释,因为注释不会被执行,且很容易作废。 在每日的编程成为急就章时,总是会忘记更新注释、文档、图示或其他次等级的工件。

匈牙利命名法怎么了?

并不只是Charles Simonyi的母语触发了创造匈牙利命名法的灵感。那些看起来像是匈牙利语的命名,例如a_crszkvc30LastNameCol和 lpszFile,并不会让我感到惊讶。如今,编译器会检查类型安全性,跟踪类型信息。在现代的IDE中,所有必要的类型信息都能被找到,并作为鼠标指针 的提示出现。在诸如C#和VB.Net的语言中,匈牙利命名法已经过时了。

然而,旧有的习惯总是很难改变。匈牙利命名法通常被看作是一种有效的然而却难以掌握的编程实践。难怪并非所有人都乐于看见自己掌握的传统命名规则原来已经过时。

使用那些类似人类的自然语言为变量命名,可以使得代码简明易懂。放弃那些有趣的前缀,改而使用别人能够轻松理解的词语吧。

结论

可以毫不夸张地说,重构是编程的一次变革,它从根本上改变了某些旧有的习惯。它必然会面对许多阻力,让不知所以者感到无比的困惑。

我希望本文能够为你打开重构之门。在你第一次展卷阅读时,不要惊讶于那些困扰你的问题。如果你正在倡导重构,或者试图向别人讲解重构,那么你应该时刻准备提出质疑。面对这种情形,我希望我已经提供了足够的论据,以证明采纳重构的原因与必要性是行得通的。

本文源于《Professional Refactoring in Visual Basic》一书,该书作者为Danijel Arsenovski,已由Wrox出版。

关于作者

Danijel Arsenovski是Wrox出版的《Professional Refactoring in Visual Basic》一书的作者。目前,他就职于Excelsys S.A(该公司为地区内的大量客户设计网上银行解决方案),担任产品和解决方案架构师。他对于重构的最初体验来自于对大型银行系统的整改, 从此,他就迷上了重构。他首创了利用重构完成代码从VB 6到VB.NET的升级。Arsenovski还是多个主要出版商的丛书作者,拥有微软认证解决方案开发专家(MCSD,Microsoft Certified Solution Developer)证书,并在2005年被提名为Visual Basic MVP。你可以通过电子邮件danijel.arsenovski@empoweragile.com与他取得联系,他的博客是 http://blog.vbrefactoring.com

查看英文原文:Debunking Common Refactoring Misconceptions

[Flash]Flash与3D编程探秘(六)- 全方位旋转摄像机

mikel阅读(770)

作者:Yang Zhou
感谢:Yunqing
日期:2008年11月

点此下载程序源文件

 

前面我们讨论过了如何横向旋转和移动摄像机,希望你已经完全理解,因为这本文中的内容是紧接着上一篇来的。回想一下你也许会意识到,在前面我们制做 的动画中,摄像机的旋转一直是围绕着y轴(竖直向上的轴)旋转,然而现实中我们可以上下旋转摄像机,甚至可以把摄像机倾斜一定角度,这就提醒了我们还需要 更深入的研究旋转这个课题。下面几个动画演示了我们摄像机(简单的摄像机轮廓)3种旋转模式,从左到右分别是横向旋转,纵向旋转,倾斜。从我们3D空间角 度来说,分别是沿y,x和z轴旋转。

      

横向和纵向旋转摄像机

倾斜摄像机

 

再介绍一些三角函数方程

事情变得复杂起来了!不过请你还是保持头脑清醒,这一篇文章你的任务就是学会纵向旋转和倾斜你的摄像机。你也许会想,我已经会了横向旋转摄像机,算法中我 们使用了一个panning的变量代表旋转角度,那么我再加两个变量,然后按顺序先沿x旋转,然后再沿y,最后z旋转不就好了。Well,这种想法很接近 但是是错的(不要把3D数学想的那么简单)。来看一下下面的两个图,我们还是以3D空间在2D平面上的投影举例,我们把线段OB沿着z轴(也就是2D平面 上 的原点)旋转大约78度好了,可以看到OB的长度就是我们的旋转半径。那么接下来,我们把OB沿y轴旋转,看一下图中的旋转半径是多少?不难看出,我们的 旋转半径明显的变小了。继续,如果你沿y旋转后,再打算沿x轴旋转,你还是会有同样“半径变化”的问题。

先沿z轴旋转再试图沿y旋转

 

在上面的例子中,如果我们想要保持旋转半径不变,那么一开始我们就不要沿z旋转。不过,我们拿着摄像机左转右转,怎可能保持角度不变!?因此你要在每一次摄像机旋转后,计算新的旋转半径。可是如果每一次我们都把旋转半径计算出来,那一定是很头疼!
于是我们聪明的想到了省力的方法。首先,让我们再看一下2D的三角函数(又是三角函数),我们根据旋转半径和旋转角度可以得到x和y:

= Math.cos(angle)*radius;
= Math.sin(angle)*radius;

需要注意一点,上面的方程式旋转点为原点,并且之前旋转角度为0。如果我们之前就有旋转角度a,再旋转b,那么我们的方程式就成了:

= Math.cos(a+b)*radius;
= Math.sin(a+b)*radius;

看起来眼熟,但是不知道是什么了?别担心:

cos(a+b) = cos(a)*cos(b)  sin(a)*sin(b);
sin(a
+b) = sin(a)*cos(b) + sin(b)*cos(a);

把cos(a+b)和sin(a+b)带入上面的x和y求值方程我们就有:

= radius*cos(a)*cos(b)  radius*sin(a)*sin(b);
= radius*sin(a)*cos(b) + radius*sin(b)*cos(a);

化简一下得到:

= x_before*cos(b)  y_before*sin(b);
= x_before*sin(b) + y_before*cos(b);

这样,我们使用上面两个方程,不用担心你在其他平面(xz或者yz平面)的旋转角度了,也不用每一次旋转后再去计算物体新的旋转半径了,我们只要关心旋转 后的x和y,并且把它们作为下一次旋转的x_before和y_before,问题就解决了。下面我把相应的方程式写上:
围绕y轴旋转pan角度:

= Math.cos(pan)*x_before  Math.sin(pan)*z_before;
= Math.sin(pan)*x_before + Math.cos(pan)*z_before;

围绕x轴旋转pitch角度:

= Math.cos(pitch)*y_before  Math.sin(pitch)*z_before;
= Math.sin(pitch)*y_before + Math.cos(pitch)*z_before;

围绕z轴旋转tilt角度:

= Math.cos(tilt)*x_before  Math.sin(tilt)*y_before;
= Math.sin(tilt)*x_before + Math.cos(tilt)*y_before;

那么基本的知识已经说完了。总结一下是,当你在使用这些方程式操作摄像机全方位旋转的时候,只要取得相应的变量,然后替换在方程里就可以了。是不是看起来 有点 难理解?不要担心,适应这些东西是需要花一点时间(特别是这些对你来说还是新课题的话),不过适应以后你应该就觉得很简单了。坦白的说,其实你并不需要知 道到底这些是怎样得来的,只要你知道如何使用它们得到你想要得结果就可以了(当然完全理解会对你以后的学习有一些帮助)。这些方程你可以写成一个函数,然 后命名它为“给我旋转”方程,当你需要摄像机旋转的时候,只要呼唤“给我旋转”就好了,至于“给我旋转”怎么做的工作,你就不需要担心了。

全方位旋转摄像机 

只说这些理论的东西,你肯定会觉得乏味,那么我举个例子来说明。下面这个程序演示了摄像机的全方位旋转,运行程序你会看到你置身在一个巨大的正方体 中,这个正方体是由很多我们的朋友小P组成的,不过这回我们用不同颜色的小P来代表不同的边。使用WS键控制纵向旋转,AD键控制横向旋转,QE控制倾斜 角度,鼠标点击屏幕禁止或者允许鼠标移动控制纵向和横向旋转。

全方位旋转摄像机,使用WS纵向旋转,AD横向旋转,QE倾斜角度,鼠标点击禁止或者允许鼠标控制

制作步骤:

1. 首先定义几个常量,MAX_OBJ是我们每条边上的物体数量,CUBE_WIDTH是我们正方体的边长。

// constants
var MAX_OBJ = 8;
var PI 
= 3.1415926535897932384626433832795;
var CUBE_WIDTH 
= 300;

 

2. 下面还是设置原点,场景,焦距等。

// same as usual
var origin = new Object();
origin.x 
= stage.stageWidth/2;
origin.y 
= stage.stageHeight/2;
origin.z 
= 0;
var scene 
= new Sprite();
scene.x 
= origin.x;
scene.y 
= origin.y;
this.addChild(scene);
var focal_length 
= 1400;

 

3. 设置摄像机,这回我们的摄像机多了几个新的属性,pitching是纵向旋转角度,tilt是倾斜的角度。

var camera = new Object();
camera.x 
= 0;
camera.y 
= 0;
camera.z 
= 0;
camera.panning 
= PI/8;                // init pan angle of our camera, pan left
camera.pitching = PI/8;               // pitch up
camera.tilt = 0;                           // and no tilt

 

4. 设置一些全局变量,在我们处理键盘和鼠标事件时会用到。

// global booleans for our keyboard control
var pan_left;
var pan_right;
var pitch_up;
var pitch_down;
var mouse_ctl 
= true;

5. 下面我们步置一个正方体,你完全不必要明白我是怎么布置场景的,因为布置场景的方式并不唯一,所以如果你愿意的话,你可以自己动手布置3D场景,当然我也 不介意直接拷贝我的设置场景的代码去用。总之,这些代码就是初始化一些小P,然后把它们摆放在合适位置(围绕着我们的摄像机)。

// ok, here you don't have to know how i acutally setup the cube
// cause every body has a different way of doing that, if you really
// interested in how i did it, then you may have a look
// you can just copy my code and it will set it up for you
var len = CUBE_WIDTH/2;
for (var seg = 0; seg < 3; seg++)
{
    var line_h;
    var line_v;    
    var line_z;
    
    
switch (seg)
    {
        
case 0:
            line_h 
= true;
            line_v 
= false;
            line_z 
= false;
            
break;
        
case 1:
            line_h 
= false;
            line_v 
= true;
            line_z 
= false;
            
break;
        
case 2:
            line_h 
= false;
            line_v 
= false;
            line_z 
= true;
            
break;
    }
    
    
for (var i = 0; i < MAX_OBJ; i++)
    {
        
if (line_h || (i == 0 || i == MAX_OBJ1))
        {
            
for (var j = 0; j < MAX_OBJ; j++)
            {
                
if (line_v || (j == 0 || j == MAX_OBJ1))
                {
                    
for (var k = 0; k < MAX_OBJ; k++)
                    {
                        
if (line_z || (k == 0 || k == MAX_OBJ1))
                        {
                            var ball;
                            
if (line_h)
                            {
                                ball 
= new SphereHorizontal();
                                
if ((i == 0 || i == MAX_OBJ1))
                                {
                                    ball 
= new SphereVertex();
                                }
                                
else
                                {
                                    ball 
= new SphereHorizontal();
                                }
                            }
                            
if (line_v)
                            {
                                ball 
= new SphereVertical();
                                
if ((j == 0 || j == MAX_OBJ1))
                                {
                                    
continue;
                                }
                            }
                            
if (line_z)
                            {
                                ball 
= new SphereStraight();
                                
if ((k == 0 || k == MAX_OBJ1))
                                {
                                    
continue;
                                }
                            }
                            ball.x_3d 
= len + (i)*(CUBE_WIDTH/MAX_OBJ);
                            ball.y_3d 
= len + (j)*(CUBE_WIDTH/MAX_OBJ);
                            ball.z_3d 
= len + (k)*(CUBE_WIDTH/MAX_OBJ);
                            scene.addChild(ball);
                        }
                    }
                }
            }
        }
    }
}

6. 下面的函数就是刷新小P位置和大小的函数,这也是这篇文章主要讲述的内容,所以,请集中。OK,首先我们要得出小P(其中一个小P)到摄像机的x,y和z 距离。对于横向旋转角度panning,我们使用本文前面讲述的方程,把相应的x距离和z距离带入,然后我们得出新的x距离和z距离。使用相同的方法得出 纵向旋转角度pitching后的y和z距离,对摄像机倾斜角度tilt我们在次使用上述方程。这样我们就得到围绕三个轴旋转后新的x,y和z距离,继而 我们便可以使用老办法算出物体的缩放和移动。最后别忘记加一个z_near变量,存储小P到摄像机的距离,以便于我们对所有小P进行z排序。

// update ball size and position
// here is what we really care about, so concentrate
function display(obj)
{    
    var x_distance 
= obj.x_3d  camera.x;         // first we determine x distance ball to camera
    var y_distance = obj.y_3d  camera.y;        // y
    var z_distance = obj.z_3d  camera.z;        // z distance
    
    var tempx, tempy, tempz;                       
// some temporary variables
    
    
// two more trig you need to know about, suppose a is the previous angle
    
// cos(a+b) = cos(a)*cos(b) – sin(a)*sin(b)
    
// sin(a+b) = sin(a)*cos(b) + cos(a)*sin(b)
    
// thus we have the following
    var angle = camera.panning;
    tempx 
= Math.cos(angle)*x_distance  Math.sin(angle)*z_distance;
    tempz 
= Math.sin(angle)*x_distance + Math.cos(angle)*z_distance;
    x_distance 
= tempx;
    z_distance 
= tempz;
    
    angle 
= camera.pitching;                    // the same thing we have for pitch angle
    tempy = Math.cos(angle)*y_distance  Math.sin(angle)*z_distance;
    tempz 
= Math.sin(angle)*y_distance + Math.cos(angle)*z_distance;
    y_distance 
= tempy;
    z_distance 
= tempz;
    
    angle 
= camera.tilt;                          // and tilt angle
    tempx = Math.cos(angle)*x_distance  Math.sin(angle)*y_distance;
    tempy 
= Math.sin(angle)*x_distance + Math.cos(angle)*y_distance;
    x_distance 
= tempx;
    y_distance 
= tempy;
    
    
if (z_distance > 0)                                           // if the ball isin front of the camera
    {
        
if (!obj.visible)                                
            obj.visible 
= true;                                    // make the ball visible anyway
            
        var scale 
= focal_length/(focal_length+z_distance);   // cal the scale of the ball
        obj.x = x_distance*scale;                             // calcualte the x position in a camera view 
        obj.y = y_distance*scale;                            // and y position
        obj.scaleX = obj.scaleY = scale;                    // scale the ball to a proper state
    }
    
else
    {
        obj.visible 
= false;
    }
    
    obj.z_near 
= z_distance;            // keep track of z distance to our camera
}

 

7. 写一个循环函数,不停的调用第6步的函数刷新所有的小P。如果你一直有看文章的话那么这些对你来说应该不难。

// loop to update the screen
function run(e:Event)
{
    
for (var i = 0; i < scene.numChildren; i++)                  // update all the balls on the screen
    {
        display(scene.getChildAt(i));
    }
    
    swap_depth(scene);
}
// bubble sort algo
function swap_depth(container:Sprite)
{
    
for (var i = 0; i < container.numChildren  1; i++)
    {
        
for (var j = container.numChildren  1; j > 0; j)
        {
            
if (Object(container.getChildAt(j1)).z_near < Object(container.getChildAt(j)).z_near)
            {
                container.swapChildren(container.getChildAt(j
1), container.getChildAt(j));
            }
        }
    }
}

 

8. 最后是设置一些键盘和鼠标事件响应函数,完成我们的程序。

function key_down(e:KeyboardEvent):void
{
    
if (e.keyCode == 65)            // a
        pan_left = true;
    
if (e.keyCode == 68)            // d
        pan_right = true;
    
if (e.keyCode == 87)            // w
        pitch_up = true;
    
if (e.keyCode == 83)            // s
        pitch_down = true;
}
function key_up(e:KeyboardEvent):
void
{
    
    
if (e.keyCode == 65)
        pan_left 
= false;
    
if (e.keyCode == 68)
        pan_right 
= false;
    
if (e.keyCode == 87)
        pitch_up 
= false;
    
if (e.keyCode == 83)
        pitch_down 
= false;
}
function key_response(e:Event):
void
{
    
if (pan_left)
        camera.panning 
-= 0.01;
    
if (pan_right)
        camera.panning 
+= 0.01;
    
if (pitch_up)
        camera.pitching 
-= 0.01;
    
if (pitch_down)
        camera.pitching 
+= 0.01;
        
    
if (mouse_ctl)                                // if allow mouse control pan and pitch
    {
        camera.panning 
+= scene.mouseX/22000;
        camera.pitching 
+= scene.mouseY/22000;
    }
    
    
// limit the pitch and tilt
    if (camera.pitching < 1*PI/3)
        camera.pitching 
= 1*PI/3;
    
if (camera.pitching > PI/3)
        camera.pitching 
= PI/3;
}
function clicked(e:Event)                        
// toggle mouse control
{
    mouse_ctl 
= !mouse_ctl;
}
// setup event listeners
this.addEventListener(Event.ENTER_FRAME, run);
this.addEventListener(Event.ENTER_FRAME, key_response);
stage.addEventListener(KeyboardEvent.KEY_DOWN, key_down);
stage.addEventListener(KeyboardEvent.KEY_UP, key_up);
stage.addEventListener(MouseEvent.CLICK, clicked);

总结一下,这篇文章中的三角函数部分可能有一些抽象,因为我们是在3D空间中完成的。不过还是那句话,不要担心,你只要知道如何使用这些方程就可以了。

注意:物体自身围绕中心的3D旋转

有一点,不知道你有没有注意,那就是在本文的开头,我做了几个摄像机的旋转演示。在演示里,摄像机(物体)都是本身在旋转,而并不是我们的眼睛(摄像机) 在旋转。虽然到目前为止的文章里,还没有讨论到如何让物体自身旋转,不过我们很快就会看到一些例子。我做这些演示唯一想说明的就是三种旋转的机制,所以不 要着急那些演示是怎么做出来的,会慢慢好起来的。

其实,关于Flash和3D空间的基本知识的介绍到这里我想应该结束了,从下篇文章开始我们就要关注3D物体,因此,如果你对前面文章中的基础知识还是模 模糊糊的话,你完全可以不必担心。不过我还是建议你自己多实验一些小例子,增加自己的空间感。相信你在不久的将来开发自己的3D Engine,加油,ALL THINGS ARE POSSIBLE!

点此下载程序源文件


作者:Yang Zhou
出处:http://yangzhou1030.cnblogs.com
感谢:Yunqing
本文版权归作者和博客园共有,转载未经作者同意必须保留此段声明。请在文章页面明显位置给出原文连接,作者保留追究法律责任的权利。

[C#]项目管理实践【三】每日构建【Daily Build Using CruiseControl.

mikel阅读(748)

在上一篇项目管理实践教程二、源代码控制【Source Control Using VisualSVN Server and TortoiseSVN】中我们已经讲解了如何使用TortoiseSVN和VisualSVN Server来做简单的版本控制,这一篇我们将会讲解使用CruiseControl.NET和MSBuild来搭建每日构建系统。

在第一篇项目管理实践教程一、工欲善其事,必先利其器【Basic Tools】 中我们已经安装了CruiseControl.NET 1.4,因为我们还要用到MSBuild,所以如果你的系统没有安装Visual Studio,那么你需要首先安装Visual Studio 2005/2008,我们在这里使用的是Visual Studio 2008,准备好这些了吗?OK,我们正式开始今天的课程! 

首先,我们要配置CruiseControl.NET【下面简写为CCNET】,配置完成后,我们每次提交源代码到SVN服务器后,CCNET就可 以自动从SVN服务器上迁出源代码,并调用MSBuild自动进行编译。我们以昨天的教程中创建的StartKit项目为实例,先看看下面的配置文件:


  1 <cruisecontrol xmlns:cb="urn:ccnet.config.builder">
  2 <!–项目名称–>
  3 <name>StartKit</name>
  4 <!–标示类型,有多种类型。下面为默认标示,作为每次编译时生成的日志文件的名称–>
  5 <labeller type="defaultlabeller">
  6     <!–前缀–>
  7      <prefix>StartKit-1-</prefix>
  8     <!–编译失败时是否增加–>
  9      <incrementOnFailure>false</incrementOnFailure>
 10     <!–格式–>
 11      <labelFormat>00000</labelFormat>
 12 </labeller>
 13 <!–项目的WebDashboard地址,CruiseControl.NET包括二部分,一是Server用来配置项目和监视文件修改,二是WebDashboard,是一个显示项目信息及编译信息的Website–>
 14     <webURL>http://202.196.96.55:8080/server/local/project/StartKit/ViewProjectReport.aspx</webURL>
 15 <!–触发器,包含多种,有兴趣可以查看官方文档–>
 16 <triggers>
 17 <!–时间间隔触发器,下面是60秒触发一次–>
 18       <intervalTrigger seconds="60" />
 19 </triggers>
 20 <!–如果发现修改,延迟多久开始编译,下面是2秒–>
 21 <modificationDelaySeconds>2</modificationDelaySeconds>
 22 <!–源代码控制系统,支持多种,有兴趣可以查看官方文档,下面采用svn–>
 23 <sourcecontrol type="svn">
 24 <!–源代码在SVN服务器上的路径–>
 25       <trunkUrl>http://zt.net.henu.edu.cn/svn/StartKit/StartKit/</trunkUrl>
 26 <!–svn服务器所在路径,在这里就是VisualSVN Server安装目录中的bin目录下的svn.exe –>
 27       <executable>C:/Program Files/VisualSVN Server/bin/svn.exe</executable>
 28   <!–用来迁出源代码的用户名,svn服务器进行验证–>
 29 <username>starter</username>
 30 <!–用来迁出源代码的用户名对应的密码–>
 31       <password>123456</password>
 32     <!–web获取源代码的地址,类似于开源网站上浏览代码的那部分功能,这里的类型是trac–>
 33 <!–<webUrlBuilder type="trac">
 34     <!–trac中对应项目的地址¬–>
 35       <tracProjectUrl>http://svn.net.henu.edu.cn/pojects/StartKit/</tracProjectUrl>
 36       <!–trac中对应项目的源代码库地址,相对于上面的路径–>
 37       <tracRepositoryRoot>/StartKit</tracRepositoryRoot>
 38       </webUrlBuilder>–>
 39 </sourcecontrol>
 40 <!–该节点用来配置具体执行那些任务–>
 41 <tasks>
 42 <!–msbuild任务配置,用来编译项目–>
 43       <msbuild>
 44         <!–MSBuild.exe的路径–>
 45         <executable>C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe</executable>
 46         <!–从SVN迁出的源代码的存放位置,可以不配置,下面的即为默认值 –>
 47         <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 48         <workingDirectory>D:\Program Files\CruiseControl.NET\server\StartKit\WorkingDirectory</workingDirectory>
 49         <!–对这个项目的监控过程的日志记录目录,可以不配置,下面的即为默认值–>
 50         <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 51          <artifactDirectory> D:\Program Files\CruiseControl.NET\server\StartKit\ Artifacts</artifactDirectory>
 52         <!–要编译的项目名称 –>
 53 <projectFile>StartKit.sln</projectFile>
 54 <!– MSBuild编译时的参数,具体参数信息可以查看MSDN上的说明–>
 55         <buildArgs>/p:configuration=Debug</buildArgs>
 56         <!–指定日志记录模块–>
 57         <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 58         <logger>ThoughtWorks.CruiseControl.MsBuild.XmlLogger,D:\Program Files\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll</logger>
 59         <!–编译目标–>
 60 <targets />
 61       </msbuild>
 62         <!–在这里还可以添加其他的程序,比如运行测试、部署项目等等–>
 63 </tasks>
 64 <!–项目编译状态信息的保存位置–>
 65 <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 66     <state type="state" directory="D:\Program Files\CruiseControl.NET\server\CCState" />
 67 <!–发布和部署配置–>
 68 <publishers>
 69   <!–如果编译成功,那么下面的配置,会将源代码复制到指定目录HistoryVersion下,名称为版本标识(自动增长,labeller配置)的子目录下–>
 70   <buildpublisher>
 71     <!–源代码路径–>
 72     <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 73      <sourceDir> D:\Program Files\CruiseControl.NET\server\StartKit\WorkingDirectory </sourceDir>
 74      <!–编译成功后保存源代码到该目录下名称为版本标示labeller的目录中–>
 75     <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 76 <publishDir> D:\Program Files\CruiseControl.NET\server\StartKit\HistoryVersion </publishDir>
 77   </buildpublisher> 
 78 <!–该节点用来配置合并多个文件,当时有外部插件时,要把他们分别产生的输出文件合并–> 
 79       <merge>
 80         <!–要合并的文件,合并后的信息可以显示在Web Dashboard和邮件通知里–>
 81         <files>
 82             <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 83           <file>D:\Program Files\CruiseControl.NET\server\StartKit\WorkingDirectory\results.xml</file>
 84         </files>
 85       </merge>
 86         <!–源代码路径–>
 87       <xmllogger />
 88         <!–显示历史修改记录列表, 在Web Dashboard中可以查看–>
 89       <modificationHistory />
 90         <!–所有编译信息的统计, 在Web Dashboard中可以查看–>
 91       <statistics />
 92         <!–邮件通知配置,每次编译后,都会邮件通知下面配置中添加的用户–>
 93       <!– mailhost 是发送邮件的主机,mailport是邮件发送端口,mailhostUsername发送邮件的邮箱用户名,mailhostPassword发送邮件 的邮箱密码,from希望显示在发件人中的邮箱地址, includeDetails邮件内容是否包含详细的编译信息 –>
 94 <email mailhost="smtp.qq.com" mailport="25"
 95                  mailhostUsername="******" mailhostPassword="******" from="******@qq.com" includeDetails="true">
 96         <!–接收邮件通知的用户 –>
 97 <users>
 98   <!–name是SVN服务器上存在的用户名,group是SVN服务器上存在的组,address是该用户的邮箱地址 –>
 99           <user name="zt" group="StartKit" address="******1@qq.com" />
100           <user name="***" group="StartKit" address="******2@qq.com" />
101           <user name="***" group="StartKit" address="******3@qq.com" />
102         </users>
103         <!–接收邮件通知的组–>
104         <groups>
105         <!–name必须是SVN服务器上存在的组,notification是什么时候发送通知,可选有Always/Success/Change/Fixed/Failed –>
106           <group name="StartKit " notification="always" />
107         </groups>
108       </email>
109     </publishers>
110   </project>
111   <!–可以同时添加多个项目
112 <project >
113 <name>test</name>
114 ……
115 </project>
116 –>
117 </cruisecontrol>
118

好了,我们已经对CCNET的配置文件有了大致的了解,接下来,你打开CCNET的安装路径,找到子目录server下的ccnet.config文件, 把上面的配置信息Copy到ccnet.config文件中,记得把配置文件中的一些路径修改为自己的实际路径啊,修改好后,保存。这时候,检查 Windows服务CruiseControl.NET Server是否启动,如果没有则启动它,启动该服务后,打开浏览在地址栏输入上面配置文件中的webUrl地址:http://202.196.96.55:8080/server/local/project/StartKit/ViewProjectReport.aspx 也可以直接输入http://202.196.96.55:8080/server/ ,这里是演示地址,要根据自己的实际情况修改为正确的地址,OK,看到类似下图的效果,好了,搞定!如果你遇到了什么麻烦,请在下面留言,我一定会及时回复!

 点击StartKit,转入下图所示的页面:

OK,到这里,我们提交更新到SVN服务器后,CCNET就会根据我们配置自动编译项目,而且我们也可以通过Web Dashboard来查看具体的编译信息了,提示如果配置了邮件发送,那么我们还可以通过邮件收到详细的编译信息,怎么样?够方便吧!

其实,CCNET的功能是相当强大的,上面只是最常用的配置,其他还有很多非常好的功能。你想知道吗?那你可以在这里查看CCNET官方文档 ,实际上,你安装CCNET后,文档也已经安装到你的电脑了,在CCNET的安装目录下的webdashboard的子目录doc中就是。 

好了,我们今天的教程就到这里,本来我应该把如何使用CruiseControl.NET Tray来监视每次更新后的编译状态,但是今天真的太晚了,明天还要做项目,所以我明天补上,请大家见谅! 

如果大家有什么问题,欢迎和我交流! 


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

[Flex]Flash技术入住亚马逊

mikel阅读(725)

FLASH技术入住亚马逊,其试推多媒体购物网站

Windowshop.com

北京时间11月6日消息,据国外媒体报道,亚马逊9月中旬悄无声息地推出了一个新站点,通过多媒体方式展示某些商品,试图藉此改变在线购物通常无法给消费者带来“感官盛宴”的局面。

  亚马逊这一新站点名为Windowshop.com,该站点每周二都会陈列出最新、最热销和经过精挑细选的商品。亚马逊鼓励访客使用键盘上的方向键浏览Windowshop.com,该站特点是在矩形方框中以视频方式介绍某种商品,这些方框则呈棋盘状依次排列。

   目前,在Windowshop上陈列的商品有音乐、书籍、视频游戏和电影。访客在用鼠标点击或使用键盘方向键移动至某种商品时,该商品所在方框将会变 大,供访客观看或收听相关内容,如《功夫熊猫》的视频剪辑,或是某本书籍的音频预览等。如果想要购买某种商品,只需点击方框右下方的按钮跳转至亚马逊主站 即可。

  亚马逊负责零售客户体验的副总裁伊娃·马诺里斯(Eva Manolis)称,推出Windowshop旨在给消费者带来身临其境的感觉,这是在线购物中很难获得的体验,对亚马逊这样一个规模庞大的网站来说更是如此。她表示:“我们希望尝试不同于以往的新事物。”

  此外,亚马逊还将Windowshop.com网站的技术和美学理念用于主站的一个版块,展示广受欢迎的节日玩具和深得儿童喜爱的视频游戏及DVD等商品。马诺里斯称,鞋和服装等其他类型的产品也可采用Windowshop风格。

[Flex]Prana Framework力推ActionScript 3应用开发

mikel阅读(815)

Prana Framework力推ActionScript 3应用开发

作者 Moxie Zhang译者 张龙 发布于 2008年11月5日 下午3时59分

社区
Java
主题
RIA
标签
Flex

Prana是一个面向Adobe Flex及ActionScript 3的控制反转(Inversion of Control,即IoC)应用框架。InfoQ最近采访了Prana Framework的创建者Christophe Herreman和Damir Murat以深入了解该框架的使用。

InfoQ:您能否向InfoQ的读者说明一下当初为何在其他控制反转应用框架已经存在的前提下还要开发Prana呢?

Herreman:Prana诞生于我们开始重写之前用ActionScript 2和Flash开发的一个在线学习平台之际。我们使用的一个库是来自于as2lib的IoC容器,由于之前IoC对我们的工作提供了巨大的帮助,因此我们 想在自己的这个新平台上也添加同样的功能。那时还没有ActionScript 3的IoC容器,所以我打算自己开发一个。

我以一个自己的实现(基于Spring XML方言)开始,但很快我就决定尽可能地以Spring提供的代码作为基础。这样做更容易实现某些特性,因为可以参考Spring的源码;熟悉 Spring的开发者使用Prana时会很容易上手,当然我也借此机会更深入地学习Spring的内核。

InfoQ:您认为Prana framework最突出的特点是什么?

Herreman:它是一个通用、可扩展、功能强大的IoC容器。如果你了解Spring IoC容器,那么你就会清楚Prana能做些什么。

它有一个很棒的特性:你可以向其XML解析器中增加自定义的预处理器。预处理器用来转换已加载但尚未解析的XML。接下来,你可以增加新的元素和属性以方便地描述自定义对象,同时还可以让自定义的预处理器将元素转化为Prana解析器可以理解的形式。

除了IoC容器,Prana还有一个构建于describeType()之上的Reflection API。这样你就可以在运行时获得对象的信息,比如对象包含的属性和方法以及实现的接口等等。接下来,我们还为领域对象创建了一些基础类(这是从Eric Evans的Domain-Driven Design一书中得到的灵感)。这些基础类具有比较和克隆对象等逻辑。Prana还包含几个有用的帮助类。

Murat:Prana还提供了一些工具,这些工具可用来快速建立基于Prana的项目。其中一个主要特性就是 动态更新Flex编译器的配置信息以包含编译好的swf中的类,而这些类是无法通过代码访问的。这在IoC系统中很常见,因为IoC鼓励面向接口(而不是 类)编程。我们的工具与Eclipse/Flex Builder紧密集成,同时他们可以检测到Prana的配置信息何时发生了变化,如果需要的话,他们就会解析Prana的配置并相应地更新flex编译 器设置。当程序员必须显式声明无法通过代码访问的类以将其包含在最终编译好的swf中时,这种方式就无需再使用典型的flex“模式”了。我们的工具会自 动完成这些事情。

还有其他一些特性,如预定义的项目布局、定义好的Ant target,对subversion的支持等等。所有这些特性都是可配置的,并可通过几个步骤轻松搞定。开发者可以查看prana-tools项目(从svn或是分发包中都可以得到)以了解感兴趣的信息。

InfoQ: Prana集成了Cairngorm和PureMVC。您能否说明一下Prana为什么要与这两个框架集成,并且是如何实现集成的?

Herreman:我们为Cairngorm和PureMVC提供了一套扩展。因为我们使用了IoC,所以我们还想将该原理应用到我们正在使用的框架上,同时我们想用依赖注入(Dependency Injection,即DI)对应用的不同部分进行包装。

我们为Cairngorm提供了一个可配置在IoC容器中的服务定位器。你可以在外部定义远程对象、channelsets、consumers等, 并可以改变他们而无需重新编译应用。通过这种方式,你也无需编译services-config.xml文件并可以轻松地将其部署到不同的地方。它还使测 试变得更简单。典型的例子就是当你从开发机器迁移到测试或是产品服务器上时,你得改变端点(endpoints)。Prana使这一切变得简单,你无需重 新构建应用。

我们提供的frontcontroller是Cairngorm frontcontroller的一个子类,它接收定制的命令工厂。通过这种方式,你可以控制命令创建的方式,一旦命令创建好后,你就可以将额外的属性注入到命令中。除此以外,我们还支持链式的事件/命令,这样你就无需显式地从另一个命令中调用命令了。

Murat:与PureMVC的集成最初只是一种实验性的尝试,用来将IoC带到PureMVC应用中。后来发现这是可行的,于是我们就将这项工作公开了。

对于PureMVC用户来说,主要的好处是当遇到依赖时可以使用依赖注入。同时这也是最大的缺点,因为DI的使用不可避免地会改变一些原始的 PureMVC使用习惯,而这些习惯是基于服务定位器模式的。然而我们相信DI对任何应用都是很有帮助的,PureMVC也不例外。为了减轻移植到DI的 代价,我们尽可能简化Prana的PureMVC集成。例如,PureMVC开发者可以选择一个DI的应用范围。Prana只能用来管理非PureMVC 对象,或者说它只能用来管理部分PureMVC类,当然它可以管理应用中的PureMVC对象和非PureMVC对象。

InfoQ:能不能推荐一下使用Prana的最佳方式(或者是应用类型)?

Herreman:如果你需要在应用中保持一定程度的灵活性以便其可以运行在不同的上下文中,或者是你拥有大量的配置,想要集中管理他们,那么我极力推荐使用Prana。因为它基于Spring,很多开发者已经熟悉了其概念和XML方言。

就我们的情况来说,我们已经创建了一个在线学习平台,用户可以定制其自己的需求。因为我们自己管理该平台,所以需要有一种机制以允许所有这些定制。 如果没有IoC,我们就不得不对每个定制编译不同的软件版本,或者是我们必须编写一个基于XML或者是数据库的客户配置系统,而对其的维护绝对是一个噩 梦。与此相反,我们可以让每种定制都有一个应用上下文,当应用加载时就去装载该上下文,这取决于登录的用户。更酷的是我们可以从ASP页面(需要从数据库 中读取配置)中即时生成应用上下文。

InfoQ:Prana的长期计划是什么?

Herreman:最重要的事情就是IoC容器,我们期望1.0版会有一个稳定的容器。目前来看,容器本身很不错,但我们还可以改进一些东西,增加更多的Spring特性,如parent beans及自动装配等。我们还需要编写一些文档。

我们一直在与开发团队探讨将扩展(Cairngorm、PureMVC等)从主代码库中移除,然后将其作为独立的扩展库发布。这么做将有利于发布管理。

我还准备开发一个AOP(Aspect-Oriented Programming,面向方面的编程)框架,但遇到了一些麻烦,这些麻烦是由ActionScript 3的一些限制导致的。AOP背后的主要思想是基于动态代理机制创建新的对象类型,该对象会在运行时实现一些接口。问题在于这在ActionScript 3中是不可能的。我们已经在Adobe JIRA上发布了这个话题,如果有人愿意与我们分享一些见解的话我将感激不尽。该话题位于:http://bugs.adobe.com/jira/browse/ASC-3136

至于其他方面,我们还没有制订严格的路线图。当我们有新想法时就会引入一些新特性和进行一些改进,我们一直在倾听来自其他开发者的建议,同时还期待有更多的人能加入到我们的团队中。

查看英文原文:Prana Framework Helps on ActionScript 3 Application Development

[C#]BlogEngine.Net架构与源代码分析系列part4:Blog全局设置——BlogSe

mikel阅读(827)

     这 已经是本系列的第四篇了,以前我多数时间是看文章,自己写起来才感觉到当博主不容易啊,所以我们无论评论也好,阅读也好,都要尊重博主的劳动成果。闲话少 说,在这篇文章中我们将对BlogEngine.Net的全局配置进行一下分析与探讨。关于配置这一部分单独拿出来做一篇文章实在有些牵强,但是我总觉得 这个配置部分比较独立,而且BlogEngine.Net的设计和实现都有很多可以参考的地方。

在一个企业级应用系统中,对一些系统全局参数进行配置是必不可少的,那么我们是怎么处理这些配置的呢?

     一般都有以下三步:

1.在业务模块开发的过程中将一些可变的参量提取出来。

2.当所有业务模块开发完成时,将这些参量分类存储起来。

3.开发出相应的管理功能,允许用户对这些参量进行设置。

     相信大多数开发者都是直接操作数据库中的数据,可能有些比较完善的系统 会做出单独的页面来给用户管理使用,本质上也都是直接与数据库打交道,没有涉及太多的逻辑,比较直接。不过在BlogEngine.Net的架构模型上这 种操作就失效了,我们不可以在BlogEngine.Net的数据库中修改数据而希望在它的运行系统中体现出来(除非重启应用),因为 BlogEngine.Net的数据库只是完成数据存储功能,所有的业务逻辑都在BlogEngine.Core层由对象的状态维护来完成(可以参考我的第二篇文章)。有人可能会想可不可以进一步开发一种像ASP.NET中Cache那样对数据库表的依赖机制,我觉得这个问题要看需求,这种在BlogEngine.Net中似乎没有什么太多必要。

那么BlogEngine.Net的全局设置到底是怎么实现的呢?

     在安装 BlogEngine.Net以后进入后台管理中,我们会看到有很多分类的配置选项,包括主题,语言文化,时区等。这些配置的逻辑处理都是通过 BlogEngine.Core中的一个类完成的,那就是BlogSettings。BlogSettings只是完成BlogEngine.Net的全 局的配置处理,对于后文讲述的Widget的一些具体配置并不在这里完成,这主要是为了配置独立的目的,使得Widget可以独立开发。

     BlogSettings与外界交互图(这个图使用画图程序弄的,大家先凑合着看吧):

 

     

     在BlogSettings中除了各种配置项的对应属性以外,还有一个 静态的Changed事件用来通知外部全局配置已经发生了改变,这样就可以写出更多扩展来。BlogSettings使用单例模式来实现(一个设计模式的 很好的应用,全局配置具有系统唯一性)。

Code

 

从这里我们就可以知道为什么对于数据源的直接修改不能在BlogEngine.Net的运行系统中体现的原因了。

     BlogSettings在对象构造时执行了一个Load方法来加载所有数据存储中的配置信息,这个加载过程应用到了.Net反射技术,找到数据存储中与对象属性相同名称的配置项并将其值强制转换以后付给属性,当然这里的数据访问是通过我的第三篇文章中讲述的BlogService调用获得。同理修改配置是通过Save将数据保存回数据存储,也是使用反射完成。

Code

 

客户端的使用方法(注意:这里所说的客户端是指BlogSettings使用者或调用者)

Code

 

总结

     从BlogEngine.Net的全局配置部分我们可以学习到以下几点:
1.单例模式是如何应用在实际项目中的。
2.配置项的数据存取部分的实现有很好的参考价值,可以了解到.Net的反射给开发带来的方便。
3.对于静态事件的使用(BlogEngine.Net中有很多例子)使得我们可以在外部做一些扩展,例如开发一个监控配置修改的跟踪系统。

     好的设计要经过不断的重构才可以达到。

     上一篇:BlogEngine.Net架构与源代码分析系列part3:数据存储——基于Provider模式的实现

版权声明
作者:Thriving.country
出处:http://thriving-country.cnblogs.com/
本文版权归作者和博客园共同所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接.

[FireFox]FireFox3.1开始支持HTML5的视频和音频

mikel阅读(801)

Firefox 3.1现在包含对HTML 5 视频video音频audio 标签tags的支持了!直接可以在页面上控制音频和视频信息,Mozilla开发者网站发布一篇新的文章指导如何使用。Firefox 3.1目前支持Ogg format(一种新的崭露头角的音频文件格式),其他音乐格式还在讨论中,可能未来支持。
下面是范例使用方法:

Html代码 复制代码
  1. <video src="http://v2v.cc/~j/theora_testsuite/320×240.ogg" autoplay>   
  2. Your browser does not support the <code>video</code> element.   
  3. </video>  

是不是非常简单?你能为不同的格式提供不同的代码选项:

Html代码 复制代码
  1. <video autoplay>    
  2.    <source src="foo.ogg" type="video/ogg"></source>    
  3.    <source src="foo.mov"></source>    
  4.    Your browser does not support the <code>video</code> element.    
  5. </video>   

目前,所有源码元素还没有完全支持,媒体录音能通过JavaScript控制:

Js代码 复制代码
  1. var v = document.getElementsByTagName("video")[0];   
  2. v.play();  

你还可以通过不同的媒体事件来更新你的UI:

Js代码 复制代码
  1. var v = document.getElementsByTagName("video")[0];  
  2.   
  3. v.addEventListener("seeked"function() { document.getElementsByTagName("video")[0].play(); }, true);  
  4. v.currentTime = 10.0;   

真是挺强大的,希望其他浏览器对HTML 5的支持也能加强。

来自:ajaxian.com

[设计]30大优秀Logo设计详解

mikel阅读(960)

原文:30 Brilliant Vector Logo Designs, Deconstructed -Chris Spooner

编译:Vinwin (不倒猫

 

拥有一个抢眼的Logo对企业来乃一大幸事,毕竟Logo千千万,但真正让人过目不忘的作品可是屈指可数。好的Logo必须量体裁衣,迅速传递出企业的价值和理念。
但Logo不能只是金玉其外,还必须有思想、多功能。比如,可以用标准色在任何尺寸的纸张完美复制和再现。下面就晾出当前30个自认为很杰出的Logo,飨宴大家。

1.    Castle Print
一个打印机品牌,该Logo直截了当地体现了了企业的业务性质:利用减色模型,直指其打印行业背景,同时通过色彩的混合塑造出一个与其品牌相符的城堡(Castle)形象。


2.    Ryan-Biggs
负空间的运动使得这幅Logo有一种奇幻的效果,完全考验你的空间想象力!B和R两个字母代表了这个品牌,微微的倾斜让整个设计看起来更有深度和立体感。色彩搭配极为简单—红色,赋予了Logo更广的使用范围。

3.    One Leaf
One Leaf, 顾名思义,即一片树叶。以此为轴线,就呈现出了如此简洁巧妙的画面。

4.    Greener
设计师用粗细不同的灯芯体(San-serif)字体塑造出一种现代感。该Logo不但层次感强,更重要的是可以用单一色调复制重现(这也是评判Logo好坏的重要标准之一)。

5.    Talkmore
Talkmore字面意思是“多说点儿”。设计师采用象征的手法,用英文中的单引号分别代替字母“a”和“e”,从而在图形上给予品牌最鲜活的注解。


6.    Black Sparrow
看上去很简单的图标,但在细节处理上却达到了极致。从麻雀(即Black Sparrow的中文)的图案到字体,柔和的曲线与平滑的字体相得益彰,将完整的设计融入品牌表现。


7.    Swannie Lake
富有时代气息的Avenir字体配合平滑的图案,不但与该Logo完美贴合,而且增添了一许微妙的色彩。

8.    Elara Systems
先介绍一下,Elara Systems是一个动画和动态模型工作室,也必然要求2D和3D的结合。体现在Logo上,就是大家所看到的效果:柔软弯曲的字体配上3D的字母“e”(即首字母),很好的创意

9.    Onwine
完美的字体搭配独到的理念,Onwine Logo为我们展现出一个酿酒商的特质,无论是图案还是字体设计都无可挑剔。


10.    Popp
什么叫一气呵成?这幅Logo将基于同一字体的设计方法带上了全新的高度。每个字母都包含字母“O”,只是做了细微的改动就成了“P”,独具匠心。

11.    Therauz Fashions
能看出这个Logo的绝妙之处吗?该公司从事时装设计业务,所以针线活是少不了的。画面中央的长针,不仅反映了公司业务,而且将中间一串字母给“缝”了起来,成了必不可少的一部分,内涵之深可见一斑。

12.    Alatau
无论是字母还是彩色远点,共同的特点就是间距较大,这种手法带来的效果就是严肃而前卫。这样排列的好处还在于以中间两个字母中心,整个Logo看起来非常平衡。


13.    Spiffy Sparrow
作品采用复杂的形状、线条和色彩,为以后的调整打好了基础。另外,用负空间来塑造鸟的躯干也是令人叫绝的地方。


14.    About Thyme
干净利落的线条和形状,是Logo设计的固有套路。不过,本作品似乎摆脱了这样的条条框框:粗糙的手绘图案,给人以一种亲和力,也给体现了品牌所有者作为调味品公司的价值。

15.    Ta Jevi
一个娱乐网站。Logo的每一部分都闪烁着欢乐感。箭头组成的笑脸传递出了这样的信息—欢乐无止境。跳跃的色彩和超酷的手写字更突出了该网站的娱乐价值。


16.    Anti Particle
中文名—反粒子,是一家电影制作公司。这里有必要做个小小的科普,反粒子其实很简单,比如负电子的反粒子就是正电子,质子的反粒子就是反质子……好了,回 到Logo上,很明显,这又是一个用公司名做文章的范例。首先首字母“a”有无数颗粒组成,但里面恰恰有一颗蓝色颗粒,这就体现了“反粒子”的概念。


17. 69 Monos
不同于平面2D设计,有些Logo,就像69 Monos,通过3D效果给Logo增添深度和趣味。改变角度就能带来动感,何乐而不为?


18.    Green Dolphin
Logo使用海豚(Dolphin)的轮廓和绿色来传递品牌信息,同时也隐藏了次级画面—即大写字母G。

19.    Aramova
经典的莫比乌斯带(Mobius Strip http://hi.baidu.com/totogo/blog/item/629a233ff2239aec54e723ad.html)使用案例!柔软的质地+奇幻的色彩,整幅画面让人体会到永不停息的动感。


20.    Vize
并不是所有的Logo都需要附属图案,比如我们看到的这个。虽然只有字体,但由于在排版上做足了功夫,Logo同样可以表达品牌的理念。

21.    Friends in Places
交友网站,很明显。看似纷繁复杂的箭头构成了一幅世界地图,表现了互联网时代网络社交的全球性和广泛性,企业的品牌价值由此得到体现。


22.    Koloroo
一款色彩方案的软件产品。随机的颜色搭配勾勒出了一只袋鼠的形象,这也符合其产品形象。字体设计同样出众,因此可以说,该Logo两部分合起来是精品,拆开来也是优秀的图案和文字标识。


23.    Pangur
手工玻璃制造商。通常而言,透明的运用在Logo设计中是慎而又慎的,不过随着打印技术的改进,已经没有理由限制这种技法的使用了。设计师用不同色彩的玻璃碗进行简单堆砌,且呈现不规则倾斜,这恰恰抓住了玻璃手工艺的特点。Futura字体也给作品锦上添花。

24.    Tammy Lenski
美国一家争端解决公司(比如劳资纠纷等等)。现在,搭上千纸鹤,来一趟意境之旅吧。大多数Logo都直截了当地反映目标企业的各个方面,然而这个设计却通过千纸鹤的故事和寓意来展现企业形象(编者注:之所以这么说,是因为西方人并不了解千纸鹤的含义)。


25.    Eco Cafe
图案简洁又不是标识性,整幅作品看起来就像破土而出的生命。生态咖啡馆—该企业的名称—得到了最鲜明的宣示。而正因为标识本身的简洁,使得它贴在窗户上,也可以印在咖啡杯上。

26. Firefish
这又是一个运用流行技术—比如透明—塑造多层次标识的典范。火焰拼成的鱼(即Firefish,火鱼)搭配上经典的大写灯芯体(san-serif)字母,堪称一绝。

27. Boxbound
立体和透明同属当今Logo设计潮流,这幅作品同时运用了这两种技巧。生动的色彩加上浑圆可爱的字体,俨然一副基于网络的时代先锋形象。


28. AdMagik
看完这个Logo,你应该能学会用颜色区分信息。品红色部分突出了公司的名称“Magic”(魔术),而灰色字体暗示了该公司的身份,也是Logo的重点 所在,即“Ad”(广告)。最后,在字母J和I上做图,使之成为兔子的形象(编者注:兔子在西方是魔术的象征),再一次强调了企业点石成金的业务能力。


29. Jivespace
这个Logo用爆炸性的手法给人以强烈的视觉冲击,既有深度又充满活力,同时,全部小写字母的排版方式体现出其时代气息。

30. Core
和前面提到的“Popp”Logo类似,它也是基于一个形状延续下去,线条干净利落,颜色单一,该Logo的功能性可想而知有多么强大。

来自:blogbeta.com

[C#]BlogEngine.Net架构与源代码分析系列part3:数据存储——基于Provider

mikel阅读(856)

   上一篇文章中,我们主要分析了一下BlogEngine.Net的整体设计,在后半部分我们又对BusinessBase业务对象的状态维护做了一些比较深入的探讨。在这篇文章中我将引领大家完成对BlogEngine.Net中业务对象数据存储的设计思路与实现细节的分析。

BlogEngine.Net中的数据存储主要是应用Provider模式实现的,那么首先让我们认识一下Provider模式。

     Provider 模式应该是一种设计模式,是用来解决软件变化问题的。不过它关注的角度(或者维度)是功能声明跟功能实现的分离。一般来说,系统对某一功能的需求可能是相 对稳定的(比如每个系统都要求对登录用户进行验证,这个需要是相对稳定的),而这个功能的的具体实现却可以是多样的(比如,你可以去数据库里匹配用户进行 验证,也可以去一个XML文件里面去匹配)。Provider模式在.Net类库的设计中随处可见,如:MembershipProvider、 SiteMapProvider等,它的出现使我们的应用程序有了更大的扩展性,要注意Provider可以是一个数据工厂的提供者,也可以是一个逻辑处 理的提供者。在BlogEngine.Net中我们看到的都是数据工厂的提供者,对于逻辑处理的提供者,大家可以参考一下微软企业库 ApplicationBlock中的加密Block的实现。
     在.Net中要实现这种模式是相当的简单,因为它已经为我们实现了一部分,我们只需实现以下三步即可:
1、定义一个类,抽象出我们所需要的操作,它的基类为ProviderBase
2、实现一个Section,用来从配置文件中读取Provider的相关配置,该类继承于ConfigurationSection
3、在调用时去读取配置文件,并加载指定的Provider

对于BlogEngine.Net中的数据存储部分我是怎么看的呢(个人观点,不必在意)?

     BlogEngine.Net可以支持多种数据存储,在它目前的版本中 我们可以看到XML(默认采用,主要是为安装时即插即用考虑的)和数据库两种存储方式。BlogEngine.Net数据存储的Provider只是针对 数据如何存储部分(不涉及到一些逻辑处理与运算),所以对于数据库的要求非常的低,只要支持SQL语句并可以存储数据就行,实际上它在数据库中只有一些 表,从它的Provider的实现来看并没有使用数据库的主键级联删除等功能,而完全使用多条嵌入式SQL语句完成,这样做就可以使 BlogEngine.Net支持更多的数据库。对于数据库的存储BlogEngine.Net只使用了一个DbBlogProvider,而没有具体区 分到底采用何种数据库,我们只要在配置文件中根据链接字符串设定providerName就可以指定具体的数据库存储了。BlogEngine.Net的 这种对数据处理方式在业务逻辑很复杂的系统中我并不是很推荐。

那么再让我们看看在BlogEngine.Net中是怎样应用ProviderBase来完成数据存储问题的。

     先看一幅继承关系图:

从上图我们可以看出DbBlogProvider 和XmlBlogProvider都继承了抽象类BlogProvider(包含一些业务类或其它类等的操作声明),而BlogProvider直接继承 自ProviderBase,ProviderBase是微软的标准Provider框架,我们只要按照这种模型开发就行了,这个标准的Provider 框架已经解决很多问题,例如依赖注入问题等。为了利用.Net平台已有资源对于角色和成员BlogEngine.Net采用了.Net提供的 MembershipProvider和RoleProvider,BlogEngine.Net中 DbMembershipProvider,XmlMembershipProvider继承于 MembershipProvider,DbRoleProvider和XmlRoleProvider继承于RoleProvider,.Net的 MembershipProvider和RoleProvider同样也继承自ProviderBase。BlogProviderSection这个类 是为了解决配置问题的,也就是.Net中Provider模式实现中的第二步,这就很好的解决了依赖注入问题。BlogProvider定义了一些业务类 的标准的操作方法:

Code

子类只要重写这些方法就可以了。

     此外在BlogEngine.Net中还有一个比较推荐的处理,由于BlogProvider需要处理很多业务类型数据的操作,方法成员就会很多,所以在实现XmlBlogProvider时采用了partial class解决。

     DbBlogProvider重写了ProviderBase的 Initialize方法来达到链接字符串、表前缀、字段前缀等的获得,这么设计我觉得用处可能是由于BlogEngine.Net的数据库只有数据存储 功能,用户可能直接把它部署到了已有数据库中,为了和已有数据库中的其它表区分,我们可以指定一个表名前缀。对于DbRoleProvider和 DbMembershipProvider处理方式也是类似。在实现时BlogEngine.Net似乎也考虑了Mono,部分代码出现了对Mono运行 时的判断,但是我没有试过是否可以跨平台安装,感兴趣的朋友可以研究一下!

客户端是如何使用BlogProvider的呢?

     对于MembershipProvider和RoleProvider 的使用这里不再介绍,可以参照一下MSDN文档,BlogEngine.Net也是这么用的。这里主要讲一下BlogProvider的使用问 题,BlogEngine.Net中提供了一个BlogService的静态类,这个类提供给外界一个统一的数据访问接口,它内部的静态方法实现调用 BlogProvider来完成。在BlogService中有一个LoadProviders方法来根据配置文件动态加载BlogProvider:

BlogService

 

最后让我们看一下Web.config的相应配置

BlogProviderConfig

 

MembershipProviderConfig和RoleProviderConfig

 

总结

     BlogEngine.Net数据存储的实现应用了Provider模 式,这个在.Net平台下有很好的支持和案例,这种设计思想很值得借鉴,尤其是BlogService设计的很巧妙,也有一点面向服务的味道。不过在一些 业务逻辑非常复杂,尤其是一些应用到数据库逻辑处理的系统设计时,这种设计可能就无法满足需求了,还要坚持数据的处理离数据越近越好的原则。

     取其精华!

     上一篇:BlogEngine.Net架构与源代码分析系列part2:业务对象——共同的父类BusinessBase

版权声明
作者:Thriving.country
出处:http://thriving-country.cnblogs.com/
本文版权归作者和博客园共同所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接.