2004年10月16日

博客园的功能比这里强好多,更合适 程序写作,所以 转移了

http://www.cnblogs.com/9527

感谢 donews让我认识了什么是blog

2004年09月15日

 

Microsoft PetShop 3.0 设计与实现   分析报告数据访问层

 

 最近对多层系统以及.net很感兴趣,从而学习分析了一下Microsoft公开的多层范例系统――PetShop,现在的版本是3.0,这个版本和以前的版本从设计上已有一定的区别,应该是和JavaPetshop设计相当。

关于一些Microsoft PetShop的来由、如何安装,所表现业务流程,数据库表结构,以及3.0版本与以前版本的区别等基本的信息的资料请大家参考下面文章

 http://msdn.microsoft.com/library/en-us/dnbda/html/bdasamppet.asp

另外建议先看一下这篇文章:

http://msdn.microsoft.com/library/en-us/dnbda/html/petshop3x.asp

 

 

本文将以设计和实现紧密结合的方式来分析,这也是我们广大实践型的软件开发人员的风格。先看一下设计图和具体实现VS.NET工程的表格。

 

MSPetShop 3.0 系统结构图:

从图中可以看到系统大体分为Presentation,Business Logic,Data Access 三层,每层中又有子层。每层(也包括子层)各司其职,又互相协作,本文顺序以此图为准,从下到上分析说明。

 

对应上图,具体的.NET Project实现列表(借用MS文章中的列表不用翻译了吧)

Project

Purpose

BLL

Home for business logic components

ConfigTool

Administration application used to encrypt connection strings and create event log source

DALFactory

Classes used to determine which database access assembly to load

IDAL

Set of interfaces which need to be implemented by each DAL implementation

Model

Thin data classes or business entities

OracleDAL

Oracle specific implementation of the Pet Shop DAL which uses the IDAL interfaces

Post-Build

Project to run post compile actions such as adding assemblies to the GAC or COM+

Pre-Build

Project to remove assemblies from the GAC or unregister assemblies from COM+

SQLServerDAL

Microsoft SQL Server specific implementation of the Pet Shop DAL which uses the IDAL interfaces

Utility

Set of helper classes including a wrapper for the DPAPI

Web

Web pages and controls

Solution Items

Miscellaneous items used to build the application such as Pet Shop.snk key file used to sign application assemblies

 

 

另外我写这篇文章时是一边看源码一边写,所以建意大家最好安装一个Petshop3,因为时间仓促,水平有限,如我有不对之处请给我发Email更正。email:cocoboy79@163.com      qq:364941

 

首先我们来看一下DAL层。

 

一:Data Access Layer:

1 PetShop.Utility如下图:(上表中Utility为其实现工程)

 

这个名字空间有两个类,一个类是DataProtector调用了Crypt32.dll和kernel32.dll实现一些加密解密安全操作,而ConnectionInfo类调用DataProtector的功能来用于加密解密数据库连接信息,这个ConnectionInfo提供的功能要在下面的PetShop.XXXDAL名字空间中用,可见Petshop.Utility是起到的是数据访问辅助工具的作用,如果有兴趣研究加密解密方法可以看一下DataProtector的源码,以及Cryp32.dll的使用。

2 PetShop.SQLServerDAL ――系统结构图中DAL层中的SqlServer DAL子层实现

SqlHelper类实际上是封装了关于此系统中数据库操作访问的一些常用功能,其中它还会调用上面的PetShop.Utility中的ConectionInfo类方法加密解密连接字符串,如:ConnectionInfo.DecryptDBConnectionString方法。SqlHelper类是基于Microsoft Data Access Application Block for .NET。这个东西是用来帮助用户更好的在.NET的访问数据。如MS一段话:Are you involved in the design and development of data access code for .NET-based applications? Have you ever felt that you write the same data access code again and again? Have you wrapped data access code in helper functions that let you call a stored procedure in one line? If so, the Microsoft? Data Access Application Block for .NET is for you。其实可以自已写一个类似SqlHelper的东西,以实现一般化的对数据库的操作,以在各项目中重用,当然也可以使用现在的MS为你做好的这个SqlHelper或是Microsoft? Data Access Application Block for .NET,避免不同项目中总是写同样的重复的数据库访问程序。有时间最好还是看一下SqlHelper的具体程序实现思路以及所提到的那个Microsoft Data Access Application Block for .NET。不过这里我们的SqlHelper应该只是部分实现。更全面信息请参看:http://msdn.microsoft.com/library/en-us/dnbda/html/daab-rm.asp

 

Account类对用户帐户进行操作如Insert,Update,SignIn,其中这些对数据库的操作,使用了上面的SqlHelper类来实现。另外Inventory和Order,Product,Profile和Account类的都是同样对数据库相关表进行操作,程序风格一致,这些类中对数据库的操作都是通过此名字空间下的SqlHelper类进行的,例如,下面语句:

private const string SQL_INSERT_SIGNON = “INSERT INTO SignOn VALUES (@UserId, @Password)”;

private const string PARM_USER_ID = “@UserId”;

private const string PARM_PASSWORD = “@Password”;

来定义一个sql语句,以及声明其中可变参数,并且用一些xxxx Parameters的private方法来处理上面语句中的参数,然后像下面这样用SqlHelper类的合适的方法执行:

SQLHelper.ExecuteNonQuery(trans, CommandType.Text, SQL_INSERT_SIGNON, signOnParms);

最后在SQLHelper.ExecuteNonQuery实现中,再调用ado.net中的相关类最终执行对数据库的操作,可见SqlHelper在这里又封装了一下ado.net相关类以优化数据操作。正如SqlHelper.cs中注释提示:The SqlHelper class is intended to encapsulate high performance,  scalable best practices for common uses of SqlClient.下面是SqlHelper. ExecuteNonQuery的实现内容:

public static int ExecuteNonQuery(string connString, CommandType cmdType, string cmdText, params SqlParameter[] cmdParms) {

              //注:运行时cmdText的实参就是SQL_INSERT_SIGNON

              SqlCommand cmd = new SqlCommand();

              using (SqlConnection conn = new SqlConnection(connString)) {

                   PrepareCommand(cmd, conn, null, cmdType, cmdText, cmdParms);

                   int val = cmd.ExecuteNonQuery();

                  cmd.Parameters.Clear();

                   return val;

              }

         }

另外Inventory和Order,Product,Profile和Account类的声明都是像public class Account : IAccount这样实现某个相关的接口,像IAccount这样的接口是在PetShop.IDAL中声明的,见后面介绍。

3 PetShop.OracleDAL ―――系统结构图中 DAL层的OracleDAL子层实现

个人认为结构应该同上面的PetShop. SQLServerDAL,另外SqlHelper变成了OraHelper,在OraHelper中当然具体实现了对特定的Oracle数据库的联接操作,看一下源程序很明显原来的     SqlCommand cmd = new SqlCommand(); 变成了OracleCommand cmd = new OracleCommand();。

 

注意一下:在系统结构图中的DAL层还有两个XXX DAAB的子层,它们对应的实现在哪里呢? 下面对应一下:

以下是左边是图中 DataAccessLayer的各部分,右边是具体实现所在名字空间或类

SqlServer DAL――PetShop.SQLServerDAL名字空间

Sql DAAB――PetShop.SqlServerDal.SqlHelper类

Oracle DAL――PetShop.OracleDAL名字空间

Oracle DAAB――PetShop.OracleDAL.OraHelper类

 

4 PetShop.IDAL 数据访问接口――对应系统结构图中DAL Interface

接口是一种系列‘功能’的声明或名单,接口没有实现细节,如下接口IAccount定义也可以看出IAccount只有声明:

using System;

using PetShop.Model;

 

namespace PetShop.IDAL

{

// Inteface for the Account DAL

     public interface IAccount

     {

   // Authenticate a user

         AccountInfo SignIn(string userId, string password);

         /// Get a user’s address stored in the database

         AddressInfo GetAddress(string userId);

         /// Insert an account into the database

         void Insert(AccountInfo account);

         /// Update an account in the database

         void Update(AccountInfo Account);

     }

}

 

您只需要调用接口,而不用管接口是如何实现的,那么接口没有实现,调用它有什么用?实际上接口的实现是由某个类来做的,那么这里的IAccount接口是由PetShop.SqlServerDAL.Account类或是PetShop.OracleDAL.Account类来实现的,从他们的定义可以看到:

public class Account : IAccount {…….}

即然同一接口可以有多个实现类,这样对于使用接口的程序来说,无论有多个少实现类,始终还是使用这个接口,这样就统一了外观。当上层BLL层调用此接口方法时不用知道这个接口由哪个类实现的。那谁来确定使用哪个类的实现?请再看下面。

PetShop.IDAL下的其它接口如Iitem,Iorder等和IAccount一样,故在此略过。)

 

5 PetShop.DALFactory 数据访问工厂

 

工厂模式是设计模式的一种,以我理解就像Factory这个词一样,对于用户来说,工厂里产品如何生产的你不用知道,你只要去用工厂里生产出来的东西就可以了。MSPetShop3.0用工厂模式来实现了对SqlServer和Oracle数据库访问的操作,而用户(business Logic Layer)不用知道也不用关心后台用的是哪一种数据库,它只要用接口就行了,接口中定义了要用的方法,当调用接口时会根据具体的情况再去调用底层数据访问操作。而现在这个DALFactory就是关键,当BLL层要操作数据库时,DALFactory会根据具体情况再去使用本文上面介绍的SqlServerDAL和OracleDAL中的一个。这样系统上层只管调用,而下层来实现细节,上级只管发号施令,下级去干活。对于上层来说实现细节被隐藏了。

那么DALFactory是如何决定应该用SqlServerDAL还是用OracleDAL的呢?我们接着分析。

   以下是PetShop.DALFactory.Account类的实现:

  namespace PetShop.DALFactory {

    

     /// <summary>

     /// Factory implementaion for the Account DAL object

     /// </summary>

     public class Account

     {

         public static PetShop.IDAL.IAccount Create()     //<<<<?—-这里返回接口

         {            

              /// Look up the DAL implementation we should be using

              string path = System.Configuration.ConfigurationSettings.AppSettings["WebDAL"];

              string className = path + “.Account”;

 

              // Using the evidence given in the config file load the appropriate assembly and class

              return (PetShop.IDAL.IAccount) Assembly.Load(path).CreateInstance(className);

         }

     }

}

  以下则是web.config中<appSettings>节点中的一部分

<add key=”WebDAL” value=”PetShop.SQLServerDAL” />

    <add key=”OrdersDAL” value=”PetShop.SQLServerDAL” /> 

<add key=”Event Log Source” value=”.NET Pet Shop” />

上面的Create()方法返回IAccount接口,用System.Configuration.ConfigurationSettings.AppSettings["WebDAL"];则可以得到Web.config的<appsettings>节点中的关于系统中应该使用哪个数据访问层(SqlserverDAL还是OracleDAL)的信息。因为我在安装PetShop3.0时选择的是Sqlserver所以在此是:value=”PetShop.SQLServerDAL”,如果用的是Oracle那就是value=”PetShop.OracleDAL” 了吧!而且这个文件也应该是可以更改的。接下来className=path+.Account返回的应该是PetShop.SQLServerDAL.Account,然后再用Assembly.Load加载PetShop.SQLServerDAL.dll,同时创建PetShop.SQLServerDAL.Account的实例,并以接口(PetShop.IDAL.IAccount)类型返回。这样BLL调用IAccount接口时就会用PetShop.SQLServerDAL.Account类的实现代码。(回上面第4再看一下)

 

看!这样根据系统当前Web.config文件的配置描述(这也应该是系统运行时实际的配置),BLL层只要像下面这样:

// Get an instance of the account DAL using the DALFactory

              IAccount dal = PetShop.DALFactory.Account.Create();

AccountInfo account = dal.SignIn(userId, password);//<<?—-看看上面第4点的IAccount接口

就可以直接调用接口方法通过下层DAL层操作数据库了(在此具体为用户账号相关操作),而BLL层并不用知道应该通过SqlserverDAL还是OracleDAL访问数据库,这由都DAL Factory决定,你用的是什么数据库以及底层细节,更不用BLL知道,这样做的好处是对于BLL层以及更上层的程序不会或很少机率会因为底层程序变动影响,因为BLL层中调用接口就行了,只要那个接口定义没变,一切仍然OK.

 

 

6  PetShop.ConfigTool



首先在..\Microsoft\PetShop\ConfigTool\中有一个app.config文件,看一下其中内容,分别定义了两种数据库的联接字符串,在app.config中有一行  <add key=”WebConfigFileLocation” value=”Web\Web.config” /> 则标识出给asp.net程序使用的web.config配置文件的相对位置。然后看一下PetShopConnectionString的EncryptConnectionString方法的源码,这个类中先是从当前目录的app.config文件中读出web.config文件的位置,如下:

public static readonly string CONFIGFILE = ConfigurationSettings.AppSettings["WebConfigFileLocation"];

 

然后语句

XmlDocument doc = new XmlDocument();

doc.Load(CONFIGFILE);

加载Web.config文件,最后将加密的连接字符串写入Web.config对应的XML节点中。以供Asp.net应用程序使用。其中的加密还是使用PetShop.Utility。

ConfigConsole,调用PetShopConnectionString类EncryptConnectionString执行对联接字符串进行加密。另外PetShopEventLog类也是在ConfigConsole中使用的。用于记录程序日志。

所以在最后部署时Web.config的连接字符串是加密的。

 

7 PetShop.Model   业务实体模型

    以下是AccountInfo类的程序:

 

namespace PetShop.Model {

 

     /// <summary>

     /// Business entity used to model accounts

     /// </summary>

     [Serializable]

     public class AccountInfo {

 

         // Internal member variables

         private string _userId;

         private string _password;

         private string _email;

         private AddressInfo _address;

         private string _language;

         private string _category;

         private bool _showFavorites;

         private bool _showBanners;

 

         public AccountInfo() {

         }

    

         public AccountInfo(string userId, string password, string email, AddressInfo address, string language, string category, bool showFavorites, bool showBanners) {

              this._userId = userId;

              this._password = password;

              this._email = email;

              this._address = address;

              this._language = language;

              this._category = category;

              this._showFavorites = showFavorites;

              this._showBanners = showBanners;

         }

 

         // Properties

         public string UserId {

              get { return _userId; }

         }

         public string Password {

              get { return _password; }

         }

         public string Email {

              get { return _email; }

         }

         public AddressInfo Address {

              get { return _address; }

         }

         public string Language {

              get { return _language; }

         }

         public string Category {

              get { return _category; }

         }

         public bool IsShowFavorites {

              get { return _showFavorites; }

         }

         public bool IsShowBanners {

              get { return _showBanners; }

         }

     }

}

   这个本来想在分析BLL层时再说,但是在SqlServerDALOracleDAL中都使用了这些Model,无论怎么样,上层的程序执行最终结果都是要操作数据库,而数据库是关系型,不是面向对象的,那就得把平面的‘表’结合业务规则抽象成类,这样想办法让上层(BLL及以上)以为自已在操作类而不是数据库表,从而使‘它们’感觉没有数据库的存在,上层只管面向对象编程就可以了。我想现在所说的OR-MAPPING的目的也是如此吧(只是猜想啊,希望是这样的,错了请指出)。我们在上面第2中已看到,在PetShop中的SqlServerDALOracleDAL中定义Sql语句,然后根据上层调用,把此Sql语句作为参数,并最终在SqlHelper执行。再仔细看看SqlServerDAL的程序,会找到类似下面的程序:

     private void SetAccountParameters(SqlParameter[] parms, AccountInfo acc) {

              parms[0].Value = acc.Email;

              parms[1].Value = acc.Address.FirstName;

              parms[2].Value = acc.Address.LastName;

              parms[3].Value = acc.Address.Address1;

              parms[4].Value = acc.Address.Address2;

              parms[5].Value = acc.Address.City;

              parms[6].Value = acc.Address.State;

              parms[7].Value = acc.Address.Zip;

              parms[8].Value = acc.Address.Country;

              parms[9].Value = acc.Address.Phone;

              parms[10].Value = acc.UserId;

         }

parms[x]就是那些有声明为常量的有参数的Sql语句中的参数,这里用Model中的AccountInfo的各Field和这些参数Mapping。所以对于BLL层,只知道这些Model就可以了,反正最后在SqlServerDAL或是OracleDALModel的成员们会Mapping到参数中以存取数据库(为什么不直接Mapping到数据集的字段呢?我想应该是因为这里数据库操作直接给SqlHeper的原因,不然就没必要用SqlHelper了。于是才又多了图中的xxx DAAB层,这样Mapping给参数再传给DAAB来处理)

 

 Data Access Layer小结:

     DAL完成数据库访问任务,上层(BLL)直需调用接口即可,不用知道具体访问细节,也不用知道有什么样的数据库和数据库表等信息,这是用Factory模式和上面第7的方法来实现。

 

2003-9-19

                                                                                         cocoboy79                 

 

每个.Net开发人员应该下载的十种必备工具
作者:James Avery   来源:MSDN   时间:2004年8月4日 23:05   阅读103
 

本文讨论:

?

用于编写单元测试的 NUnit

?

用于创建代码文档资料的 NDoc

?

用于生成解决方案的 NAnt

?

用于生成代码的 CodeSmith

?

用于监视代码的 FxCop

?

用于编译少量代码的 Snippet Compiler

?

两种不同的转换器工具:ASP.NET 版本转换器和 Visual Studio .NET 项目转换器

?

用于生成正则表达式的 Regulator

?

用于分析程序集的 .NET Reflector

本文使用了下列技术:

.NET、C# 或 Visual Basic .NET、Visual Studio .NET

除非您使用能够获得的最佳工具,否则您无法期望生成一流的应用程序。除了像 Visual Studio?.NET 这样的著名工具以外,还可以从 .NET 社区获得许多小型的、不太为人所知的工具。在本文中,我将向您介绍一些目前可以获得的、面向 .NET 开发的最佳免费工具。我将引导您完成一个有关如何使用其中每种工具的快速教程 — 一些工具在许多时候可以使您节约一分钟,而另一些工具则可能彻底改变您编写代码的方式。因为我要在本篇文章中介绍如此之多的不同工具,所以我无法详尽讨论其中每种工具,但您应该了解到有关每种工具的足够信息,以便判断哪些工具对您的项目有用。

Snippet Compiler

Snippet Compiler 是一个基于 Windows? 的小型应用程序,您可以通过它来编写、编译和运行代码。如果您具有较小的代码段,并且您不希望为其创建完整的 Visual Studio .NET 项目(以及伴随该项目的所有文件),则该工具将很有用。

例如,假设我希望向您说明如何从 Microsoft?.NET 框架中启动另一个应用程序。在 Snippet Compiler 中,我将通过新建一个能够创建小型控制台应用程序的文件开始。可以在该控制台应用程序的 Main 方法内部创建代码片段,而这正是我要在这里做的事情。下面的代码片段演示了如何从 .NET 框架中创建记事本实例:

System.Diagnostics.Process proc = new System.Diagnostics.Process(); proc.StartInfo.FileName= "notepad.exe"; proc.Start(); proc.WaitForExit(); 

当然该代码片段本身无法编译,而这正是 Snippet Compiler 的用武之地。图 1 显示了 Snippet Compiler 中的这一代码示例。


1 Snippet Compiler

要测试该代码片段,只须按 play(运行)按钮(绿色三角形),它就会在调试模式下运行。该代码片段将生成一个弹出式控制台应用程序,并且将显示记事本。当您关闭记事本时,该控制台应用程序也将关闭。

就我个人而言,我是在尝试为某位向我求助的人士创建一个小型示例时,才发现 Snippet Compiler 是如此宝贵的 — 如果不使用该工具,则我通常必须新建一个项目,确保每个部分都能编译通过,然后将代码片段发送给求助者,并删除该项目。Snippet Compiler 使得这一过程变得更加容易、更加愉快。

Snippet Compiler 由 Jeff Key 编写,并且可以从 http://www.sliver.com/dotnet/SnippetCompiler 下载。

Regulator

Regulator 是最后一个添加到我的头等工具清单中的。它是一种很有特色的工具,能够使生成和测试正则表达式变得很容易。人们对正则表达式重新产生了兴趣,因为它们在 .NET 框架中受到很好的支持。正则表达式用来基于字符、频率和字符顺序定义字符串中的模式。它们最常见的用途是作为验证用户输入有效性的手段或者作为在较大字符串中查找字符串的方法 — 例如,在 Web 页上查找 URL 或电子邮件地址。

Regulator 使您可以输入一个正则表达式以及一些针对其运行该表达式的输入内容。这样,在应用程序中实现该正则表达式之前,您可以了解它将产生什么效果以及它将返回哪些种类的匹配项。图 2 显示了带有简单正则表达式的 Regulator。

图 2

文档中包含该正则表达式 — 在该示例中,它是 [0-9]*,应该匹配一行中任意数量的数字。右下侧的框中含有针对该正则表达式的输入,而左下侧的框显示了该正则表达式在输入内容中找到的匹配项。在这样的单独应用程序中编写和测试正则表达式,要比尝试在您的应用程序中处理它们容易得多。

Regulator 中的最佳功能之一是能够在 regexlib.com 搜索联机正则表达式库。例如,如果您在搜索框中输入字符串“phone”,您将找到 20 种以上能够匹配各种电话号码的不同的正则表达式,包括用于英国、澳大利亚的表达式以及其他许多电话号码。Regulator 由 Roy Osherove 编写,并且可以在 http://royo.is-a-geek.com/regulator 下载。

CodeSmith

CodeSmith 是一种基于模板的代码生成工具,它使用类似于 ASP.NET 的语法来生成任意类型的代码或文本。与其他许多代码生成工具不同,CodeSmith 不要求您订阅特定的应用程序设计或体系结构。使用 CodeSmith,可以生成包括简单的强类型集合和完整应用程序在内的任何东西。

当您生成应用程序时,您经常需要重复完成某些特定的任务,例如编写数据访问代码或者生成自定义集合。CodeSmith 在这些时候特别有用,因为您可以编写模板自动完成这些任务,从而不仅提高您的工作效率,而且能够自动完成那些最为乏味的任务。CodeSmith 附带了许多模板,包括对应于所有 .NET 集合类型的模板以及用于生成存储过程的模板,但该工具的真正威力在于能够创建自定义模板。为了使您能够入门,我将快速介绍一下如何生成自定义模板。

生成自定义模板

CodeSmith 模板只是一些可以在任意文本编辑器中创建的文本文件。它们的唯一要求是用 .cst 文件扩展名来保存它们。我将要生成的示例模板将接受一个字符串,然后基于该字符串生成一个类。创建模板的第一步是添加模板头,它可声明模板的语言、目标语言以及简要模板说明:

<%@ CodeTemplate Language="C#" TargetLanguage="C#" Description="Car Template" %> 

模板的下一部分是属性声明,在这里可声明将在模板每次运行时指定的属性。就该模板而言,我要使用的唯一属性只是一个字符串,因此属性声明如下所示:

<%@ Property Name="ClassName" Type="String" Category="Context" Description="Class Name" %> 

该属性声明将使 ClassName 属性出现在 CodeSmith 属性窗口中,以便可以在模板运行时指定它。下一步是实际生成模板主体,它非常类似于用 ASP.NET 进行编码。以下是该模板的主体:

Custom Template

public sealed class <%= ClassName %> { private static volatile <%= ClassName %> _instance; private <%= ClassName %>() {} private static readonly object _syncRoot = new object(); public static <%= ClassName %> Value { get { if (_instance == null) { lock(_syncRoot) { if (_instance == null) { _instance = new <%= ClassName %>(); } } } return _instance; } } } 

SingletonClass

public sealed class SingletonClass { private static volatile SingletonClass _instance; private SingletonClass() {} private static readonly object _syncRoot = new object(); public static SingletonClass Value { get { if (_instance == null) { lock(_syncRoot) { if (_instance == null) { _instance = new SingletonClass(); } } } return _instance; } } } 

 

正如您所见,该模板接受字符串输入并使用该类名生成单独的类。在模板主体中,使用与 ASP.NET 中相同的起始和结束标记。在该模板中,我只是插入属性值,但您还可以在这些标记内部使用任意类型的 .NET 代码。在该模板完成之后,您就可以通过双击它或者从 CodeSmith 应用程序中打开它将其加载到 CodeSmith 中。图 4 显示了已经加载到 CodeSmith 中的该模板。

图 4

您可以看到左侧的属性正是我在该模板中声明的属性。如果我输入“SingletonClass”作为类名,并单击 Generate 按钮,则将生成模板代码底部显示的类。

CodeSmith 使用起来相当容易,如果能够正确应用,则可以产生一些令人难以置信的结果。面向代码生成的应用程序中最常见的部分之一是数据访问层。CodeSmith 包括一个名为 SchemaExplorer 的特殊的程序集,可用来从表、存储过程或几乎任何其他 SQL Server? 对象生成模板。

CodeSmith 由 Eric J. Smith 编写,并且可以在 http://www.ericjsmith.net/codesmith 下载。

NUnit

NUnit 是为 .NET 框架生成的开放源代码单元测试框架。NUnit 使您可以用您喜欢的语言编写测试,从而测试应用程序的特定功能。当您首次编写代码时,单元测试是一种测试代码功能的很好方法,它还提供了一种对应用程序进行回归测试的方法。NUnit 应用程序提供了一个用于编写单元测试的框架,以及一个运行这些测试和查看结果的图形界面。

编写 NUnit 测试

作为示例,我将测试 .NET 框架中 Hashtable 类的功能,以确定是否可以添加两个对象并且随后检索这些对象。我的第一步是添加对 NUnit.Framework 程序集的引用,该程序集将赋予我对 NUnit 框架的属性和方法的访问权。接下来,我将创建一个类并用 TestFixture 属性标记它。该属性使 NUnit 可以知道该类包含 NUnit 测试:

using System; using System.Collections; using NUnit.Framework; namespace NUnitExample { [TestFixture] public class HashtableTest { public HashtableTest() { } } } 

下一步,我将创建一个方法并用 [Test] 属性标记它,以便 NUnit 知道该方法是一个测试。然后,我将建立一个 Hashtable 并向其添加两个值,再使用 Assert.AreEqual 方法查看我是否可以检索到与我添加到 Hashtable 的值相同的值,如下面的代码所示:

[Test] public void HashtableAddTest() { Hashtable ht = new Hashtable(); ht.Add("Key1", "Value1"); ht.Add("Key2", "Value2"); Assert.AreEqual("Value1", ht["Key1"], "Wrong object returned!"); Assert.AreEqual("Value2", ht["Key2"], "Wrong object returned!"); } 

这将确认我可以首先向 Hashtable 中添加值并随后检索相应的值 — 这是一个很简单的测试,但能够表现 NUnit 的功能。存在许多测试类型以及各种 Assert 方法,可使用它们来测试代码的每个部分。

要运行该测试,我需要生成项目,在 NUnit 应用程序中打开生成的程序集,然后单击 Run 按钮。图 5 显示了结果。当我看到那个大的绿色条纹时,我有一种兴奋和头晕的感觉,因为它让我知道测试已经通过了。这个简单的示例表明 NUnit 和单元测试是多么方便和强大。由于能够编写可以保存的单元测试,并且每当您更改代码时都可以重新运行该单元测试,您不仅可以更容易地检测到代码中的缺陷,而且最终能够交付更好的应用程序。


5 NUnit

NUnit 是一个开放源代码项目,并且可以从 http://www.nunit.org 下载。还有一个优秀的 NUnit Visual Studio .NET 外接程序,它使您可以直接从 Visual Studio 中运行单元测试。您可以在 http://sourceforge.net/projects/nunitaddin 找到它。有关 NUnit 及其在测试驱动开发中的地位的详细信息,请参阅文章“Test-Driven C#: Improve the Design and Flexibility of Your Project with Extreme Programming Techniques”(MSDN ?Magazine 2004 年 4 月刊)。

FxCop

.NET 框架非常强大,这意味着存在创建优秀应用程序的极大可能,但是也同样存在创建劣质程序的可能。FxCop 是有助于创建更好的应用程序的工具之一,它所采用的方法是:使您能够分析程序集,并使用一些不同的规则来检查它是否符合这些规则。FxCop 随附了由 Microsoft 创建的固定数量的规则,但您也可以创建并包括您自己的规则。例如,如果您决定所有的类都应该具有一个不带任何参数的默认构造函数,则可以编写一条规则,以确保程序集的每个类上都具有一个构造函数。这样,无论是谁编写该代码,您都将获得一定程度的一致性。如果您需要有关创建自定义规则的详细信息,请参阅 John Robbins 的有关该主题的 Bugslayer 专栏文章(MSDN ? Magazine 2004 年 6 月刊)。

那么,让我们观察一下实际运行的 FxCop,并且看一下它在我一直在处理的 NUnitExample 程序集中找到哪些错误。当您打开 FxCop 时,您首先需要创建一个 FxCop 项目,然后向其添加您要测试的程序集。在将该程序集添加到项目以后,就可以按 Analyze,FxCop 将分析该程序集。图 6 中显示了在该程序集中找到的错误和警告。

图 6

FxCop 在我的程序集中找到了几个问题。您可以双击某个错误以查看详细信息,包括规则说明以及在哪里可以找到更多信息。(您可以做的一件有趣的事情是在框架程序集上运行 FxCop 并查看发生了什么事情。)

FxCop 可以帮助您创建更好的、更一致的代码,但它无法补偿低劣的应用程序设计或非常简单拙劣的编程。FxCop 也不能替代对等代码检查,但是因为它可以在进行代码检查之前捕获大量错误,所以您可以花费更多时间来解决严重的问题,而不必担心命名约定。FxCop 由 Microsoft 开发,并且可以从 http://www.gotdotnet.com/team/fxcop 下载。

Lutz Roeder 的 .NET Reflector

下一个必不可少的工具称为 .NET Reflector,它是一个类浏览器和反编译器,可以分析程序集并向您展示它的所有秘密。.NET 框架向全世界引入了可用来分析任何基于 .NET 的代码(无论它是单个类还是完整的程序集)的反射概念。反射还可以用来检索有关特定程序集中包含的各种类、方法和属性的信息。使用 .NET Reflector,您可以浏览程序集的类和方法,可以分析由这些类和方法生成的 Microsoft 中间语言 (MSIL),并且可以反编译这些类和方法并查看 C# 或 Visual Basic ?.NET 中的等价类和方法。

为了演示 .NET Reflector 的工作方式,我将加载和分析前面已经显示的 NUnitExample 程序集。图 7 显示了 .NET Reflector 中加载的该程序集。


7 NUnitExample 程序集

在 .NET Reflector 内部,有各种可用来进一步分析该程序集的工具。要查看构成某个方法的 MSIL,请单击该方法并从菜单中选择 Disassembler。

除了能够查看 MSIL 以外,您还可以通过选择 Tools 菜单下的 Decompiler 来查看该方法的 C# 形式。通过在 Languages 菜单下更改您的选择,您还可以查看该方法被反编译到 Visual Basic .NET 或 Delphi 以后的形式。以下为 .NET Reflector 生成的代码:

public void HashtableAddTest() { Hashtable hashtable1; hashtable1 = new Hashtable(); hashtable1.Add("Key1", "Value1"); hashtable1.Add("Key2", "Value2"); Assert.AreEqual("Value1", hashtable1["Key1"], "Wrong object returned!"); Assert.AreEqual("Value2", hashtable1["Key2"], "Wrong object returned!"); } 

前面的代码看起来非常像我为该方法实际编写的代码。以下为该程序集中的实际代码:

public void HashtableAddTest() { Hashtable ht = new Hashtable(); ht.Add("Key1", "Value1"); ht.Add("Key2", "Value2"); Assert.AreEqual("Value1", ht["Key1"], "Wrong object returned!"); Assert.AreEqual("Value2", ht["Key2"], "Wrong object returned!"); } 

尽管上述代码中存在一些小的差异,但它们在功能上是完全相同的。

虽然该示例是一种显示实际代码与反编译代码之间对比的好方法,但在我看来,它并不代表 .NET Reflector 所具有的最佳用途 — 分析 .NET 框架程序集和方法。.NET 框架提供了许多执行类似操作的不同方法。例如,如果您需要从 XML 中读取一组数据,则存在多种使用 XmlDocument、XPathNavigator 或 XmlReader 完成该工作的不同方法。通过使用 .NET Reflector,您可以查看 Microsoft 在编写数据集的 ReadXml 方法时使用了什么,或者查看他们在从配置文件读取数据时做了哪些工作。.NET Reflector 还是一个了解以下最佳实施策略的优秀方法:创建诸如 HttpHandlers 或配置处理程序之类的对象,因为您可以了解到 Microsoft 工作组实际上是如何在框架中生成这些对象的。

.NET Reflector 由 Lutz Roeder 编写,并且可以从 http://www.aisto.com/roeder/dotnet 下载。

NDoc

编写代码文档资料几乎总是一项令人畏惧的任务。我所说的不是早期设计文档,甚至也不是更为详细的设计文档;我说的是记录类上的各个方法和属性。NDoc 工具能够使用反射来分析程序集,并使用从 C# XML 注释生成的 XML 自动为代码生成文档资料。XML 注释仅适用于 C#,但有一个名为 VBCommenter 的 Visual Studio .NET Power Toy,它能够为 Visual Basic .NET 完成类似的工作。此外,下一版本的 Visual Studio 将为更多语言支持 XML 注释。

使用 NDoc 时,您仍然在编写代码的技术文档,但您是在编写代码的过程中完成了文档编写工作(在 XML 注释中),而这更容易忍受。使用 NDoc 时,第一步是为您的程序集打开 XML 注释生成功能。右键单击该项目并选择 Properties | Configuration Properties | Build,然后在 XML Documentation File 选项中输入用于保存 XML 文件的路径。当该项目生成时,将创建一个 XML 文件,其中包含所有 XML 注释。下面是 NUnit 示例中的一个用 XML 编写了文档的方法:

/// <summary> /// This test adds a number of values to the Hashtable collection /// and then retrieves those values and checks if they match. /// </summary> [Test] public void HashtableAddTest() { //Method Body Here } 

有关该方法的 XML 文档资料将被提取并保存在 XML 文件中,如下所示:

<member name="M:NUnitExample.HashtableTest.HashtableAddTest"> <summary>This test adds a number of values to the Hashtable collection and then retrieves those values and checks if they match.</summary> </member> 

NDoc 使用反射来考察您的程序集,然后读取该文档中的 XML,并且将它们进行匹配。NDoc 使用该数据来创建任意数量的不同文档格式,包括 HTML 帮助文件 (CHM)。在生成 XML 文件以后,下一步是将程序集和 XML 文件加载到 NDoc 中,以便可以对它们进行处理。通过打开 NDoc 并单击 Add 按钮,可以容易地完成该工作。

在将程序集和 XML 文件加载到 NDoc 中并且使用可用的属性范围自定义输出以后,单击 Generate 按钮将启动生成文档资料的过程。使用默认的属性,NDoc 可以生成一些非常吸引人并且实用的 .html 和 .chm 文件,从而以快速有效的方式自动完成原来非常乏味的任务。

NDoc 是一个开放源代码项目,并且可以从 http://ndoc.sourceforge.net 下载。

NAnt

NAnt 是一个基于 .NET 的生成工具,与当前版本的 Visual Studio .NET 不同,它使得为您的项目创建生成过程变得非常容易。当您拥有大量从事单个项目的开发人员时,您不能依赖于从单个用户的座位进行生成。您也不希望必须定期手动生成该项目。您更愿意创建每天晚上运行的自动生成过程。NAnt 使您可以生成解决方案、复制文件、运行 NUnit 测试、发送电子邮件,等等。遗憾的是,NAnt 缺少漂亮的图形界面,但它的确具有可以指定应该在生成过程中完成哪些任务的控制台应用程序和 XML 文件。注意,MSBuild(属于 Visual Studio 2005 的新的生成平台)为每种健壮的生成方案进行了准备,并且由基于 XML 的项目文件以类似的方式驱动。

实际运行的 NAnt

在该示例中,我将为前面创建的 NUnitExample 解决方案创建一个 NAnt 版本文件。首先,我需要创建一个具有 .build 扩展名的 XML 文件,将其放在我的项目的根目录中,然后向该文件的顶部添加一个 XML 声明。我需要添加到该文件的第一个标记是 project 标记:

<?xml version="1.0"?> <project name="NUnit Example" default="build" basedir="."> <description>The NUnit Example Project</description> </project> 

项目标记还用于设置项目名称、默认目标以及基目录。Description 标记用于设置该项目的简短说明。

接下来,我将添加 property 标记,该标记可用于将设置存储到单个位置(随后可以从文件中的任意位置访问该位置)。在该例中,我将创建一个名为 debug 的属性,我可以随后将其设置为 true 或 false,以反映我是否要在调试配置下编译该项目。(最后,这一特定属性并未真正影响如何生成该项目;它只是您设置的一个变量,当您真正确定了如何生成该项目时将读取该变量。)

接下来,我需要创建一个 target 标记。一个项目可以包含多个可在 NAnt 运行时指定的 target。如果未指定 target,则使用默认 target(我在 project 元素中设置的 target)。在该示例中,默认 target 是 build。让我们观察一下 target 元素,它将包含大多数生成信息:

<target name="build" description="compiles the source code"> </target> 

在 target 元素内,我将把 target 的名称设置为 build,并且创建有关该 target 将做哪些工作的说明。我还将创建一个 csc 元素,该元素用于指定应该传递给 csc C# 编译器的数据。让我们看一下该 csc 元素:

<csc target="library" output=".\bin\debug\NUnitExample.dll" debug="${debug}"> <references> <includes name="C:\program files\NUnit V2.1\bin\NUnit.Framework.dll"/> </references> <sources> <includes name="HashtableTest.cs"/> </sources> </csc> 

首先,我必须设置该 csc 元素的 target。在该例中,我将创建一个 .dll 文件,因此我将 target 设置为 library。接下来,我必须设置 csc 元素的 output,它是将要创建 .dll 文件的位置。最后,我需要设置 debug 属性,它确定了是否在调试中编译该项目。因为我在前面创建了一个用于存储该值的属性,所以我可以使用下面的字符串来访问该属性的值:${debug}。Csc 元素还包含一些子元素。我需要创建两个元素:references 元素将告诉 NAnt 需要为该项目引用哪些程序集,sources 元素告诉 NAnt 要在生成过程中包含哪些文件。在该示例中,我引用了 NUnit.Framework.dll 程序集并包含了 HashtableTest.cs 文件。以下代码中显示了完整的生成文件。(您通常还要创建一个干净的 target,用于删除生成的文件,但为了简洁起见,我已经将其省略。)

<?xml version="1.0"?> <project name="NUnit Example" default="build" basedir="."> <description>The NUnit Example Project</description> <property name="debug" value="true"/> <target name="build" description="compiles the source code"> <csc target="library" output=".\bin\debug\NUnitExample.dll" debug="${debug}"> <references> <includes name="C:\program files\NUnit V2.1\bin\NUnit.Framework.dll" /> </references> <sources> <includes name="HashtableTest.cs"/> </sources> </csc> </target> </project> 

要生成该文件,我需要转到我的项目的根目录(生成文件位于此处),然后从该位置执行 nant.exe。如果生成成功,您可以在该应用程序的 bin 目录中找到 .dll 和 .pdb 文件。尽管使用 NAnt 肯定不像在 Visual Studio 中单击 Build 那样简单,但它仍然是一种非常强大的工具,可用于开发按自动计划运行的生成过程。NAnt 还包括一些有用的功能,例如能够运行单元测试或者复制附加文件(这些功能没有受到当前 Visual Studio 生成过程的支持)。

NAnt 是一个开放源代码项目,并且可以从 http://nant.sourceforge.net 下载。

转换工具

我已经将两个独立的工具合在一起放在标题“转换工具”下面。这两个工具都非常简单,但又可能极为有用。第一个工具是 ASP.NET 版本转换器,它可用于转换 ASP.NET(虚拟目录在它下面运行)的版本。第二个工具是 Visual Studio Converter,它可用于将项目文件从 Visual Studio .NET 2002 转换到 Visual Studio .NET 2003。

当 IIS 处理请求时,它会查看正在请求的文件的扩展名,然后基于该 Web 站点或虚拟目录的扩展名映射,将请求委派给 ISAPI 扩展或者自己处理该请求。这正是 ASP.NET 的工作方式;将为所有 ASP.NET 扩展名注册扩展名映射,并将这些扩展名映射导向 aspnet_isapi.dll。这种工作方式是完美无缺的,除非您安装了 ASP.NET 1.1 — 它会将扩展名映射升级到新版本的 aspnet_isapi.dll。当在 ASP.NET 1.0 上生成的应用程序试图用 1.1 版运行时,这会导致错误。要解决该问题,可以将所有扩展名映射重新转换到 1.0 版的 aspnet_isapi.dll,但是由于有 18 种扩展名映射,所以手动完成这一工作将很枯燥。这正是 ASP.NET 版本转换器可以发挥作用的时候。使用这一小型实用工具,可以转换任何单个 ASP.NET 应用程序所使用的 .NET 框架的版本。


9 ASP.NET 版本转换器

图 9 显示了实际运行的 ASP.NET 版本转换器。它的使用方法非常简单,只须选择相应的应用程序,然后选择您希望该应用程序使用的 .NET 框架版本。该工具随后将使用 aspnet_regiis.exe 命令行工具将该应用程序转换到所选版本的框架。随着将来版本的 ASP.NET 和 .NET 框架的发布,该工具将变得更为有用。

ASP.NET 版本转换器由 Denis Bauer 编写,并且可以从 http://www.denisbauer.com/NETTools/ASPNETVersionSwitcher.aspx 下载。

Visual Studio .NET 项目转换器(参见图 10)非常类似于 ASP.NET 版本转换器,区别在于它用于转换 Visual Studio 项目文件的版本。尽管在 .NET 框架的 1.0 版和 1.1 版之间只有很小的差异,但一旦将项目文件从 Visual Studio .NET 2002 转换到 Visual Studio .NET 2003,将无法再把它转换回去。虽然这在大多数时候可能不会成为问题(因为在 .NET 框架 1.0 版和 1.1 版之间几乎没有什么破坏性的更改),但在某些时刻您可能需要将项目转换回去。该转换器可以将任何解决方案或项目文件从 Visual Studio 7.1 (Visual Studio .NET 2003) 转换到 Visual Studio 7.0 (Visual Studio .NET 2002),并在必要时进行反向转换。


10 Visual Studio .NET 项目转换器

Visual Studio .NET 项目转换器由 Dacris Software 编写。该工具可以从 http://www.codeproject.com/macro/vsconvert.asp 下载。

 

小结

本文采用走马观花的方式介绍了上述工具,但我已经试图起码向您提供足够的信息以激起您的好奇心。我相信本文已经让您在某种程度上领悟了几个免费工具,您可以立即开始使用这些工具来编写更好的项目。同时,我还要敦促您确保自己拥有所有其他可以获得的合适工具,无论是最新版本的 Visual Studio、功能强大的计算机还是免费的实用工具。拥有合适的工具将使一切变得大不相同

2004年09月14日

创建型模式

1、FACTORY—追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说“来四个鸡翅”就行了。麦当劳和肯德基就是生产鸡翅的Factory

工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:如何创建及如何向客户端提供。

2、BUILDER—MM最爱听的就是“我爱你”这句话了,见到不同地方的MM,要能够用她们的方言跟她说这句话哦,我有一个多种语言翻译机,上面每种语言都有一个按键,见到MM我只要按对应的键,它就能够用相应的语言说出“我爱你”这句话了,国外的MM也可以轻松搞掂,这就是我的“我爱你”builder。(这一定比美军在伊拉克用的翻译机好卖)

建造模式:将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。建造模式使得产品内部表象可以独立的变化,客户不必知道产品内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。

3、FACTORY METHOD—请MM去麦当劳吃汉堡,不同的MM有不同的口味,要每个都记住是一件烦人的事情,我一般采用Factory Method模式,带着MM到服务员那儿,说“要一个汉堡”,具体要什么样的汉堡呢,让MM直接跟服务员说就行了。

工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

4、PROTOTYPE—跟MM用QQ聊天,一定要说些深情的话语了,我搜集了好多肉麻的情话,需要时只要copy出来放到QQ里面就行了,这就是我的情话prototype了。(100块钱一份,你要不要)

原始模型模式:通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。

5、SINGLETON—俺有6个漂亮的老婆,她们的老公都是我,我就是我们家里的老公Sigleton,她们只要说道“老公”,都是指的同一个人,那就是我(刚才做了个梦啦,哪有这么好的事)

单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的“单一实例”的需求时才可使用。

结构型模式

6、ADAPTER—在朋友聚会上碰到了一个美女Sarah,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友kent了,他作为我和Sarah之间的Adapter,让我和Sarah可以相互交谈了(也不知道他会不会耍我)

适配器(变压器)模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。

7、BRIDGE—早上碰到MM,要说早上好,晚上碰到MM,要说晚上好;碰到MM穿了件新衣服,要说你的衣服好漂亮哦,碰到MM新做的发型,要说你的头发好漂亮哦。不要问我“早上碰到MM新做了个发型怎么说”这种问题,自己用BRIDGE组合一下不就行了

桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以独立的变化。

8、COMPOSITE—Mary今天过生日。“我过生日,你要送我一件礼物。”“嗯,好吧,去商店,你自己挑。”“这件T恤挺漂亮,买,这条裙子好看,买,这个包也不错,买。”“喂,买了三件了呀,我只答应送一件礼物的哦。”“什么呀,T恤加裙子加包包,正好配成一套呀,小姐,麻烦你包起来。”“……”,MM都会用Composite模式了,你会了没有?

合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。

9、DECORATOR—Mary过完轮到Sarly过生日,还是不要叫她自己挑了,不然这个月伙食费肯定玩完,拿出我去年在华山顶上照的照片,在背面写上“最好的的礼物,就是爱你的Fita”,再到街上礼品店买了个像框(卖礼品的MM也很漂亮哦),再找隔壁搞美术设计的Mike设计了一个漂亮的盒子装起来……,我们都是Decorator,最终都在修饰我这个人呀,怎么样,看懂了吗?

装饰模式:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个对象增加功能,这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。

10、FACADE—我有一个专业的Nikon相机,我就喜欢自己手动调光圈、快门,这样照出来的照片才专业,但MM可不懂这些,教了半天也不会。幸好相机有Facade设计模式,把相机调整到自动档,只要对准目标按快门就行了,一切由相机自动调整,这样MM也可以用这个相机给我拍张照片了。

    门面模式:外部与一个子系统的通信必须通过一个统一的门面对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。每一个子系统只有一个门面类,而且此门面类只有一个实例,也就是说它是一个单例模式。但整个系统可以有多个门面类。

11、FLYWEIGHT—每天跟MM发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来,在前面加上MM的名字就可以发送了,再不用一个字一个字敲了。共享的句子就是Flyweight,MM的名字就是提取出来的外部特征,根据上下文情况使用。

    享元模式:FLYWEIGHT在拳击比赛中指最轻量级。享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态,它们是相互独立的。将可以共享的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里剔除出去。客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的数量。

12、PROXY—跟MM在网上聊天,一开头总是“hi,你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?”这些话,真烦人,写个程序做为我的Proxy吧,凡是接收到这些话都设置好了自动的回答,接收到其他的话时再通知我回答,怎么样,酷吧。

    代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。

行为模式

13、CHAIN OF RESPONSIBLEITY—晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的MM哎,找张纸条,写上“Hi,可以做我的女朋友吗?如果不愿意请向前传”,纸条就一个接一个的传上去了,糟糕,传到第一排的MM把纸条传给老师了,听说是个老处女呀,快跑!

   责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接

    起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下动态的重新组织链和分配责任。处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。

14、COMMAND—俺有一个MM家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。这不,她弟弟又传送过来一个COMMAND,为了感谢他,我请他吃了碗杂酱面,哪知道他说:“我同时给我姐姐三个男朋友送COMMAND,就数你最小气,才请我吃面。”,:-(

    命令模式:命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令的撤消。

15、INTERPRETER—俺有一个《泡MM真经》,上面有各种泡MM的攻略,比如说去吃西餐的步骤、去看电影的方法等等,跟MM约会时,只要做一个Interpreter,照着上面的脚本执行就可以了。

    解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。解释器模式将描述怎样在有了一个简单的文法后,使用模式设计解释这些语句。在解释器模式里面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模式中需要定义一个代表文法的命令类的等级结构,也就是一系列的组合规则。每一个命令对象都有一个解释方法,代表对命令对象的解释。命令对象的等级结构中的对象的任何排列组合都是一个语言。

 

16、ITERATOR—我爱上了Mary,不顾一切的向她求婚。

          Mary:“想要我跟你结婚,得答应我的条件”

          我:“什么条件我都答应,你说吧”

          Mary:“我看上了那个一克拉的钻石”

          我:“我买,我买,还有吗?”

          Mary:“我看上了湖边的那栋别墅”

          我:“我买,我买,还有吗?”

          Mary:“你的小弟弟必须要有50cm长”

          我脑袋嗡的一声,坐在椅子上,一咬牙:“我剪,我剪,还有吗?”

          ……

    迭代子模式:迭代子模式可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。迭代子模式简化了聚集的界面。每一个聚集对象都可以有一个或一个以上的迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。迭代算法可以独立于聚集角色变化。

17、MEDIATOR—四个MM打麻将,相互之间谁应该给谁多少钱算不清楚了,幸亏当时我在旁边,按照各自的筹码数算钱,赚了钱的从我这里拿,赔了钱的也付给我,一切就OK啦,俺得到了四个MM的电话。

    调停者模式:调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使他们可以松散偶合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。调停者模式将多对多的相互作用转化为一对多的相互作用。调停者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。

18、MEMENTO—同时跟几个MM聊天时,一定要记清楚刚才跟MM说了些什么话,不然MM发现了会不高兴的哦,幸亏我有个备忘录,刚才与哪个MM说了什么话我都拷贝一份放到备忘录里面保存,这样可以随时察看以前的记录啦。

    备忘录模式:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。

19、OBSERVER—想知道咱们公司最新MM情报吗?加入公司的MM情报邮件组就行了,tom负责搜集情报,他发现的新情报不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦

    观察者模式:观察者模式定义了一种一队多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。

20、STATE—跟MM交往时,一定要注意她的状态哦,在不同的状态时她的行为会有不同,比如你约她今天晚上去看电影,对你没兴趣的MM就会说“有事情啦”,对你不讨厌但还没喜欢上的MM就会说“好啊,不过可以带上我同事么?”,已经喜欢上你的MM就会说“几点钟?看完电影再去泡吧怎么样?”,当然你看电影过程中表现良好的话,也可以把MM的状态从不讨厌不喜欢变成喜欢哦。

    状态模式:状态模式允许一个对象在其内部状态改变的时候改变行为。这个对象看上去象是改变了它的类一样。状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。

21、STRATEGY—跟不同类型的MM约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到MM的芳心,我的追MM锦囊中有好多Strategy哦。

    策略模式:策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模式把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。

22、TEMPLATE METHOD——看过《如何说服女生上床》这部经典文章吗?女生从认识到上床的不变的步骤分为巧遇、打破僵局、展开追求、接吻、前戏、动手、爱抚、进去八大步骤(Template method),但每个步骤针对不同的情况,都有不一样的做法,这就要看你随机应变啦(具体实现);

    模板方法模式:模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。

23、VISITOR—情人节到了,要给每个MM送一束鲜花和一张卡片,可是每个MM送的花都要针对她个人的特点,每张卡片也要根据个人的特点来挑,我一个人哪搞得清楚,还是找花店老板和礼品店老板做一下Visitor,让花店老板根据MM的特点选一束花,让礼品店老板也根据每个人特点选一张卡,这样就轻松多了;

    访问者模式:访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。访问者模式使得增加新的操作变的很容易,就是增加一个新的访问者类。访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。当使用访问者模式时,要将尽可能多的对象浏览逻辑放在访问者类中,而不是放到它的子类中。访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。

2004年08月13日

Asp实现简单MVC() ———— ASP模板的实现

近来设计模式风行,MVC随处可见,MVC作为一种成功的模式被广泛的应用于基于JAVAWeb应用这种, PHP也通过模拟的方法实现基本的MVC,由于PHP是解释性语言,不能像Java Bean一样长驻内存,PHP页面开始执行时创建,在页面解释执行结束时就已经结束。在这样的情况下,我们根本无法直接实现M,所以只能通过实现的方式一般是在当前页面结束前把数据存入数据库或者cookie/session,在下一个页面中再透过数据库或者cookie/session重建MPHPV是通过模板技术来实现的,其实就是分离了HTMLPHP的代码,并不是MVC所指的显示逻辑和业务逻辑。从上面看来 PHP所模拟的MVC还不是一个完美,甚至可以说是非常不完善的MVC,但是他体现了MVC的基本思想。

做为同为解释性语言的ASP在这方面的做的就更不尽人意了,首先要把ASP代码和HTML代码 分离已经是一件很不容易的事了,我参考了PHPLIB的实现方式,PHPLIB是通过一个Template的类来实现HTML和代码分离。参看了PHP Template的代码后,发现其中也没有特殊之处,只不过是把HTML载入,然后把其中定义的标签剥离出来,用数据替换,最后一起输出客户端形成页面,我想到,既然PHP可以实现,ASP没有理由不能实现,我采取了一个最笨的办法,就是用ASP的语言 照着PHPLIB Template重写一个ASP的类,刚开始的时候碰到了一点问题就是PHP LIST() 函数,这个可以说是实现Template功有的关建之所在,VBScript并没有提供相应函数,但是VBScript 提供了一个Dictionary对象,基本等价于PHPLIST()。好的问题解决了,因为是改写PHP的代码所以也没有什么心得可以谈的,下面是我用ASP重写的Template(Template.inc)

<script language=”VBScript” runat=”server”>

Class Template

Private FSO,re

public last_error,halt_on_error,debug

Private m_root,unKnowns,m_nfile

Private varkeys,varvals

 

Private Sub class_Initialize

       Set FSO=CreateObject(“Scripting.FileSystemObject”)

       Set re = new regexp : re.IgnoreCase = true : re.Global = true

       Set m_nfile=CreateObject(“Scripting.Dictionary”)

       Set varkeys=CreateObject(“Scripting.Dictionary”)

       Set varvals=CreateObject(“Scripting.Dictionary”)

       m_root=”templates/”

       unknowns=”keep”

       debug=”false”

       halt_on_error=”yes”

End Sub

 

Public Sub Set_Root(Byval root)

       If FSO.FolderExists(Server.MapPath(root)) Then

              m_root=replace(root,”\”,”/”)

              If m_root<>”" and not Right(m_root,1)=”/” Then

                     m_root=m_root & “/”

                     If debug Then response.write “Set_Root:Root is “&m_root&”.<br>”

              Else

                     Call halt(“模版目录名有错.”)

                     Exit Sub

              End If

       Else

              Call halt(“找不到模版目录.”)

              Exit Sub

       End If

End Sub

 

Public Property Get Root

       Root=m_root

End Property

 

Public Sub set_unknowns(ByVal A_strUnknowns)

      Unknowns = A_strUnknowns

End Sub

 

Public Sub set_file(handle,str_filename)

Dim str_tmp

If NOT IsArray(handle) Then

       If str_filename=”" Then

              Call halt(“set_file: For handle”& handle &”filename is empty.<br>”)

              Exit Sub

       End If

       str_tmp=filename(str_filename)

       If debug Then response.write “set_file:set ” & handle & “=” & Str_tmp & “.<br>”

       m_nfile.add handle,str_tmp

Else

       Call setFiles(handle)

End If

End Sub

 

Public Sub setfiles(s_handle)

Dim num,i

       If IsArray(s_handle) Then

              num=UBOUND(s_handle)

              If((num+1) mod 2)<>0 Then

                     Call halt(“set_file:For handle’s array have some error”)

                     Exit Sub

              Else

                     For i=0 to num step 2

                            Call Set_file(s_handle(i),s_handle(i+1))

                     Next

              End If

       End If

End Sub

 

Public Function get_undefined(handle)

Dim Str,i,result

If Not LoadFile(handle) Then

       Call halt(“get_undefined: unable to load “& handle&”.<br>”)

       get_undefined=false

       Exit Function

End If

       Str=get_var(handle)

       re.Pattern = “({)([^ \t\r\n}]+)}”

       Set matches=re.execute(Str)

       i=0

       For each match in matches

              If not varvals.exists(match.submatches(1)) Then

                     result(i)=match.submatches(1)

                     i=i+1

              End If

       Next

       If IsArray(result) Then

              get_undefined=result

       Else

              get_undefined=false

       End If

End Function

 

 

Public Sub set_block(parent,handle,str_Name)

Dim Str,matches,matche,str_matche

If Not LoadFile(parent) Then

       Call halt(“subst: unable to load”& parent &”.”)

       Exit Sub

End If

If str_name=”" Then str_name=handle

Str=Get_var(parent)

re.Pattern = “<!” & “–\s+BEGIN\s+(” & handle & “)\s+–>([\s\S.]*)<!” & “–\s+END\s+” & handle & “\s+–>”

Set Matches = re.Execute(Str)

For each matche In matches

       ‘response.write matche.value &”dd”

       str_matche=matche.submatches(1)

       Str=re.replace(Str,”{” & str_name & “}”)

       Call Set_Var(handle,str_Matche)

       Call Set_Var(parent,Str)

Next

End Sub

 

Public Sub Set_var(byval str_varname,byval value)

Dim Str

If Not IsArray(str_varname) Then

       If str_varname<>”" Then

              If debug Then response.write “scalar: set “& str_varname &” to “& value &”<br>”&vbCRLF

       End If

              Str=varname(str_varname)

              If varkeys.Exists(str_varname) Then

                     varkeys.remove(str_varname)

                     varkeys.add str_varname,”/”& Str &”/”

              Else

                     varkeys.add str_varname,”/”& Str &”/”

              End If

              If debug Then response.write “set_var:varkeys.add “&str_varname&”,”&str&”.<br>”

              If varvals.Exists(str_varname) Then

                     varvals.remove(str_varname)

                     varvals.add str_varname,value

              Else

                     varvals.add str_varname,value

              End If

Else

       Call Setvars(str_varname)

End If

End Sub

 

Public Sub SetVars(ByVal A_varname)

  Dim i, num

   If IsArray(A_varname) Then

    num = Ubound(A_varname)

    if ((num +1) mod 2) <> 0 Then

     Call halt(“SetVars: For varname array’s element not gemination.<br>”)

     Exit Sub

    Else

     For i = 0 To num Step 2

      Call Set_Var(A_varname(i), A_varname(i+1))

     Next

    End If

   Else

    Call Set_Var(A_varname, “”)

   End If

End Sub

 

Public Function subst(handle)

Dim Str

       If not LoadFile(handle) Then

              Call halt(“subst:unableto load “& handle &”.<br>”)

              subst=false

       End If

       Str=get_var(handle)

       re.Pattern=”({)([^\t\r\n}]+)}”

       Set matches=re.execute(Str)

       For each match in matches

              If varvals.exists(match.submatches(1)) Then

                     re.pattern=match.value

                     Str=re.replace(Str,varvals.item(match.submatches(1)))

              End If

       Next

       subst=Str

End Function

Public Sub psubst(handle)

       response.write  subst(handle)

End Sub

 

Public Function parse(target,handle,Append)

Dim Str

       If NOT IsArray(handle) Then

              Str=subst(handle)

              If(Append) Then

                     Call set_var(target,Get_var(target)&Str)

              Else

                     Call set_var(target,Str)

              End If

       Else

              For i=0 to UBound(handle)

                     Str=subst(handle(i))

                     If(Append) Then

                            Call set_var(target,Get_var(target)&Str)

                     Else

                            Call set_var(target,Str)

                     End If

              Next

       End If    

       parse=Str

End Function

Public Function pparse(target,handle,Append)

       response.write parse(target,handle,Append)

End Function

 

Public Function p(varname)

       response.write finish(get_var(varname))

End Function

 

Public Function vGet(varname)

       vGet=finish(get_var(varname))

End Function

 

Public Function finish(Str)

Dim str_trmp

       Select Case unknowns

       Case “keep”

              str_tmp=Str

       Case “remove”

              re.Pattern = “({)([^ \t\r\n}]+)}”

              str_tmp=re.replace(Str,”")

       Case “comment”

              re.Pattern = “({)([^ \t\r\n}]+)}”

              Set mathces=re.execute(Str)

              For each match in mathces

                     str_tmp=Replace(Str, “<!”&”– Template variable ” & Match.SubMatches(1) &” undefined –>”)

              Next

       End Select

       finish=str_tmp

             

      

End Function

 

Private Function varname(Str)

       varname=”{” & Str & “}”

End Function

 

Public Function get_vars()

       Set get_vars=varvals

End Function

 

 

Public Function Get_Var(varname)

Dim tmp,Count,Str

       If NOT IsArray(varname) Then

              If varvals.Exists(varname) Then

                     get_var=varvals.Item(varname)

              End If

       Else

              Set tmp=CreateObject(“scripting.Dictionary”)

              Count=UBound(varname)

              For i=0 to Count

                     If varvals.Exists(varname(i)) Then

                            Str=varvals.Item(varname(i))

                            tmp.add varname(i),Str

                            If debug Then response.write “Get_var: set “& varname(i) &” to “& Str &”<br>”&vbCRLF

                     End If

              Next      

              Set getvar=tmp

       End If

End Function

 

 

 

Private Function LoadFile(handle)

Dim Str_filename,str_file,Str

       If varkeys.Exists(handle) and varkeys.Item(handle)<>”" Then

              LoadFile=true

              Exit Function

       End If

       If Not m_nfile.Exists(handle) then

              call halt(“loadfile:”& handle &” is not a valid handle.<br>”)

              LoadFile=False

              Exit Function

       End If

       str_filename=m_nfile.Item(handle)

       str_filename=Server.Mappath(str_filename)

       If debug Then response.write str_filename &”<br>”

       If Not FSO.FileExists(str_filename) Then

              Call halt(“loadfile:”& str_filename &” is not a valid file.<br>”)

              LoadFile=false

              Exit Function

       End If

              Set str_file=FSO.OpenTextFile(Str_filename)

              Str=str_file.readall()

              str_file.close

              Set str_file=nothing

              If Str=”" Then

                     Call halt(“loadfile: While loading”& handle &”,”& str_filename &”does not exist or is empty.<br>”)

                     LoadFile=false

                     Exit Function

              End If

              Call set_var(handle,Str)

              LoadFile=true

End Function

      

Private Function FileName(str_filename)

       FileName=m_root & str_filename

End Function

 

Public Sub Halt(msg)

       last_error=msg

       If halt_on_error<>”no” Then Call haltmsg(msg)

       If halt_on_error=”yes” Then

              response.write “<b>Halted.</b>”

              response.end

       End If

End Sub

 

Public Sub haltmsg(msg)

       Response.Write “<b>Template Error:</b>” & msg & “<br>”

End Sub

Public Sub viewcache()

response.write “varkeys:<br>”

a=varkeys.keys

       For d=0 to varkeys.count-1

              response.write “key:”&a(d) &” value:”& varkeys.item(a(d))&”<br>”

       Next

response.write “varvals:<br>”

a=varvals.keys

       For d=0 to varvals.count-1

              response.write “key:”&a(d) &” value:”& varvals.item(a(d))&”<br>”

       Next

End Sub        

Private Sub class_Terminate

  Set FSO = Nothing

  Set m_nfile=nothing

  Set VarKeys = Nothing

  Set VarVals = Nothing

  Set re = Nothing

End Sub        

 

End Class

</script>

下面测试一下:

新建一个模板

<html>

<head>

  <meta http-equiv=”Content-Language” content=”zh-tw”>

  <meta http-equiv=”Content-Type” content=”text/html; charset=gb2312″>

  <meta name=”GENERATOR” content=”Microsoft FrontPage 4.0″>

  <meta name=”ProgId” content=”FrontPage.Editor.Document”>

  <title>姓名</title>

</head>

<body>

<table border=”0″ width=”100%”>

  <!– BEGIN row –> //定义重复的块

    <tr><td width=”100%” colspan=”2″><hr></td></tr>

    <tr><td width=”16%”><font color=”#0000FF”>姓名</font></td>

      <td width=”84%”><font color=”#0000FF”>{NAME}</font></td>

    </tr><tr>

      <td width=”16%”>邮件</td>

      <td width=”84%”>{EMAIL}</td>

    </tr><tr>

      <td width=”100%” colspan=”2″><font color=”#008000″>留言: {MESSAGE}</font></td>

    </tr>

  <!– END row –> //结束

</table>

</body></html>

原有PHP代码:


<?  $db = new DB_mytest;                     //
建立 DB Class 

$template = new Template(“./”,”keep”);   // 建立 Template 

$template->set_file(“main”,”test.html”); // 设定 test.html main 

$db->query(“SELECT * FROM test”);        // SQL  $template->set_block(“main”,”row”,”rows”);  

while($db->next_record())                //指针移动至下一笔  {                                       

                                   // 设定 {XXX} 内的变量将其取代   

$template->set_var(   

       array(        

        “NAME” => $db->f(“name”),         

        “EMAIL” => $db->f(“email”),         

        “MESSAGE” => $db->f(“message”)        

        )                     

);   

$template->parse(“rows”,”row”,true);   // row 的资料增加到 rows  }//

if(!$db->nf()) $template->set_var(“rows”,”没有资料“);

$template->parse(“out”, “main”); // main 解析后存入

out$template->p(“out”); // out 印出

?>

ASP来实现:

<!–#include file=”template.asp”–>

       <!–#include file=”conn.asp” à

<%

dim templates

set templates=new template // 建立 Template

sql=”select * from test”

set rs=conn.execute(sql)

temparray=rs.getrows(-1)//取所有记录 存放到temparray

templates.set_root(“dd”) //设定Template目录

templates.set_file “main”,”test.htm”// 设定 test.html main

templates.set_block “main”,”row”,”rows”

for i=1 to ubound(temparray,2)

templates.set_var array(_

       “name”,temparray(0,i),_

       “Email”,temparray(1,i) _

       ),”"

templates.parse “rows“,”row” true

next

templates.parse “out”,”main”

templates.p(“out”)

%>

IIS中运行了一下,地切OK,完美运于,有兴趣的朋友 可以测试一下PHPLIB Template的其它应用,基本上也能实现。

       第一节就写到这里了,由于水平有限对MVC的理解还不够深入,出错在所难免,希望大家能批评指正 ,还有还请光临,我用这个模式实习的第一个实验性站点“贝特系统

(http://www.001offfice.com/better)

                                                                                                                       平波

                                                                                                                2004813

2004年08月04日

我的分页类发布了以后,得到很多朋友的支持,许多朋友也提出了不少意见,比如我的分页类,如果分的页多了的话,分页条会从1一直显示到 N,N一大的话,就影响了页面的美观,所以,重写了

Private Function showNumBtn()

先在头部加入
Const BtnNum_Count=10     ’定义最多显示多少页分页 
Const Btn_more=”…”     ’定义更多按钮

用以下代码替换 原有代码
‘====================================================================
‘ShowNumBtn  数字导航


‘====================================================================
Private Function showNumBtn()
 Dim i,str_tmp
 Dim start_p,end_p
 Dim Str_start,str_end
 If int_totalPage<btnnum_count Then
  For i=1 to int_totalpage
   str_tmp=str_tmp & “<a href=”& geturl & i &”>["&i&"]</a>&nbsp;”
  Next
 Else
  If int_curpage<=BtnNum_Count Then
   start_p=1
   end_p=btnnum_count
   str_start=”"
   str_end=”<a href=” & GetUrl & end_p+1 &”>”&Btn_more&”</a>&nbsp;”
  ElseIf int_curpage>(int_totalpage\BtnNum_Count)*BtnNum_Count Then
   start_p=(int_totalpage\BtnNum_Count)*BtnNum_Count+1
   end_p=int_totalpage
   str_start=”<a href=” & GetUrl & start_p-1 &”>”&Btn_more&”</a>&nbsp;”
   str_end=”"
  Else
   if (int_curpage mod btnnum_count)=0 then
   start_p=int_curpage-btnnum_count+1
   End_p=int_curpage
   else
   start_p=(int_curpage\btnnum_count)*btnnum_count+1
   End_p=(int_curpage\btnnum_count)*btnnum_count+btnnum_count
   end if
   str_start=”<a href=” & GetUrl & start_p-1 &”>”&Btn_more&”</a>&nbsp;”
   str_end=”<a href=” & GetUrl & end_P+1 &”>”&Btn_more&”</a>&nbsp;”
  End If
  For i=start_p to End_p
   str_tmp=str_tmp & “<a href=”& geturl & i &”>["&i&"]</a>&nbsp;”
  Next
  str_tmp=str_start & str_tmp & str_end
 End If

 showNumBtn=str_tmp
 
End Function

2004年07月13日

Access突然坏了,可以建表,建了后 就打不开了,只要双击表就出现“未知”

其他机子上好的数据库也打不开。

连好的 asp程序(access数据库的)都运行不了,都出现下列错误:

错误类型:
Microsoft JET Database Engine (0×80040E14)
未知
修复了一次Office 还是同样的问题,卸掉重装还是老问题,甚至换了一个更高的版本(office 2003)也还是老问题,
尝试装jet 4.0 sp8 不行!
装mdac_typ.exe (2.8) 不行!!
重打windows 2000 sp4 不行!!
网上搜了一整天 毫无结果,论坛上也问了很多次,也没有结果,真的快要绝望了,
准备重做系统前,做了最后一博,从ASP的错误信息来看,应该是jet出了问题
我已经重装了jet 4.0了,为什么还是没有效果呢,尝试性的再用
RegSvr32.Exe    C:\Windows\System32\msjtes40.dll
试了一下,没想到 还真的好了 ,怪哉!!!!
我早上的时候 也这么试过啊 ,为什么不行呢????

2004年06月17日

下午碰到了这个问题,头大了一下,由于对.Net的控件还是很熟悉,所以一开始碰到这个问题
基本上让我无从下手,只好翻开MSDN ,但是不知道是由于我的查找方法不对还是自己有rpwt
始终找不到相应的内容,只好回想以前做VB,Delphi程序的时候怎么做了,乖乖,想到这些,
Windows 消息机制就想起来了,退步好快啊, 为什么不向ListView发送一条LVM_SCROLL消息
呢,这个方法应该能实现,但是实现起来好像有点麻烦,.Net的ListView到底能不能用一个简单的
方法来实现呢,只好求助网络了,搜了半天在中文GOOGLE中也找不到相关资料,郁闷,最后只能
使用英文 GOOGLE了,(我的英文水平不太行),我尝似着输入 “.Net ListView auto scroll to the end of the list” 搜到几位老兄和我一样的问题,翻了好多地方,终于找到个地方,有答复的,但是好事多磨,这位老兄不知道是哪个国家的,回答的内容全都是乱码,唯一一句看得懂的就是Item.EnsureVisible()了,看来关键在这里了,回到msdn 中一查

ListView.EnsureVisible 方法  [C#]

请参见

ListView 类 | ListView 成员 | System.Windows.Forms 命名空间 | TopItem | ListView 成员(Visual J# 语法) 

要求

平台: Windows 98, Windows NT 4.0, Windows ME, Windows 2000, Windows XP Home Edition, Windows XP Professional, Windows Server 2003 系列, .NET Framework 精简版 - Windows CE .NET

语言

C#

C++

JScript

Visual Basic

全部显示

确保指定项在控件中是可见的,必要时滚动控件的内容。

[Visual Basic]

Public Sub EnsureVisible( _

   ByVal index As Integer _

)

[C#]

public void EnsureVisible(

   int index

);

[C++]

public: void EnsureVisible(

   int index

);

[JScript]

public function EnsureVisible(

   index : int

);

参数

index

要滚动至视图中的项的从零开始的索引。

备注

您可以使用该方法确保指定的项在 ListView 控件中是可见的。对 ListView 中的项执行验证时,您可以调用 EnsureVisible 方法来确保 ListView 控件中显示未通过验证的项,以便用户能够对该项执行更改。如果要确保其可见的项位于 ListView 控件可视区域的上方,调用 EnsureVisible 方法将会滚动控件的内容,直至该项成为控件可视区域的第一项。如果该项位于 ListView 控件可视区域的下方,调用 EnsureVisible 方法将会滚动 ListView 控件的内容,直至该项成为控件可视区域中的最后一项。

要确定项是否位于 ListView 控件显示区域的顶部,请使用 TopItem 属性。

 

靠这不是我要找到吗,只要找到最后一项的index不就可以了吗

马上打开vs.net 加上了这句

ListView1.Items[ListView1.Items.Count-1].EnsureVisible()

运行 通过。

2004年06月10日

以前学过一点C#,好久没有用了,没有想到 ,还真的忘了不少的东西,(难道真的老了??),为了写一个列出指定目录下的,所有指定扩展名(多个扩展名)的文件的小函数 竟然花了我两个小时,要用到的函数都要去翻一遍MSDN,看来真的是三天不练 手就生啊, 写了半天,写出来的东西自己还是不怎么满意,还望大家批评指正,源码如下…


  private Array GetAllFiles(string strpath)
  {
   DirectoryInfo di=new DirectoryInfo(strpath);
   FileInfo[] result=di.GetFiles();
   string strExtFilter=”.jpg,.bmp,.gif”;//需要列出文件的扩展名
   ArrayList AL=new ArrayList(result);
   int num=AL.Count;
   int count=0;
   for(int i=0;i   {
    FileInfo sf=(FileInfo)AL[count];
    if((strExtFilter.IndexOf(sf.Extension.ToLower())==-1) || (sf.Extension==”"))//当前文件的扩展名存在在过滤列表中
    {
     AL.Remove(sf);//如果不存在就删去此项
     count–;
    }
    count++;
   }
   
   return AL.ToArray();
  }

2004年06月05日

昨天又去理发了,理了个酷头,注意,此“酷”非彼“裤” 。时间过的还真TMD快啊, 理完回到家,从电脑上翻出了刚开始工作哪年,也就是2004年,对就是去年4月份(全都是废话)拍的照片,左看右看,还行,理发师的水平 还是没有变,理的也蛮帅的,但是总感觉照片上的我和现在的我有所不同,不同在哪里,我也说不出来,难道 我变老了??怪哉 怪哉