posts - 16, comments - 27, trackbacks - 0, articles - 0

2007年1月15日

 

EXCEL中获取数据到datatable,这个任务听起来似乎轻而易举,一开始希望用vba来完成,但当我查询相关资料以后才发现。。。原来office还可以这样玩,.net不会跟vba抢买卖,只会给我们带来新的惊喜。

    首先是如何获取数据,我们需要选择一个sheet,并设置需要的数据范围,如“A1:R19”,而后用oleAdapteroffice数据源进行连接。在之前要对OleDbCommand进行一系列的设计工作,其中最重要的就是设置查询条件,如:

_oleCmdSelect =new OleDbCommand(

@"
SELECT * FROM ["

+ _strSheetName

+ "$" + _strSheetRange

+ "
]", _oleConn);

在这里微软结合了sql数据查询方式,有点类似上次说的进程处理方式,在关系数据库面向对象化前,这样的解决思路也不失为一种高效的方法。

将查询出的数据绑定在dataview上,接下来就可以根据实际情况进行数据导入,对备份测试数据来说是个不错的做法。

posted @ 2007-01-15 11:53 Phono 阅读(75) | 评论 (0)编辑

 

  在面向过程时代,统计程序复杂度的最佳方法就是单位功能的代码量,计算每天工作量的方法,也是通过统计有效代码量实现的。但目前的代码量工具中,我还没有发现一个比较通用高效的….也难怪,在面向对象盛行的现在,也就是我们这种行当还在死守着代码量至上的原则,优秀程序员每天的代码量在400行以上的说。

    于是我在codeproject上找到了这个,并将其加以改善,顺便介绍一下codeproject是个好网站,从那上学了不少东西。统计代码行数的算法不太难,就是有点麻烦,把代码放上了,目前还没有解决aspx层注释和多行统计的问题,大家先看看吧。/Files/phono/CodeCounter.rar,这两天心情不太好,不太想长篇大论的写了

             

posted @ 2007-01-15 10:21 Phono 阅读(730) | 评论 (2)编辑

2006年12月29日

以前因为要配置网络打印机发现了脚本的强大功能,寒一个查里斯——晨的语录“脚本才素王道。。。。”
言归正传,还是老规矩,先看代码,下面解释
strComputer = "."
arrTargetProcs 
= Array("calc.exe","notepad.exe","freecell.exe")

Set objWMIService = GetObject("winmgmts:" _
 
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecQuery("SELECT * FROM Win32_Process")

Wscript.Echo 
"Checking for target processes "

For Each objProcess in colProcesses
  
For Each strTargetProc In arrTargetProcs
    
If LCase(objProcess.Name) = LCase(strTargetProc) Then
      WScript.Echo VbCrLf 
& "Process Name: " & objProcess.Name
      WScript.Echo 
"  Time: " & Now
      intReturn 
= objProcess.Terminate
      
If intReturn = 0 Then
        WScript.Echo 
"  Terminated"
      
Else
        WScript.Echo 
"  Unable to terminate"
      
End If
    
End If
  
Next
Next

strComputer = "."指的是脚本作用域的主机,再没有防火墙干扰的情况下,可以部署在服务器上作用于客户端。
arrTargetProcs 存储需要关闭的进程名,在运行特定程序前,需要节省开支,或出于其他的目的,结束进程,我经常在cpu100%的时候用它来关机,效果比重启还好.....大材小用了。

而后获得VMI对象,安指定条件获取,之后这个就厉害了“SELECT * FROM Win32_Process”对,VMI对象都是按照面向对象思想设计的,支持这种结构查询,性能也不错。
下面的也很简单了,将查询结果与制定需要关闭的进程名进行对比,如果查询结果中有需要关闭的进程,就将其名称列出,而后objProcess.Terminate将其终结。是不是很简单

问我说的那个重启脚本么?
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
 
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecQuery("SELECT * FROM Win32_Process")
For Each objProcess in colProcesses
    objProcess.Terminate
Next

不要把它拷到记事本里,另存为don't double click me.vbs,然后发给同事。也不要告诉他们你是看了我的博客,呵呵
新年快乐~~

posted @ 2006-12-29 10:25 Phono 阅读(85) | 评论 (0)编辑

2006年12月14日

看见旁边的同事在测试,点一百次那种。。。想起以前好像做过鼠标事件的模拟,结果什么都没留下来,结果为了显示一下就答应帮他做,结果做了半天才做出来,为了防止以后再忘,记一下。
首先引用:
        [DllImport("user32.dll")]
     private static extern int mouse_event(int dwFlags,int dx,int dy,int cButtons,int dwExtraInfo);
        const int MOUSEEVENTF_MOVE = 0x0001;
        const int MOUSEEVENTF_LEFTDOWN = 0X0002;
        const int MOUSEEVENTF_LEFTUP = 0X0004;
        const int MOUSEEVENTF_RIGHTDOWN = 0X0008;
        const int MOUSEEVENTF_RIGHTUP = 0X0010;
        const int MOUSEEVENTF_MIDDLEDOWN = 0X0020;
        const int MOUSEEVENTF_MIDDLEUP = 0X0040;

最简单的是移动事件,最好配合个延时,效果比较好,也方便做测试
           mouse_event(MOUSEEVENTF_MOVE, 150, 5, 0, 0);
           System.Threading.Thread.Sleep(100);
鼠标点击事件的处理是在按钮送开时被调用的,千万要配对用,这次就吃了这亏了。
            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
最后别忘了在带码头的地方添加引用
            using System.Runtime.InteropServices;
还可以做个自动关机啊,格式化硬盘什么的。。像写脚本一样,看大家的想象了,呵呵

posted @ 2006-12-14 16:14 Phono 阅读(200) | 评论 (0)编辑

2006年9月19日

京剧中节奏比较快的适合年轻人听的,一般是西皮流水、西皮快板、一部分的西皮二六和二簧快三眼。
我介绍一些流水板为主的唱段,如下:
(剧目) (唱段名)
三家店-将身儿来至在大街口;
定军山-这一封书信来得巧,在黄罗宝帐领将令;(此为两段)
桑园会-秋胡打马奔家乡,站立在桑园把话答;(此为两段)
珠帘寨-贤弟抬头来观瞧,老虽老孤的须发老(张建国唱),昔日有个三大贤(三个"哗啦啦",层层高起,很是好听);
打严嵩-(麒派和马派都不错);
追韩信-我主爷起义在芒砀;
五家坡-苏龙魏虎为媒证(流水对唱);
四郎探母-听他言吓得我浑身是汗(流水对唱);
梅龙镇-月儿弯弯照天下(对唱);
淮河营-此时间不可闹笑话(马连良唱);
甘露寺-劝千岁杀字休出口(历数"刘关张赵诸葛"的流水,有气派);
斩黄袍-天作保来地作保;
秦琼卖马-站立店中用目洒;
空城计-我正在城楼观山景(余言高马杨奚关派均有此段);
穆桂英挂帅-猛听得金鼓响画角声震;
锁麟囊-耳听得悲声惨(此为"二六板");
春闺梦-可怜负弩充前阵(此段唱词极为优美,如诗如画);
红娘-叫张生隐藏在棋盘之下;
苏三起解-苏三离了洪桐县(梅尚程荀张派都有,各有千秋);
牧虎关-高老爷来至在牧虎关;
断密涧-李密闻一言无定准;
双投唐-这时候孤才把这宽心放(邓沐伟,关怀对唱,是净角生角对唱经典)
打龙袍-一见皇儿跪埃尘;
四郎探母-一见娇儿泪满腮;
法门寺-刘公道在大街我珠泪双抛。
锁五龙-号令一声绑帐外;
斩马谡-翻来覆去难消恨


以上只是一部分,希望以后诸君能体味到京剧的真谛。慢慢地品味吧。 另外我感觉京剧一定要听名角的段子,比如谭富英,梅兰芳,但可惜那个时代的录音流传到现在已经很不清楚了。比较清楚的有方荣翔,于魁智
注:以上唱段都可以在http://www.jingjuok.com/qk/ 找到。 可下载,搜索唱段名,或剧名都可以找到。还带有戏词。

posted @ 2006-09-19 08:59 Phono 阅读(1569) | 评论 (0)编辑

2006年9月15日

     摘要: 这次说解析器,像所有最基础的操作一样,解析器的最终目的是为了返回需要的值,而通过读取配置文件到引擎而获得的这些数据还不能马上用于操作,通过解析器内部的 方法集,返回对类型的判断,或对四则运算的命令作出相应,这里要说一说优先度的问题,按照优先度的由低到高依次调用调用,如:在加减法方法中调用乘方法,在乘方方法中调用成除运算,而后是正负符号运算,括号预算,每种方法中都会带一个默认为的”0&#... 阅读全文

posted @ 2006-09-15 17:12 Phono 阅读(65) | 评论 (1)编辑

今天翻译一篇文章,老外的东西却是先进,但他们也特别拽,我下载他们的代码很少能一下执行成功的,得来回来去的改,计较旮旯的一通溜你,不过改完也就明白个大概了,再看就有点自己原创的感觉了,不知道他们是不是有这个嗜好。言归正传,这次说的是CSLA,前一阵正好在研究,以后有时间会写些更详细的出来。

从这里/Files/phono/person_csla.rar下载源代码

这个例子是介绍Rockford Lhotka大哥的Component-based Scalable Logical Architecture (CSLA)设计思想的一个简单示例,有本书,叫《业务对象专家指南》,说的就是这个,说实话,不太好懂。CSLA实现的是一种N层框架,程序部署在四个逻辑层次——表现层,UI业务对象,数据业务对象和数据服务,这个框架的目的是要实现了两个方面的功能,
1.可扩展性
2.在不同物理层间灵活部署
后面一条的意思是我们能自由决定部署方式,可以把所有层都部署在一台机器上,或者在稍稍或根本不用修改代码的情况下把程序变为客户端——服务器模式的应用程序,这还说明了我们可以不用过多考虑表现层的性质,windows的也好,webPage也好,后台的业务逻辑代码和部署方式都不会有太大变化。

一个典型的物理框架结构是这样的,使用一个胖客户端作为用户接口,把UI业务对象部署在这个客户端工作站上使之能以最近距离服务用户接口,UI业务对象和部署在远端应用程序服务器上的数据业务对象进行通信,而数据业务对象会跟数据库服务器上的数据服务进行通信。

实例里的代码不是完整的重视与框架,这些代码只是简单的阐述了一些框架的基本设计思想,它实现了了一个简单的UI业务对象对一个公民的信息维护。社会保险号,姓名和出生日期,通过这是实例表现了一个 Microsoft .Net Windows Forms 用户接口如何同一个业务对象进行交互,并对用户输入的信息进行校验,用户接口不会输入信息作任何有关业务逻辑的校验,他只负责显示业务对象的属性,而不考虑其是不是经过校验的正确数据。

 

公民类

公民类包括三个供外界使用的属性:

    • 社会保险号
    • 姓名
    • 出生年月 

还包含一个计算得出的属性:

    • 年龄

有三条业务规则

    • 社会保险号只能是十一位的英文数字
    • 姓名不能为空
    • 生日格式必须是日期型,且时间合理。

 一个公民类如果不满足以上三点,就不能将其置为合法状态

        为了得到最丰富,响应效果最佳的用户体验,有一个想法就是在用户输入过程中就对其进行校验,这样比全部输入完成后再统一校验效果更好。基于其他的用户接口,如HTML 3.2接口,能够提供提交后的批量校验,而我们需要的是一个丰富而友善的用户接口,Windows型的GUI恰恰提供了这一切。
       基本思想是,保存按钮只有在对所有控件的输入都合法的情况下才能变成有效。这显然是用户界面开发人员编写代码实现的,但他们不用编写具体的校验逻辑,他们只负责运行校验函数然后根据返回结果设置校验成功标志位。
       在实际项目中,UI开发人员需要知道的业务逻辑当然更多,但他们需要做的不是编写业务逻辑,而只是根据情况实时改变公民实体的状态。
让我们来考虑一下公民类的操作会有哪些

    • 添加一个新公民
    • 把这个公民的信息保存到数据库里
    • 从数据库中读出一个既存的公民数据
    • 删除一个公民的信息

一个用户接口至少可以满足客户的一下要求:

    • 编辑数据,保存变更并关闭应用程序
    • 把这个公民的信息保存到数据库里
    • 编辑数据,保存变更,并作进一步编辑,保存数据关闭应用程序

等等等等

        由上述操作生成的数据实体一般来说会用到OK,Cancel 或Apply按钮。乍一看好像没什么特别的,然而CSLA框架的特性之一就是根据操作者和步骤的不同,公民对象的状态会随时改变,如果用户取消了输入,一个很重要问题就是要将对象中的数据恢复到前一个有效状态,这既意味着我们要对以前的操作结果进行保存。
 
        这个实例虽不是一个很复杂的程序但通过对公民类的维护操作,将尽力给他家展现他的核心思想。

        除了上边讲的,UI开发者还需要知道些其他的事,比如,这个公民是不是新生成的,是否被修改过,是否正在被编辑。

        比如,一个正在被编辑的公民不能被从数据库中读出同类数据所覆盖。

编辑管理

为了使编辑功能完善,有三个方法是必须的:
BeginEdit

This enables editing.

ApplyEdit

在适当或终止编辑的时候保存或删除对象,客户端应当调用BeginEdit 后才能进行编辑。

CancelEdit

取消自上次ApplyEdit 操作后所有的改变,并将对象定义为可消除状态,结束当前的编辑。
在数据库技术中,也有三个类似的方法,分别为 "BeginTrans", "Commit" and "Rollback",但区别在于一个作用于对象,一个作用于数据库。

管理业务规则

在Rockford Lhotka大神原版的框架中,他创建了一个BrokenRules 类,供每一个业务规则使用,但是在实例中,我将这一功能性模块放到了基类BusinessObject中,同时也可以使用抽象接口来定义编辑用的方法,但在例子里没有这么做。

一个公民对象是否合法取决于叫做broken business rules的一个collection ,它维护着违反规则的项目,当这个collection 不为空的时候,这个公民对象就是不合法的,当其中项目为零的时候,他自然就是一个“合法公民”了

维护这个collection的方法:
void RuleBroken(string rule, bool isBroken)
在其中
rule 是对业务规则的描述,它可能只是一个很简单的属性名,比如 "Birth Date"

isBroken 指示着这个业务规则是否被打破。

如果这个规则被大破,则将它加到collection中,如果是一个被重复打破的规则,通过算法控制忽略它,不把它加到collection中,如果是遵守了规则的合法情况,对应的该条记录将被从collection中删除。

当一个类刚刚被实例化的时候,所有的属性都是空,所以所有的业务规则都是被打破的,强调一下这些规则:

    • 社会保险号只能是十一位的英文数字
    • 姓名不能为空
    • 生日格式必须是日期型,且时间合理。

所以在初始化公民类的时候,我们写道:

RuleBroken("Social Security Number", true);
RuleBroken("Name", true);
RuleBroken("Birth Date", true);

当然,这种情况是不常有的,按照常识来讲,当所有条件都具备,没有违反业务逻辑的情况下,对象才会被实例化出来,然而,这是一种特殊情况,当进行一些关键操作的时候,如将类持久化或还原,这一方法保证了类在这些操作前是合法的。

创建一个对公民的验证

设计思想是随着用户输入正确的信息一个公民类的状态将由不合法专为合法,,在初始状态,所有的业务校验
逻辑都处于错误的状态,,而后,随着用户逐个键入正确的信息,当所有的校验逻辑都被标记为正确以后,这个公民类的状态也就变成了有效。

要实现这一功能就要编写公民的SET某某属性事件,以及画面的TextChanged 事件

以社会保险号为例:
Person.set_SocialSecurityNumber属性中

set
{
socialSecurityNumber = value;
RuleBroken("Social Security Number", socialSecurityNumber.Length != 11);
}

在 Form.txtSocialSecurityNumber_TextChanged 事件中.

person.SocialSecurityNumber = txtSocialSecurityNumber.Text;

TextChanged事件在每次输入触发中都会被调用,公民设置SocialSecurityNumber 属性的同时会调用RuleBroken

注意表达式的书写方式:socialSecurityNumber.Length != 11.

当返回值为true的时候则说明它违反了业务规则。

当刚开始输入社会保险号的时间发生时,由于输入内容不正确,所以该条规则违反,因而将其加入到违反规则集合中。在接下来的输入中,由于依然违反规则,则忽略对他的处理,当11个值都输入完成以后,返回值为false,这也表示着该条规则不再是错误的了,于是就要将其从集合中删除。

在填写姓名和生日属性的时候这个过程还将重复,直到公民对象变为合法为止。

通知客户端一个公民是否合法

公民类具有验证标志属性,同时还抛出相应的自定义事件。这两个事件交由客户端处理,以Window Form应用程序为例,使公民对象可以保存或使之只读,是通过控制OK 和Apply 两个button的使能(enable)实现的。

为了触发该事件,公民类(实际上应该是基类)声明了两个事件句柄委托和两个事件。

public delegate void EventHandler(object sender, EventArgs e);
public event EventHandler OnInvalid;
public event EventHandler OnValid;

为了触发 OnValid 事件

      if (OnValid != null)
{
OnValid (this, EventArgs.Empty);
}

当违反规则的数量降为0的时候,RuleBroken 就会执行这段代码

客户端的处理事件如下:
      // Subscribe to Person event
person.OnValid += new Person.EventHandler(Person_OnValid);
private void Person_OnValid(object sender, System.EventArgs e)
{
btnOK.Enabled = true;
btnApply.Enabled = true;
}

这段代码与实际业务相比起来要简单的多,但也能点明设计思想了。

OnInvalid 事件处理与之相似

年龄属性

年龄属性很有趣,他被设置成了只读,这样客户端就不能够重新设置它的值,它的值通过计算生日获得,由于这个原因,他在用户接口上表现为一个随输入生日变化而变化的动态值。
(注:例程中用了UK 格式的时间 - day/month/year (dd/mm/yyyy))

假设用户输入了 11/1/1969 作为出生日期,返回年龄值是33,效果就是在制度栏里显示33,输入9就会报错,而输入1959,数值也就随之变成了43。这个行为可以定义在公民类中:

public event EventHandler OnNewAge;
在公民对象中,当输入生日日期是正确的情况下,就会触发该事件,之后每次合法的改变都会触发该事件已改变年龄栏的值。

客户端处理事件如下:

      private void Person_OnNewAge(object sender, System.EventArgs e)
{
// Update the displayed age
lblAge.Text = person.Age.ToString();
}

持久化

持久化通过一个 "manager" 对象实现,PersonManager. 它包括Load, Save and Delete 方法并与公民对象通信。数据通过ADO.NET 与 Microsoft Access database连接。

两者间的接口很简单,在实际环境之,我们需要使用 .Net remoting 技术并把这个类序列化,其原因是PersonManager类通常部署在server上。

在Lhotka 的那本书中,他还阐述了把公民对象分为面向UI的公民对象和面向数据的公民对象两个分别的业务对象,这样更利于进行OR映射。

实例

为了方便说明,示例使用了windows form的用户接口。便于大家理解

 

posted @ 2006-09-15 16:56 Phono 阅读(344) | 评论 (0)编辑

2006年9月11日

     摘要: 最早接触xml配置文件驱动事件是在实习的时候,那时候恶毒的项目经理让我研究一个工作流引擎,鼎鼎大名的OSWORKFLOW,那东西是JAVA写的,唉~~太强人所难了....最后当然是可耻的失败鸟。最近在研究业务架构的时候,偶尔发现了一个XML引擎驱动事件的程序,fm2.0的,倒不是为了报仇,只是我一直没弄明白到底xml配置是怎么驱动事件的。就看了看,多少有点收获,跟大家分享一下 工欲善其事,必先利其... 阅读全文

posted @ 2006-09-11 11:13 Phono 阅读(1778) | 评论 (3)编辑

2006年9月7日

     摘要: 接触数字图像处理最早是在高中,那时候PHOTOSHOP还是4.0,可能是因为先入为主的关系,到现在都没有学3DMAX之类的兴趣,2D到3D的飞跃估计是没我什么事了,舍不得那平方到立方的高薪....呵呵。在上大学的时候,就和同学一起写过一些图像处理的程序,那个时候编程还很随意,考虑的只是如何实现,现在看来真正的技术是把握全局的能力,而不是灵光一现的神奇。前些日子接触了一些国外的图像处理程序,在这里算... 阅读全文

posted @ 2006-09-07 17:05 Phono 阅读(1665) | 评论 (3)编辑

2006年8月15日

转载一篇文章,感觉写得很不错,希望大家看了也有收获。
原文出处:http://www.itpub.net/612065.html
正文:
有了翅膀才能飞,欠缺灵活的代码就象冻坏了翅膀的鸟儿。不能飞翔,就少了几许灵动的气韵。我们需要给代码带去温暖的阳光,让僵冷的翅膀重新飞起来。结合实例,通过应用OOP、设计模式和重构,你会看到代码是怎样一步一步复活的。

为了更好的理解设计思想,实例尽可能简单化。但随着需求的增加,程序将越来越复杂。此时就有修改设计的必要,重构和设计模式就可以派上用场了。最后当设计渐趋完美后,你会发现,即使需求不断增加,你也可以神清气闲,不用为代码设计而烦恼了。

假定我们要设计一个媒体播放器。该媒体播放器目前只支持音频文件mp3和wav。如果不谈设计,设计出来的播放器可能很简单:
public class MediaPlayer
{
private void PlayMp3()
{
MessageBox.Show("Play the mp3 file.");
}

private void PlayWav()
{
MessageBox.Show("Play the wav file.");
}

public void Play(string audioType)
{
switch (audioType.ToLower())
{
case ("mp3"):
PlayMp3();
break;
case ("wav"):
PlayWav();
break;
}
}
}

自然,你会发现这个设计非常的糟糕。因为它根本没有为未来的需求变更提供最起码的扩展。如果你的设计结果是这样,那么当你为应接不暇的需求变更而焦头烂额的时候,你可能更希望让这份设计到它应该去的地方,就是桌面的回收站。仔细分析这段代码,它其实是一种最古老的面向结构的设计。如果你要播放的不仅仅是mp3和wav,你会不断地增加相应地播放方法,然后让switch子句越来越长,直至达到你视线看不到的地步。

好吧,我们先来体验对象的精神。根据OOP的思想,我们应该把mp3和wav看作是一个独立的对象。那么是这样吗?
public class MP3
{
public void Play()
{
MessageBox.Show("Play the mp3 file.");
}
}

public class WAV
{
public void Play()
{
MessageBox.Show("Play the wav file.");
}
}

好样的,你已经知道怎么建立对象了。更可喜的是,你在不知不觉中应用了重构的方法,把原来那个垃圾设计中的方法名字改为了统一的Play()方法。你在后面的设计中,会发现这样改名是多么的关键!但似乎你并没有击中要害,以现在的方式去更改MediaPlayer的代码,实质并没有多大的变化。

既然mp3和wav都属于音频文件,他们都具有音频文件的共性,为什么不为它们建立一个共同的父类呢?
public class AudioMedia
{
public void Play()
{
MessageBox.Show("Play the AudioMedia file.");
}
}

现在我们引入了继承的思想,OOP也算是象模象样了。得意之余,还是认真分析现实世界吧。其实在现实生活中,我们播放的只会是某种具体类型的音频文件,因此这个AudioMedia类并没有实际使用的情况。对应在设计中,就是:这个类永远不会被实例化。所以,还得动一下手术,将其改为抽象类。好了,现在的代码有点OOP的感觉了:
public abstract class AudioMedia
{
public abstract void Play();
}

public class MP3:AudioMedia
{
public override void Play()
{
MessageBox.Show("Play the mp3 file.");
}
}

public class WAV:AudioMedia
{
public override void Play()
{
MessageBox.Show("Play the wav file.");
}
}

public class MediaPlayer
{
public void Play(AudioMedia media)
{
media.Play();
}
}

看看现在的设计,即满足了类之间的层次关系,同时又保证了类的最小化原则,更利于扩展(到这里,你会发现play方法名改得多有必要)。即使你现在又增加了对WMA文件的播放,只需要设计WMA类,并继承AudioMedia,重写Play方法就可以了,MediaPlayer类对象的Play方法根本不用改变。

是不是到此就该画上圆满的句号呢?然后刁钻的客户是永远不会满足的,他们在抱怨这个媒体播放器了。因为他们不想在看足球比赛的时候,只听到主持人的解说,他们更渴望看到足球明星在球场奔跑的英姿。也就是说,他们希望你的媒体播放器能够支持视频文件。你又该痛苦了,因为在更改硬件设计的同时,原来的软件设计结构似乎出了问题。因为视频文件和音频文件有很多不同的地方,你可不能偷懒,让视频文件对象认音频文件作父亲啊。你需要为视频文件设计另外的类对象了,假设我们支持RM和MPEG格式的视频:
public abstract class VideoMedia
{
public abstract void Play();
}

public class RM:VideoMedia
{
public override void Play()
{
MessageBox.Show("Play the rm file.");
}
}

public class MPEG:VideoMedia
{
public override void Play()
{
MessageBox.Show("Play the mpeg file.");
}
}

糟糕的是,你不能一劳永逸地享受原有的MediaPlayer类了。因为你要播放的RM文件并不是AudioMedia的子类。

不过不用着急,因为接口这个利器你还没有用上(虽然你也可以用抽象类,但在C#里只支持类的单继承)。虽然视频和音频格式不同,别忘了,他们都是媒体中的一种,很多时候,他们有许多相似的功能,比如播放。根据接口的定义,你完全可以将相同功能的一系列对象实现同一个接口:
public interface IMedia
{
void Play();
}

public abstract class AudioMedia:IMedia
{
public abstract void Play();
}

public abstract class VideoMedia:IMedia
{
public abstract void Play();
}

再更改一下MediaPlayer的设计就OK了:
public class MediaPlayer
{
public void Play(IMedia media)
{
media.Play();
}
}

现在可以总结一下,从MediaPlayer类的演变,我们可以得出这样一个结论:在调用类对象的属性和方法时,尽量避免将具体类对象作为传递参数,而应传递其抽象对象,更好地是传递接口,将实际的调用和具体对象完全剥离开,这样可以提高代码的灵活性。

不过,事情并没有完。虽然一切看起来都很完美了,但我们忽略了这个事实,就是忘记了MediaPlayer的调用者。还记得文章最开始的switch语句吗?看起来我们已经非常漂亮地除掉了这个烦恼。事实上,我在这里玩了一个诡计,将switch语句延后了。虽然在MediaPlayer中,代码显得干净利落,其实烦恼只不过是转嫁到了MediaPlayer的调用者那里。例如,在主程序界面中:
Public void BtnPlay_Click(object sender,EventArgs e)
{
switch (cbbMediaType.SelectItem.ToString().ToLower())
{
IMedia media;
case ("mp3"):
media = new MP3();
break;
case ("wav"):
media = new WAV();
break;
//其它类型略;
}
MediaPlayer player = new MediaPlayer();
player.Play(media);
}
用户通过选择cbbMediaType组合框的选项,决定播放哪一种文件,然后单击Play按钮执行。

现在该设计模式粉墨登场了,这种根据不同情况创建不同类型的方式,工厂模式是最拿手的。先看看我们的工厂需要生产哪些产品呢?虽然这里有两种不同类型的媒体AudioMedia和VideoMedia(以后可能更多),但它们同时又都实现IMedia接口,所以我们可以将其视为一种产品,用工厂方法模式就可以了。首先是工厂接口:
public interface IMediaFactory
{
IMedia CreateMedia();
}

然后为每种媒体文件对象搭建一个工厂,并统一实现工厂接口:
public class MP3MediaFactory:IMediaFactory
{
public IMedia CreateMedia()
{
return new MP3();
}
}
public class RMMediaFactory:IMediaFactory
{
public IMedia CreateMedia()
{
return new RM();
}
}
//其它工厂略;

写到这里,也许有人会问,为什么不直接给AudioMedia和VideoMedia类搭建工厂呢?很简单,因为在AudioMedia和VideoMedia中,分别还有不同的类型派生,如果为它们搭建工厂,则在CreateMedia()方法中,仍然要使用Switch语句。而且既然这两个类都实现了IMedia接口,可以认为是一种类型,为什么还要那么麻烦去请动抽象工厂模式,来生成两类产品呢?

可能还会有人问,即使你使用这种方式,那么在判断具体创建哪个工厂的时候,不是也要用到switch语句吗?我承认这种看法是对的。不过使用工厂模式,其直接好处并非是要解决switch语句的难题,而是要延迟对象的生成,以保证的代码的灵活性。当然,我还有最后一招杀手锏没有使出来,到后面你会发现,switch语句其实会完全消失。

还有一个问题,就是真的有必要实现AudioMedia和VideoMedia两个抽象类吗?让其子类直接实现接口不更简单?对于本文提到的需求,我想你是对的,但不排除AudioMedia和VideoMedia它们还会存在区别。例如音频文件只需要提供给声卡的接口,而视频文件还需要提供给显卡的接口。如果让MP3、WAV、RM、MPEG直接实现IMedia接口,而不通过AudioMedia和VideoMedia,在满足其它需求的设计上也是不合理的。当然这已经不包括在本文的范畴了。

现在主程序界面发生了稍许的改变:
Public void BtnPlay_Click(object sender,EventArgs e)
{
IMediaFactory factory = null;
switch (cbbMediaType.SelectItem.ToString().ToLower())
{
case ("mp3"):
factory = new MP3MediaFactory();
break;
case ("wav"):
factory = new WAVMediaFactory();
break;
//其他类型略;
}
MediaPlayer player = new MediaPlayer();
player.Play(factory.CreateMedia());
}

写到这里,我们再回过头来看MediaPlayer类。这个类中,实现了Play方法,并根据传递的参数,调用相应媒体文件的Play方法。在没有工厂对象的时候,看起来这个类对象运行得很好。如果是作为一个类库或组件设计者来看,他提供了这样一个接口,供主界面程序员调用。然而在引入工厂模式后,在里面使用MediaPlayer类已经多余了。所以,我们要记住的是,重构并不仅仅是往原来的代码添加新的内容。当我们发现一些不必要的设计时,还需要果断地删掉这些冗余代码。
Public void BtnPlay_Click(object sender,EventArgs e)
{
IMediaFactory factory = null;
switch (cbbMediaType.SelectItem.ToString().ToLower())
{
case ("mp3"):
factory = new MP3MediaFactory();
break;
case ("wav"):
factory = new WAVMediaFactory();
break;
//其他类型略;
}
IMedia media = factory.CreateMedia();
media.Play();
}

如果你在最开始没有体会到IMedia接口的好处,在这里你应该已经明白了。我们在工厂中用到了该接口;而在主程序中,仍然要使用该接口。使用接口有什么好处?那就是你的主程序可以在没有具体业务类的时候,同样可以编译通过。因此,即使你增加了新的业务,你的主程序是不用改动的。

不过,现在看起来,这个不用改动主程序的理想,依然没有完成。看到了吗?在BtnPlay_Click()中,依然用new创建了一些具体类的实例。如果没有完全和具体类分开,一旦更改了具体类的业务,例如增加了新的工厂类,仍然需要改变主程序,何况讨厌的switch语句仍然存在,它好像是翅膀上滋生的毒瘤,提示我们,虽然翅膀已经从僵冷的世界里复活,但这双翅膀还是有病的,并不能正常地飞翔。

是使用配置文件的时候了。我们可以把每种媒体文件类类型的相应信息放在配置文件中,然后根据配置文件来选择创建具体的对象。并且,这种创建对象的方法将使用反射来完成。首先,创建配置文件:

<appSettings>
<add key="mp3" value="WingProject.MP3Factory" />
<add key="wav" value="WingProject.WAVFactory" />
<add key="rm" value="WingProject.RMFactory" />
<add key="mpeg" value="WingProject.MPEGFactory" />
</appSettings>

然后,在主程序界面的Form_Load事件中,读取配置文件的所有key值,填充cbbMediaType组合框控件:
public void Form_Load(object sender, EventArgs e)
{
cbbMediaType.Items.Clear();
foreach (string key in ConfigurationSettings.AppSettings.AllKeys)
{
cbbMediaType.Item.Add(key);
}
cbbMediaType.SelectedIndex = 0;
}

最后,更改主程序的Play按钮单击事件:
Public void BtnPlay_Click(object sender,EventArgs e)
{
string mediaType = cbbMediaType.SelectItem.ToString().ToLower();
string factoryDllName = ConfigurationSettings.AppSettings[mediaType].ToString();
IMediaFactory factory = (IMediaFactory)Activator.CreateInstance("MediaLibrary",factoryDllName).Unwrap();//MediaLibray为引用的媒体文件及工厂的程序集;
IMedia media = factory.CreateMedia();
media.Play();
}

现在鸟儿的翅膀不仅仅复活,有了可以飞的能力;同时我们还赋予这双翅膀更强的功能,它可以飞得更高,飞得更远!

享受自由飞翔的惬意吧。设想一下,如果我们要增加某种媒体文件的播放功能,如AVI文件。那么,我们只需要在原来的业务程序集中创建AVI类,并实现IMedia接口,同时继承VideoMedia类。另外在工厂业务中创建AVIMediaFactory类,并实现IMediaFactory接口。假设这个新的工厂类型为WingProject.AVIFactory,则在配置文件中添加如下一行:
<add key="AVI" value="WingProject.AVIFactory" />。
而主程序呢?根本不需要做任何改变,甚至不用重新编译,这双翅膀照样可以自如地飞行!

posted @ 2006-08-15 13:15 Phono 阅读(155) | 评论 (4)编辑