2010年2月

C#面向对象设计模式纵横谈
25. 设计模式总结
李建忠
jianzhong.lee@gmail.com
设计模式论坛:
forum.softcompass.com
上海祝成科技高级培训讲师
www.softcompass.com

 

创建型模式
• Singleton模式解决的是实体对象个数的问题。除
了Singleton之外,其他创建型模式解决的都是
new所带来的耦合关系。
• Factory Method, Abstract Factory, Builder都需要
一个额外的工厂类来负责实例化“易变对象”,而
Prototype则是通过原型(一个特殊的工厂类)来
克隆“易变对象”。
• 如果遇到“易变类”,起初的设计通常从Factory
Method开始,当遇到更多的复杂变化时,再考虑
重构为其他三种工厂模式( Abstract Factory,
Builder , Prototype )。

结构型模式
• Adapter模式注重转换接口,将不吻合的接口适配对接
• Bridge模式注重分离接口与其实现,支持多维度变化
• Composite模式注重统一接口,将“一对多”的关系转化为
“一对一”的关系
• Decorator模式注重稳定接口,在此前提下为对象扩展功

• Façade模式注重简化接口,简化组件系统与外部客户程
序的依赖关系
• Flyweight 模式注重保留接口,在内部使用共享技术对对
象存储进行优化
• Proxy 模式注重假借接口,增加间接层来实现灵活控制

 

行为型模式(1)
• Template Method模式封装算法结构,支持算法
子步骤变化
• Strategy模式注重封装算法,支持算法的变化
• State模式注重封装与状态相关的行为,支持状态
的变化
• Memento模式注重封装对象状态变化,支持状态
保存/恢复
• Mediator模式注重封装对象间的交互,支持对象
交互的变化

 

行为型模式(2)
• Chain Of Responsibility模式注重封装对象责任,支持责
任的变化
• Command模式注重将请求封装为对象,支持请求的变化
• Iterator 模式注重封装集合对象内部结构,支持集合的变

• Interpreter模式注重封装特定领域变化,支持领域问题的
频繁变化
• Observer模式注重封装对象通知,支持通信对象的变化
• Visitor模式注重封装对象操作变化,支持在运行时为类层
次结构动态添加新的操作。

 

推荐资源
• 《设计模式:可复用面向对象软件的基础》GoF
• 《面向对象分析与设计》Grady Booch
• 《敏捷软件开发:原则、模式与实践》Robert C. Martin
• 《重构:改善既有代码的设计》Martin Fowler
• 《Refactoring to Patterns》Joshua Kerievsky

在使用.NET的过程中,数据库访问是一个很重要的部分,特别是在B/S系统的构建过程中,数据库操作几乎成为了一个必不可少的操作。调用存储过程实现数据库操作使很多程序员使用的方法,而且大多数的程序员都是能使用存储过程就使用存储过程,很少直接使用SQL语句,所以存储过程是很有用而且很重要的。

  存储过程简介

  简单的说,存储过程是由一些SQL语句和控制语句组成的被封装起来的过程,它驻留在数据库中,可以被客户应用程序调用,也可以从另一个过程或触发器调用。它的参数可以被传递和返回。与应用程序中的函数过程类似,存储过程可以通过名字来调用,而且它们同样有输入参数和输出参数。

  根据返回值类型的不同,我们可以将存储过程分为三类:返回记录集的存储过程, 返回数值的存储过程(也可以称为标量存储过程),以及行为存储过程。顾名思义,返回记录集的存储过程的执行结果是一个记录集,典型的例子是从数据库中检索出符合某一个或几个条件的记录;返回数值的存储过程执行完以后返回一个值,例如在数据库中执行一个有返回值的函数或命令;最后,行为存储过程仅仅是用来实现数据库的某个功能,而没有返回值,例如在数据库中的更新和删除操作。

  使用存储过程的好处

  相对于直接使用SQL语句,在应用程序中直接调用存储过程有以下好处:

  (1)减少网络通信量。调用一个行数不多的存储过程与直接调用SQL语句的网络通信量可能不会有很大的差别,可是如果存储过程包含上百行SQL语句,那么其性能绝对比一条一条的调用SQL语句要高得多。

  (2)执行速度更快。有两个原因:首先,在存储过程创建的时候,数据库已经对其进行了一次解析和优化。其次,存储过程一旦执行,在内存中就会保留一份这个存储过程,这样下次再执行同样的存储过程时,可以从内存中直接调用。

  (3)更强的适应性:由于存储过程对数据库的访问是通过存储过程来进行的,因此数据库开发人员可以在不改动存储过程接口的情况下对数据库进行任何改动,而这些改动不会对应用程序造成影响。

  (4) 布式工作:应用程序和数据库的编码工作可以分别独立进行,而不会相互压制。

  由以上的分析可以看到,在应用程序中使用存储过程是很有必要的。

  两种不同的存储过程调用方法

  为了突出新方法的优点,首先介绍一下在.NET中调用存储过程的“官方”方法。另外,本文的所有示例程序均工作于SqlServer数据库上,其它情况类似,以后不再一一说明。本文所有例子均采用C#语言。

  要在应用程序中访问数据库,一般性的步骤是:首先声明一个数据库连接SqlConnection,然后声明一个数据库命令SqlCommand,用来执行SQL语句和存储过程。有了这两个对象后,就可以根据自己的需要采用不同的执行方式达到目的。需要补充的是,不要忘记在页面上添加如下的引用语句:using System.Data.SqlClient。

  就执行存储过程来说,如果执行的是第一类存储过程,那么就要用一个DataAdapter将结果填充到一个DataSet中,然后就可以使用数据网格控件将结果呈现在页面上了;如果执行的是第二和第三种存储过程,则不需要此过程,只需要根据特定的返回判定操作是否成功完成即可。

  (1)执行一个没有参数的存储过程的代码如下:

SqlConnection conn=new SqlConnection(“connectionString”);
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = new SqlCommand();
da.SelectCommand.Connection = conn;
da.SelectCommand.CommandText = "NameOfProcedure";
da.SelectCommand.CommandType = CommandType.StoredProcedure;


  然后只要选择适当的方式执行此处过程,用于不同的目的即可。

  (2)执行一个有参数的存储过程的代码如下(我们可以将调用存储过程的函数声明为ExeProcedure(string inputdate)):

SqlConnection conn=new SqlConnection(“connectionString”);
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = new SqlCommand();
da.SelectCommand.Connection = conn;
da.SelectCommand.CommandText = "NameOfProcedure";
da.SelectCommand.CommandType = CommandType.StoredProcedure;
(以上代码相同,以下为要添加的代码)
param = new SqlParameter("@ParameterName", SqlDbType.DateTime);
param.Direction = ParameterDirection.Input;
param.Value = Convert.ToDateTime(inputdate);
da.SelectCommand.Parameters.Add(param);


  这样就添加了一个输入参数。若需要添加输出参数:

param = new SqlParameter("@ParameterName", SqlDbType.DateTime);
param.Direction = ParameterDirection.Output;
param.Value = Convert.ToDateTime(inputdate);
da.SelectCommand.Parameters.Add(param);


  若要获得参储过程的返回值:

param = new SqlParameter("@ParameterName", SqlDbType.DateTime);
param.Direction = ParameterDirection.ReturnValue;
param.Value = Convert.ToDateTime(inputdate);
da.SelectCommand.Parameters.Add(param);


  从上面的代码我们可以看出,当存储过程比较多或者存储过程的参数比较多时,这种方法会大大影响开发的速度;另外一方面,如果项目比较大,那么这些用于数据库逻辑的函数在以后的维护中也是一个很大的负担。那么,有没有一种改进的方法可以解决这个问题呢?想到在执行没有参数的存储过程时只需要传入一个存储过程的名字就可以调用相应的存储过程,而且在SqlServer数据库中我们可以直接在查询分析器中敲入“存储过程名(参数列表)”样的字符串就可以执行存储过程,那么,是否可以把这种思想应用到应用程序中呢?

  于是在编译器中键入相应代码。这些代码是在调用不带参数的存储过程的代码的基础上改的。具体代码如下:

SqlConnection conn=new SqlConnection(“connectionString”);
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = new SqlCommand();
da.SelectCommand.Connection = conn;
da.SelectCommand.CommandText = "NameOfProcedure(’para1’,’para2’,para3)";
da.SelectCommand.CommandType = CommandType.StoredProcedure;


  为了使代码更具有代表性,要调用的存储过程的第一个和第二个参数都为字符串类型,第三个参数为整型。执行以后发现,完全可以达到预期的效果!

  两种调用方法的比较
  
  通过比较我们可以看到,第二种方法具有一个很明显的优点,那就是可以提高开发速度,节省开发时间,而且代码容易维护,在一定程度上也减少了系统大小。但是,由于对存储过程参数的处理比较笼统,如果要获取输出参数或者得到存储过程的返回值,这种方法就不能满足需要了。虽然如此,但是,这种方法毕竟可以让开发人员少些很大一部分的代码。如果不需要获取输出参数和返回值,那么几乎可以做到“一劳永逸”。因此在实际的程序开发中,这种方法还是具有一定的实用价值的。

据《北京晚报》、新华网等多家媒体昨日报道,工信部向各地通信管理局等单位印发通知,进一步落实网站备案信息真实性核验通知中涉及的备案核验试行方案明确规定,个人是网站的合格主办者;与此同时,网站备案将需要负责人提交彩色正面免冠照。此次工信部在进一步落实网站备案信息真实性核验的同时,明确个人可办网站,应当视为对一度收紧的互联网管理政策的一次重要调整。在此之前,为加强域名注册信息审核工作,中国互联网络信息中心(CNNIC)曾发布公告规定,从2009年12月14日上午9时起,停止个人用户CN域名注册。从个人无法申请注册CN域名到再次确认个人是网站的合格主办者,充分体现出了互联网管理上的理性,但从这样的过程中,有必要认真反思我们在互联网管理上的心态和方法。

  应当说,在网络时代,个人网站的存在是一种必然。互联网是一个开放、平等的信息交流平台,个人申办、维护网站是互联网最重要的存在形式之一。但在停止个人用户CN域名注册仅仅2个多月后再次做出的调整,这一个关系到数以亿计的中国网民、数以千万计的国内域名用户的政策出台,事先是否有过足够审慎而周全的考虑?事实上我们也看到,这种通过压缩个人网站生存空间的方式来规范整治网络的政策,也在很大程度上影响到了CN域名的信誉。岁尾年初,国际域名在中国的注册量突然暴增,CN域名数量剧减就是一个例证。

  我们必须承认,互联网并非完全洁净,也不能看做一个管理的真空。当前在域名注册过程中确实存在注册信息不真实、不准确、不完整的问题,也有不少个人网站也还存在传播非法和不良信息等各种问题存在。但很显然,加强对互联网的管理和净化确有必要,但并不意味着要视个人网站为洪水猛兽,因噎废食也非应当之举。

  从面向未来的角度来说,我们应当将此次个人网站管理上的弯路作为一次教训来吸取。我们在互联网管理的心态和方法还应当更加跟上时代的发展,适应技术进步和社会结构变迁的现实。个人网站需要规范,现有网站中存在的问题也需要整治,但这种规范和整治必须以尊重个人、尊重社会愿望、尊重网络传播的基本特征和基本原则为前提。在网络社会迅猛发展,个人权利日渐拓展的社会现实面前,一刀切下,不是一种正确的办法。

  社会在走向进步,公民社会也在不断发育,需要政府在制定公共政策时更加科学、更加理性,具有更加开放的心态,也需要在社会管理的各个方面都要走出单一的管制思维,走出完全以便利管理为目的的取向,与社会一起现代化。(作者: 李琼)

Microsoft.NET框架介绍了几个新功能,旨在简化应用程序发布和解决 DLL Hell。本文介绍了DLL
Hell问题的产生原因。

 

.NET框架与DLL Hell问题:版本问题

从客户的角度,最常见的版本问题就是我们所说的 DLL Hell 问题。简单地讲, DLL Hell
是指当多个应用程序试图共享一个公用组件(如某个动态连接库(DLL)或某个组件对象模型(COM)类)时所引发的一系列问题。最典型的情况是,某个应用程序将要安装一个新版本的共享组件,而该组件与机器上的现有版本不向后兼容。虽然刚安装的应用程序运行正常,但原来依赖前一版本共享组件的应用程序也许已无法再工作。在某些情况下,问题的起因更加难以预料。比如,当用户浏览某些
Web 站点时会同时下载某个 Microsoft ActiveX?
控件。如果下载该控件,它将替换机器上原有的任何版本的控件。如果机器上的某个应用程序恰好使用该控件,则很可能也会停止工作。

在许多情况下,用户需要很长时间才会发现应用程序已停止工作。结果往往很难记起是何时的机器变化影响到了该应用程序。用户可能会回忆起一周前安装了一些东西,但安装与目前看到的状态并没有任何明显的关联。
更糟的是,现在很少有诊断工具帮助用户(或帮助他们的技术支持人员)确定有什么问题。

这些问题的原因是应用程序不同组件的版本信息没有由系统记录或加强。而且,系统为某个应用程序所做的改变会影响机器上的所有应用程序—现在建立完全从变化中隔离出来的应用程序并不容易。 

很难建立一个隔离应用程序的一个原因是当前运行时环境只允许单独版本组件或应用程序的安装。这个限制意味着组件的编写者必须以向后兼容的方式编写他们的代码,否则当他们安装新组件的时候会有终止已有应用程序的风险。实际上,如果可能的话,编写永远向后兼容的代码是非常难的。在
.NET 中,side by side 概念是版本问题的核心。"Side by side"
是在同一台机器上同时运行不同版本的相同组件的能力。使用支持并列的组件,编程人员不必努力维护严格的向后兼容,因为不同的应用程序自由使用某个共享组件的不同版本。

.NET框架与DLL Hell问题:发布和安装

现在安装应用程序是多步过程。一般,安装一个应用程序包括复制许多软件组件到磁盘,和在系统中进行一系列描述那些组件的注册项。 

注册表中的项和磁盘上文件的分隔使复制应用程序和卸载他们非常困难。而且,在注册表中完全描述某个 COM
类所需的许多项之间关系非常松散。这些项常常包括联合类、接口、类型库和 DCOM app ID
的项,不涉及任何放在注册表文档扩展或组件类别的项。要时常手工保持这些项的同步。

最后,需要该注册足迹激活任何 COM
类。这极大地复杂了发布分布式应用程序的过程,因为必须到每个客户端的机器进行适当的注册项。

如今另一个共同问题是:对一个正在运行的应用程序进行更新是不现实的。这是 Web 应用程序最大的问题,Web
应用程序必须停止工作然后重启动以更新应用程序使用的 COM 类。 

这些问题主要由从组件自己分离传来的组件描述引起的。换句话说,应用程序不是自描述的和独立的。

以上就对需要.NET框架解决的DLL Hell问题进行了简单的描述。

C# 中实现 Singleton 具有下列优缺点:

优点

  • 由于 .NET Framework 显式地指定静态变量初始化如何以及何时发生,因此静态初始化方法是可能的。

  • 列的前面的"多线程 Singleton"中所描述的 Double-Check Locking
    技术已在公共语言运行库中正确实现。

缺点

如果您的多线程应用程序需要进行显式初始化,那么必须采取措施以避免线程问题。