microgao - by - 06 三月, 2007 14:56

http://www.xio.name/XiorkFlow/


microgao - by - 26 一月, 2007 16:34

MFC有几个未公开的函数很有用,AfxExtractSubString()就是其中之一。你在MSDN文档里是找不到关于这个函数的说明的,但是你如果研究过MSDN里的例子程序的话,就会发现很多代码中都使用了这个函数。它的功能简单说来就是从某个用NULL或者换行符"n"分割的字符串中吸取子串。 CString str = "123,456,789"; CString output = ""; for (int i=0; i<3>


microgao - by - 03 十一月, 2006 14:47



microgao - by - 06 九月, 2006 14:05

1.UI层,FLASH件:

var param1:String;
var URLpreFix:String;
//设置服务器URL文件,根据实际自定义
URLpreFix = "
http://localhost:5689/flashtext/WebForm1.aspx";
bt.onRelease = function() {
//LoadVars 类是 loadVariables() 函数的替代方法,用于在 Flash 应用程序和服务器之间传输变量。
var myLoadVars = new LoadVars();
_root.onEnterFrame = function() {
if (_root.yuan.hitTest(_root.juxing)) {
txt = "<workflow></workflow>";
}
else
{
txt = "";
}
}
//txt是场景里面的一个文本变量
//把txt变量的值传给LoadVars类对象
myLoadVars.txt = txt;
//发送给服务器,可使用HTTP 协议的 GET 或 POST 方法。
myLoadVars.sendAndLoad(URLpreFix, myLoadVars, "post");
_root.lb.text="正在加载数据...";
//得到服务器是否成功返回数据,success是成功标志,成功时返回true
myLoadVars.onLoad = function(success) {
//如果成功把lb的文本换成服务器返回的变量值
if (success) {
_root.lb.text = this.txt;
} else {
_root.lb.textt = "异常错误!";
}
};
};

2.DLL层,ASPX文件:新建一“asp.net web 应用程序”,位置为http://localhost/flashtext/,然后把转到HTML设计介面,把HTML代码删除掉,只保存以下这行代码(ASP.NET是采用codebehind设计,默认VS会自动产生一些HTML代码),

3.DLL层,ASPX.cs文件:点开WebForm1.aspx.cs设计类文件,输入代码,用于Request FLASH送过来的数据而提交商业逻辑层处理,完整如下:

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="flashtext.WebForm1" %>


这个很重要,因为如果没有删除,等一下运行,服务器会返回这些HTML代码给UI层,使之产生错误。

using System;using System.Collections;using System.ComponentModel;using System.Data;using System.Drawing;using System.Web;using System.Web.SessionState;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.HtmlControls;
namespace flashtext{
/// <summary>
/// WebForm1 的摘要说明。
/// </summary>

public class WebForm1 : System.Web.UI.Page
{
private void Page_Load(object sender, System.EventArgs e)
{
// 在此处放置用户代码以初始化页面
string txt=Request["txt"];
testClass test
=new testClass();

Response.Write(
"&&txt=" + test.getData(txt));
}


Web 窗体设计器生成的代码#region Web 窗体设计器生成的代码
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: 该调用是 ASP.NET Web 窗体设计器所必需的。
// InitializeComponent();
base.OnInit(e);
}


/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>

private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);

}

#endregion

}

}


4.DLL层,运行商业逻辑:新建一个新类,名为testClass.cs,用于整个demo简单的商业逻辑处理(在此你可以完全去处理与数据库相关处理等等),只返回一行数据,代码如下:

using System;
namespace flashtext{
/// <summary>
/// testClass 的摘要说明。
/// </summary>

public class testClass
{
public testClass()
{
//
// TODO: 在此处添加构造函数逻辑
// }

//方法getData()可以取得数据 public String getData(string param1)
{
//假设这里去读数据了
return "这是来自aspx返回的数据:"+param1;
}

}

}



microgao - by - 12 七月, 2006 11:28

.NET下写程序来实现邮件发送,还真是个不大不小的问题,上网找了一下,好象经常用的有5大方法, (1):调用OUTLOOK组件,调用的是OFFICE里面的OUTLOOK组件.对组件的依赖性比较高. (2):调用.NET自带的System.Web.Mail类,发送的附件比较大时,会有点问题. (3):使用System.Net.Sockets,利用Sockets进行网络编程开发,代码量较大. (4):IIS SMTP自带的基本的发信组件CDONTS,这个用的人比较少. (5):最后说说,调用Jmail组件,现在本人的所做的项目中也是用它来发送的,点击这里看原码!


microgao - by - 11 七月, 2006 10:51

1.经过分析,工作流的流程定制可以在页面中完成,但是这就不能采用集成在WWF中的Activity控件,但是可以采用其他的形式来向服务器端传输流程定制信息,比如flash,目前flash已经开发出了针对.Net的插件,提供了一些接口。这样就可以在表现层采用flash做流程定制的表象。

2.我尝试采用经典的MVC模式进行asp.Net2的开发,因为它的结构清晰,易于捆绑WWF,但是由于我之前没有接触过经典的MVC模式(尤其是.net),所以研究基于.netMVC也会花费一些时间.

3.对于采用flash形式来向服务器端传输流程定制信息,我还有一个问题,那就是WWF中的Activity控件对于程序员来说可以理解,但是用户理解起来就比较费劲,所以还要考虑如何将WWF中的Activity控件转化或组合成用户易于理解的Activity



microgao - by - 11 七月, 2006 10:23

简介

  通过使用WWF,你可以创建基于处理器流的工作流并且把它们部署在任何类型的.NET应用程序中。此外,本文还讨论了ASP.NET开发者面对的一些特有的问题-这些问题可能通过使用工作流得到解决,如维持状态和页面导航等。

  在2005年9月,微软在它的一年两次的专业开发者会议上公开了Windows Workflow Foundation(WWF,Windows工作流基础)。作为WinFX API的支柱之一,WWF提供给开发者一个普通框架-在其上开发过程驱动的和以工作流为中心的应用程序。

  当前,有些组织力图把整个商业过程自动化;他们的标准答案就是集合一队开发者来开发相应的代码。尽管这种方式对于这些组织带来良好的作用,然而也有一些固有的问题。为了深入理解这一问题,你需要理解一个工作流的基本特征。

  一个工作流本质是一种方法-用来归档包含在完成一个单元的工作中的活动。典型地,在处理过程中,工作"流"流过一项或更多活动。这些活动可以通过机器或人工来实现,并且有可能象在一个互联网应用程序定义页面顺序一样得简单,也有可能象管理必须为任何数目的人都要看到、更改并同意的文件或产品一样得复杂。

  因为如此多的工作流必须考虑到人工参预,所以可能需要花费很长工期才能完成,时间可能为几小时到数月或更长。例如,参预在该过程中的人可能无法找到,不在本地或忙于另外的任务;因此,工作流必须在所有非活动期间能够把自身持续性存储。而且,通过编码独立实现的过程可能对非技术人员难于理解而对开发者却难于更改。这一点和其它一些因素正是例如WindowsWF等通用工作流框架的目标-其目的就在于使创建、改变和管理工作流更容易-这是通过向它们提供一个可视化接口或通过定义一组普通API来实现的。

  你可以把WWF工作流放置在任何类型的.NET应用程序中-包括Windows表单程序,控制台应用程序,Windows服务和ASP.NET Web应用程序。每种类型都需要专门的考虑。尽管一些现有示例已经足够说明如何把工作流宿主到Windows表单程序和控制台应用程序中,但是本文将集中于讨论ASP.NET开发者的问题-他们希望把工作流集成到自己的应用程序中。

   作者注:本文所提供的代码是以Windows WF Beta 1和Visual Studio 2005 Beta 2 为工具创建的。你可以在www.windowsworkflow.net找到有关安装Windows WF的信息。尽管本文讨论了Windows WF的一些基础问题,但是还有其它一些这方面的可用资源。我假定读者至少了解一点Windows WF。本文的目的是深度分析Windows WF和ASP.NET,而不是从一个高层次上讨论Windows WF。

   一、 Windows WF和MVC模式

  在开发一个ASP.NET应用程序时,你可能使用WWF的一个普通的方法是实现一种模型-视图-控制器(MVC)方法。实质上,MVC的目标是把描述层、应用程序逻辑和应用程序流逻辑分离开来。

  搞清楚这个将十分有益于一个ASP.NET应用程序的开发,请考虑一个帮助桌面票工作流的场所。假定有一个商业用户通过填写一个ASP.NET Web表单并点击一个提交按钮来启动该工作流。接下来,服务器就会通知一个使用Windows表单应用程序和帮助桌面的雇员--"有新票可用了"。该帮助桌面雇员然后将在这一问题上工作,并在最后关闭该票。如果使用Windows WF来开发这个工作流情形,那么所有的处理逻辑和流程可以被包含在工作流本身,而该ASP.NET应用程序将完全不需要了解这一逻辑。

  这种场所提供了一些稳固的证据-把描述与逻辑相分离是一件好事情。因为这个处理帮助桌面请求的过程是非常普通的,如果使用C#或VB.NET代码在若干不同的.NET应用程序中实现这一逻辑,那么你将会冒着重复编码的危险甚至更坏的情形--用完全不同的代码导致同样的商业处理过程的不同实现。但是如果你使用WWF来实现这一过程,那么需要这一过程的应用程序开发者将仅需在一处修改这些步骤-工作流本身-而不必担心这样会改变应用程序逻辑。代码复制和在哪里实现该过程可以通过Windows WF的使用来加以缓和。

  当使用Windows WF在ASP.NET中实现MVC架构时,开发者应该尝试构建独立于应用程序的工作流-而该工作流仍然宿主于该应用程序中。这将有助于保持逻辑独立于描述并且保持在该Web应用程序中的工作步骤顺序和页面流之间的高度独立性。

  一个WWF开发新手可能试图用一固定数目的活动以某种顺序去开发一个工作流,然后开发一组ASP.NET Web表单--这些表单以与之相同的顺序从一个表单流向另一个表单。很遗憾,尽管这看上去挺符合逻辑,但是实际上这是非常不具有生产效率的,因为你将会再次实现这个工作流逻辑。Web页面X不需要知道是否它需要转到页面Y或页面Z来正确地实现该工作流步骤。代之的是,该工作流(模型)应该告诉ASP.NET(控制器)下一步该干什么;然后ASP.NET应该决定要显示哪个页面。这样,每个页面几乎不需要了解整个过程;它仅需要知道怎样完成一个不同的活动并且让该工作流来关心页面是如何从一处流向另一处的。这种分离在开发者处理页面流时带来了一种极大的灵活性。例如,如果你决定改变该页面显示顺序,那么你可以从工作流中容易地实现这一点,而不需要改变该ASP.NET应用程序中的一行代码。



microgao - by - 06 七月, 2006 19:34

软件架构(software architecture)是一系列相关的抽象模式,用于指导大型软件系统各个方面的设计。 软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。在面向对象领域中,组件之间的连接通常用接口_(计算机科学)来实现。

软件体系结构是构建计算机软件实践的基础。与建筑师设定建筑项目的设计原则和目标,作为绘图员画图的基础一样,一个软件架构师或者系统架构师陈述软件构架以作为满足不同客户需求的实际系统设计方案的基础。

软件构架是一个容易理解的概念,多数工程师(尤其是经验不多的工程师)会从直觉上来认识它,但要给出精确的定义很困难。特别是,很难明确地区分设计和构架:构架属于设计的一方面,它集中于某些具体的特征。

在“软件构架简介”中,David GArlan 和 Mary Shaw 认为软件构架是有关如下问题的设计层次:“在计算的算法和数据结构之外,设计并确定系统整体结构成为了新的问题。结构问题包括总体组织结构和全局控制结构;通信、同步和数据访问的协议;设计元素的功能分配;物理分布;设计元素的组成;定标与性能;备选设计的选择。”[GS93]

但构架不仅是结构;IEEE Working Group on Architecture 把其定义为“系统在其环境中的最高层概念”[IEEE98]。构架还包括“符合”系统完整性、经济约束条件、审美需求和样式。它并不仅注重对内部的考虑,而且还在系统的用户环境和开发环境中对系统进行整体考虑,即同时注重对外部的考虑。

在 Rational Unified ProcESs 中,软件系统的构架(在某一给定点)是指系统重要构件的组织或结构,这些重要构件通过接口与不断减小的构件与接口所组成的构件进行交互。

从和目的、主题、材料和结构的联系上来说,软件架构可以和建筑物的架构相比拟。一个软件架构师需要有广泛的软件理论知识和相应的经验来事实和管理软件产品的高级设计。软件架构师定义和设计软件的模块化,模块之间的交互,用户界面风格,对外接口方法,创新的设计特性,以及高层事物的对象操作、逻辑和流程。

  是一般而言,软件系统的架构(ArchitECture)有两个要素:

  ·它是一个软件系统从整体到部分的最高层次的划分。

  一个系统通常是由元件组成的,而这些元件如何形成、相互之间如何发生作用,则是关于这个系统本身结构的重要信息。

  详细地说,就是要包括架构元件(Architecture Component)、联结器(Connector)、任务流(TASk-flow)。所谓架构元素,也就是组成系统的核心"砖瓦",而联结器则描述这些元件之间通讯的路径、通讯的机制、通讯的预期结果,任务流则描述系统如何使用这些元件和联结器完成某一项需求。

  ·建造一个系统所作出的最高层次的、以后难以更改的,商业的和技术的决定。

  在建造一个系统之前会有很多的重要决定需要事先作出,而一旦系统开始进行详细设计甚至建造,这些决定就很难更改甚至无法更改。显然,这样的决定必定是有关系统设计成败的最重要决定,必须经过非常慎重的研究和考察。

历史
早在1960年代,诸如E·W·戴克斯特拉就已经涉及软件架构这个概念了。自1990年代以来,部分由于在 Rational Software Corporation 和MiCROSoft内部的相关活动,软件架构这个概念开始越来越流行起来。

卡内基梅隆大学和加州大学埃尔文分校在这个领域作了很多研究。卡内基·梅隆大学的Mary Shaw和David Garlan于1996年写了一本叫做 Software Architecture perspective on an emerging DIscipline的书,提出了软件架构中的很多概念,例如软件组件、连接器、风格等等。 加州大学埃尔文分校的软件研究院所做的工作则主要集中于架构风格、架构描述语言以及动态架构。

  计算机软件的历史开始于五十年代,历史非常短暂,而相比之下建筑工程则从石器时代就开始了,人类在几千年的建筑设计实践中积累了大量的经验和教训。建筑设计基本上包含两点,一是建筑风格,二是建筑模式。独特的建筑风格和恰当选择的建筑模式,可以使一个独一无二。

    软件与人类的关系是架构师必须面对的核心问题,也是自从软件进入历史舞台之后就出现的问题。与此类似地,自从有了建筑以来,建筑与人类的关系就一直是建筑设计师必须面对的核心问题。英国首相丘吉尔说,我们构造建筑物,然后建筑物构造我们(We shape our buildings, and afterwaRDS our buildings shape us)。英国下议院的会议厅较狭窄,无法使所有的下议院议员面向同一个方向入座,而必须分成两侧入座。丘吉尔认为,议员们入座的时候自然会选择与自己政见相同的人同时入座,而这就是英国政党制的起源。Party这个词的原意就是"方"、"面"。政党起源的关键就是建筑物对人的影响。

  在软件设计界曾经有很多人认为功能是最为重要的,形式必须服从功能。与此类似地,在建筑学界,现代主义建筑流派的开创人之一Louis Sullivan也认为形式应当服从于功能(FORMs follows function)。

  几乎所有的软件设计理念都可以在浩如烟海的建筑学历史中找到更为遥远的历史回响。最为著名的,当然就是模式理论和XP理论。

  架构的目标是什么

  正如同软件本身有其要达到的目标一样,架构设计要达到的目标是什么呢?一般而言,软件架构设计要达到如下的目标:

  ·可靠性(Reliable)。软件系统对于用户的商业经营和管理来说极为重要,因此软件系统必须非常可靠。

  ·安全行(Secure)。软件系统所承担的交易的商业价值极高,系统的安全性非常重要。

  ·可扩展性(SCAlable)。软件必须能够在用户的使用率、用户的数目增加很快的情况下,保持合理的性能。只有这样,才能适应用户的市场扩展得可能性。

  ·可定制化(CuSTomizable)。同样的一套软件,可以根据客户群的不同和市场需求的变化进行调整。

  ·可扩展性(Extensible)。在新技术出现的时候,一个软件系统应当允许导入新技术,从而对现有系统进行功能和性能的扩展

  ·可维护性(MAIntainable)。软件系统的维护包括两方面,一是排除现有的错误,二是将新的软件需求反映到现有系统中去。一个易于维护的系统可以有效地降低技术支持的花费

  ·客户体验(Customer Experience)。软件系统必须易于使用。

  ·市场时机(Time to Market)。软件用户要面临同业竞争,软件提供商也要面临同业竞争。以最快的速度争夺市场先机非常重要。

  架构的种类

  根据我们关注的角度不同,可以将架构分成三种:

  ·逻辑架构、软件系统中元件之间的关系,比如用户界面,数据库,外部系统接口,商业逻辑元件,等等。

  比如下面就是笔者亲身经历过的一个软件系统的逻辑架构图


图2、一个逻辑架构的例子

  从上面这张图中可以看出,此系统被划分成三个逻辑层次,即表象层次,商业层次和数据持久层次。每一个层次都含有多个逻辑元件。比如WEB服务器层次中有HTML服务元件、Session服务元件、安全服务元件、系统管理元件等。

  ·物理架构、软件元件是怎样放到硬件上的。

  比如下面这张物理架构图描述了一个分布于北京和上海的分布式系统的物理架构,图中所有的元件都是物理设备,包括网络分流器、代理服务器、WEB服务器、应用服务器、报表服务器、整合服务器、存储服务器、主机等等。


图3、一个物理架构的例子

  ·系统架构、系统的非功能性特征,如可扩展性、可靠性、强壮性、灵活性、性能等。

  系统架构的设计要求架构师具备软件和硬件的功能和性能的过硬知识,这一工作无疑是架构设计工作中最为困难的工作。

  此外,从每一个角度上看,都可以看到架构的两要素:元件划分和设计决定。

  首先,一个软件系统中的元件首先是逻辑元件。这些逻辑元件如何放到硬件上,以及这些元件如何为整个系统的可扩展性、可靠性、强壮性、灵活性、性能等做出贡献,是非常重要的信息。

  其次,进行软件设计需要做出的决定中,必然会包括逻辑结构、物理结构,以及它们如何影响到系统的所有非功能性特征。这些决定中会有很多是一旦作出,就很难更改的。

  根据作者的经验,一个基于数据库的系统架构,有多少个数据表,就会有多少页的架构设计文档。比如一个中等的数据库应用系统通常含有一百个左右的数据表,这样的一个系统设计通常需要有一百页左右的架构设计文档。

构架描述

为了讨论和分析软件构架,必须首先定义构架表示方式,即描述构架重要方面的方式。在 Rational Unified Process 中,软件构架文档记录有这种描述。

构架视图

我们决定以多种构架视图来表示软件构架。每种构架视图针对于开发流程中的涉众(例如最终用户、设计人员、管理人员、系统工程师、维护人员等)所关注的特定方面。

构架视图显示了软件构架如何分解为构件,以及构件如何由连接器连接来产生有用的形式 [PW92],由此记录主要的结构设计决策。这些设计决策必须基于需求以及功能、补充和其他方面的约束。而这些决策又会在较低层次上为需求和将来的设计决策施加进一步的约束。

典型的构架视图集

构架由许多不同的构架视图来表示,这些视图本质上是以图形方式来摘要说明“在构架方面具有重要意义”的模型元素。在 Rational Unified Process 中,您将从一个典型的视图集开始,该视图集称为“4+1 视图模型”[KRU95]。它包括:

用例视图:包括用例和场景,这些用例和场景包括在构架方面具有重要意义的行为、类或技术风险。它是用例模型的子集。
逻辑视图:包括最重要的设计类、从这些设计类到包和子系统的组织形式,以及从这些包和子系统到层的组织形式。它还包括一些用例实现。它是设计模型的子集。
实施视图:包括实施模型及其从模块到包和层的组织形式的概览。 同时还描述了将逻辑视图中的包和类向实施视图中的包和模块分配的情况。它是实施模型的子集。
进程视图:包括所涉及任务(进程和线程)的描述,它们的交互和配置,以及将设计对象和类向任务的分配情况。只有在系统具有很高程度的并行时,才需要该视图。在 Rational Unified Process 中,它是设计模型的子集。
配置视图:包括对最典型的平台配置的各种物理节点的描述以及将任务(来自进程视图)向物理节点分配的情况。只有在分布式系统中才需要该视图。它是部署模型的一个子集。
构架视图记录在软件构架文档中。您可以构建其他视图来表达需要特别关注的不同方面:用户界面视图、安全视图、数据视图等等。对于简单系统,可以省略 4+1 视图模型中的一些视图。

构架重点

虽然以上视图可以表示系统的整体设计,但构架只同以下几个具体方面相关:

模型的结构,即组织模式,例如分层。
基本元素,即关键用例、主类、常用机制等,它们与模型中的各元素相对。
几个关键场景,它们表示了整个系统的主要控制流程。
记录模块度、可选特征、产品线状况的服务。

构架视图在本质上是整体设计的抽象或简化,它们通过舍弃具体细节来突出重要的特征。在考虑以下方面时,这些特征非常重要:

系统演进,即进入下一个开发周期。
在产品线环境下复用构架或构架的一部分。
评估补充质量,例如性能、可用性、可移植性和安全性。
向团队或分包商分配开发工作。
决定是否包括市售构件。
插入范围更广的系统。

构架模式

构架模式是解决复发构架问题的现成形式。构架框架或构架基础设施(中间件)是可以在其上构建某种构架的构件集。许多主要的构架困难应在框架或基础设施中进行解决,而且通常针对于特定的领域:命令和控制、MIS、控制系统等等。

构架模式示例

[BUS96] 根据构架模式最适用的系统的特征将其分类,其中一个类别处理更普遍的结构问题。下表显示了 [BUS96] 中所提供的类别和这些类别所包含的模式。

类别 模式
结构 层
管道和过滤器
黑板
分布式系统 代理
交互系统 模型-视图-控制器
表示-抽象-控制
自适应系统 反射
微核


软件构架是一个容易理解的概念,多数工程师(尤其是经验不多的工程师)会从直觉上来认识它,但要给出精确的定义很困难。特别是,很难明确地区分设计和构架:构架属于设计的一方面,它集中于某些具体的特征。

在“软件构架简介”中,David Garlan 和 Mary Shaw 认为软件构架是有关如下问题的设计层次:“在计算的算法和数据结构之外,设计并确定系统整体结构成为了新的问题。结构问题包括总体组织结构和全局控制结构;通信、同步和数据访问的协议;设计元素的功能分配;物理分布;设计元素的组成;定标与性能;备选设计的选择。”[GS93]

但构架不仅是结构;IEEE Working Group on Architecture 把其定义为“系统在其环境中的最高层概念”[IEEE98]。构架还包括“符合”系统完整性、经济约束条件、审美需求和样式。它并不仅注重对内部的考虑,而且还在系统的用户环境和开发环境中对系统进行整体考虑,即同时注重对外部的考虑。

在 Rational Unified Process 中,软件系统的构架(在某一给定点)是指系统重要构件的组织或结构,这些重要构件通过接口与不断减小的构件与接口所组成的构件进行交互。

为阐明其含义,下面将详述其中的两个;完整说明请参见 [BUS96]。模式以下列广泛使用的形式来表示:

模式名
环境
问题
影响,描述应考虑的不同问题方面
解决方案
基本原理
结果环境
示例
模式名

环境
需要进行结构分解的大系统。

问题
必须处理不同抽象层次的问题的系统。例如:硬件控制问题、常见服务问题和针对于不同领域的问题。最好不要编写垂直构件来处理所有抽象层次的问题。否则要在不同的构件中多次处理相同的问题(可能会不一致)。

影响

系统的某些部分应当是可替换的
构件中的变化不应波动
相似的责任应归为一组
构件大小 -- 复杂构件可能要进行分解
解决办法
将系统分成构件组,并使构件组形成层叠结构。使上层只使用下层(决不使用上层)提供的服务。尽量不使用非紧邻下层提供的服务(不跳层使用服务,除非中间层只添加通过构件)。

示例:

1. 通用层

严格的分层构架规定设计元素(类、构件、包、子系统)只能使用下层提供的服务, 服务可以包括事件处理、错误处理、数据库访问等等。 相对于记录在底层的原始操作系统级调用,它包括更明显的机制。

2. 业务系统层

上图显示了另一个分层示例,其中有垂直特定应用层、水平层和基础设施层。注意:此处的目标是采用非常短的业务“烟囱”并实现各种应用程序间的通用性。 否则,就可能有多个人解决同一问题,从而导致潜在的分歧。

有关该模式的深入讨论,请参见指南:分层。

模式名
黑板

环境
没有解决问题的确定方法(算法)或方法不可行的领域。例如 AI 系统、语音识别和监视系统。

问题
多个问题解决顾问(知识顾问)必须通过协作来解决他们无法单独解决的问题。各顾问的工作结果必须可以供所有其他顾问访问,使他们可以评估自己是否可以参与解决方案的查找并发布其工作结果。

影响

知识顾问参与解决问题的顺序不是确定的,这可能取决于问题解决策略

不同顾问的输入(结果或部分解决方案)可能有不同的表示方式

各顾问并不直接知道对方的存在,但可以评估对方发布的工作

解决办法
多名知识顾问都可访问一个称为“黑板”的共享数据库。黑板提供监测和更新其内容的接口。控制模块/对象激活遵循某种策略的顾问。激活后,顾问查看黑板,以确定它是否能参与解决问题。如果顾问决定它可以参与,控制对象就可以允许顾问将其部分(或最终)解决方案放置于黑板上。

示例:

以上显示了使用 UML 建模的结构或静态视图。 它将成为参数化协作的一部分,然后会绑定到实参上对模式进行实例化。

构架风格
软件构架(或仅是构架视图)可以具有名为构架风格的属性,该属性减少了可选的形式,并使构架具有一定程度的一致性。样式可以通过一组模式或通过选择特定构件或连接器作为基本构件来定义。对给定系统,某些样式可作为构架描述的一部分记录在构架风格指南(Rational Unified Process 中设计指南文档的一部分)中。样式在构架的可理解性与完整性方面起着主要的作用。

构架设计图
构架视图的图形描述称为构架设计图。对于以上描述的各种视图,设计图由以下统一建模语言图组成 [UML99]:

逻辑视图:类图、状态机和对象图。
进程视图:类图与对象图(包括任务 - 进程与线程)。
实施视图:构件图。
部署视图:配置图。
用例视图:用例图描述用例、主角和普通设计类;顺序图描述设计对象及其协作关系。
构架设计流程
在 Rational Unified Process 中,构架主要是分析设计工作流程的结果。当项目再次进行此工作流程时,构架将在一次又一次迭代中不断演化、改进、精炼。由于每次迭代都包括集成和测试,所以在交付产品时,构架就相当强壮了。构架是精化阶段各次迭代的重点,构架的基线通常会在此阶段结束时确定。

  架构师

  软体设计师中有一些技术水平较高、经验较为丰富的人,他们需要承担软件系统的架构设计,也就是需要设计系统的元件如何划分、元件之间如何发生相互作用,以及系统中逻辑的、物理的、系统的重要决定的作出。

  这样的人就是所谓的架构师(Architect)。在很多公司中,架构师不是一个专门的和正式的职务。通常在一个开发小组中,最有经验的程序员会负责一些架构方面的工作。在一个部门中,最有经验的项目经理会负责一些架构方面的工作。

  但是,越来越多的公司体认到架构工作的重要性,并且在不同的组织层次上设置专门的架构师位置,由他们负责不同层次上的逻辑架构、物理架构、系统架构的设计、配置、维护等工作。

卡内基梅隆大学软件研究所关于软件架构的定义



microgao - by - 20 六月, 2006 11:20

程序员和系统分析员,大家应该对这两个词很熟悉了,但是对词里包含的意义可能并不是特别清楚。首先必须说明的是,程序员和系统分析员不存在谁高级谁低级的分别,他们是两种职业,对职业技能的要求完全不同。所以厉害的程序员就是系统分析员的说法是不对的。当然,系统分析员的技能要求他必须要懂得如何写程序,但是他的重心在于如何把一个很大的项目切割成适合个人的小块,然后将这些小块组织起来。程序员的职责就是如何更好更快的实现这些小块。

  在正式开始之前,我们还是来看在Thinking In Java中作者对分析和设计的一段精辟见解:

  分析和设计

  面向对象的范式是思考程序设计时一种新的、而且全然不同的方式,许多人最开始都会在如何构造一个项目上皱起了眉头。事实上,我们可以作出一个“好”的设计,它能充分利用OOP提供的所有优点。

  请原谅在这里突然出现了OOP这个词,他的意思是面相对象,虽然在之前没有提到,但是在现在OO概念满天飞的软件世界里,大家应该对他不会太陌生。这里我简要的说明一下。在之前我介绍的实际上都是在很早以前程序写作流传下来的经验(什么,教我们老古董,打他!),但是以前的非OO(就是基于过程)的软件设计方法目前在国际上已经很少采用,所以我这里讲软件设计的时候所有的概念都是基于OO的。即使OO的概念很简单的啦,大家思考一下,我们再学习C++的时候一开始使用的类不都是一些动物啦、正方形啦之类的,都是生活中的例子,对吧。其实OO就是我们看世界的一种方式。可是最早由于计算机技术的不发达,我们不得不用一些很奇怪的描述来表达我们的意思,只有这样计算机才能理解,很笨不是吗。比如我们必须使用参数、过程、函数。所以当时的软件设计方法都是基于过程的。举一个简单的例子来显示OO设计方法和基于过程的设计方法之间的差别:一句简单的日常短语--“我吃饭”,用OO的方法来表述还是“我吃饭”,可是如果用基于过程的方法来描述的话就变成“我吃饭(饭)”,是不是很别扭呢。

  有关OOP分析与设计的书籍大多数都不尽如人意。其中的大多数书都充斥着莫名其妙的话语、笨拙的笔调以及许多听起来似乎很重要的声明。我认为这种书最好压缩到一章左右的空间,至多写成一本非常薄的书。具有讽剌意味的是,那些特别专注于复杂事物管理的人往往在写一些浅显、明白的书上面大费周章!如果不能说得简单和直接,一定没多少人喜欢看这方面的内容。毕竟,OOP的全部宗旨就是让软件开发的过程变得更加容易。尽管这可能影响了那些喜欢解决复杂问题的人的生计,但为什么不从一开始就把事情弄得简单些呢?因此,希望我能从开始就为大家打下一个良好的基础,尽可能用几个段落来说清楚分析与设计的问题。

  不要迷失

  在整个开发过程中,最重要的事情就是:不要将自己迷失!但事实上这种事情很容易发生。大多数方法都设计用来解决最大范围内的问题。当然,也存在一些特别困难的项目,需要作者付出更为艰辛的努力,或者付出更大的代价。但是,大多数项目都是比较“常规”的,所以一般都能作出成功的分析与设计,而且只需用到推荐的一小部分方法。但无论多么有限,某些形式的处理总是有益的,这可使整个项目的开发更加容易,总比直接了当开始编码好! 也就是说,假如你正在考察一种特殊的方法,其中包含了大量细节,并推荐了许多步骤和文档,那么仍然很难正确判断自己该在何时停止。时刻提醒自己注意以下几个问题:

  (1) 对象是什么?(怎样将自己的项目分割成一系列单独的组件?)

  (2) 它们的接口是什么?(需要将什么消息发给每一个对象?)

  在确定了对象和它们的接口后,便可着手编写一个程序。出于对多方面原因的考虑,可能还需要比这更多的说明及文档,但要求掌握的资料绝对不能比这还少。

  整个过程可划分为四个阶段,阶段0刚刚开始采用某些形式的结构。

  阶段0:拟出一个计划

  第一步是决定在后面的过程中采取哪些步骤。这听起来似乎很简单(事实上,我们这儿说的一切都似乎很简单),但很常见的一种情况是:有些人甚至没有进入阶段1,便忙忙慌慌地开始编写代码。如果你的计划本来就是“直接开始开始编码”,那样做当然也无可非议(若对自己要解决的问题已有很透彻的理解,便可考虑那样做)。但最低程度也应同意自己该有个计划。

  在这个阶段,可能要决定一些必要的附加处理结构。但非常不幸,有些程序员写程序时喜欢随心所欲,他们认为“该完成的时候自然会完成”。这样做刚开始可能不会有什么问题,但我觉得假如能在整个过程中设置几个标志,或者“路标”,将更有益于你集中注意力。这恐怕比单纯地为了“完成工作”而工作好得多。至少,在达到了一个又一个的目标,经过了一个接一个的路标以后,可对自己的进度有清晰的把握,干劲也会相应地提高,不会产生“路遥漫漫无期”的感觉。

  从我刚开始学习故事结构起(我想有一天能写本小说出来),就一直坚持这种做法,感觉就象简单地让文字“流”到纸上。在我写与计算机有关的东西时,发现结构要比小说简单得多,所以不需要考虑太多这方面的问题。但我仍然制订了整个写作的结构,使自己对要写什么做到心中有数。因此,即使你的计划就是直接开始写程序,仍然需要经历以下的阶段,同时向自己提出一些特定的问题。

  阶段1:要制作什么?

  在上一代程序设计中(即“过程化或程序化设计”),这个阶段称为“建立需求分析和系统规格”。当然,那些操作今天已经不再需要了,或者至少改换了形式。大量令人头痛的文档资料已成为历史。但当时的初衷是好的。需求分析的意思是“建立一系列规则,根据它判断任务什么时候完成,以及客户怎样才能满意”。系统规格则表示“这里是一些具体的说明,让你知道程序需要做什么(而不是怎样做)才能满足要求”。

  需求分析实际就是你和客户之间的一份合约(即使客户就在本公司内部工作,或者是其他对象及系统)。系统规格是对所面临问题的最高级别的一种揭示,我们依据它判断任务是否完成,以及需要花多长的时间。由于这些都需要取得参与者的一致同意,所以我建议尽可能地简化它们——最好采用列表和基本图表的形式——以节省时间。可能还会面临另一些限制,需要把它们扩充成为更大的文档。 我们特别要注意将重点放在这一阶段的核心问题上,不要纠缠于细枝末节。这个核心问题就是:决定采用什么系统。对这个问题,最有价值的工具就是一个名为“使用条件”的集合。对那些采用“假如……,系统该怎样做?”形式的问题,这便是最有说服力的回答。例如,“假如客户需要提取一张现金支票,但当时又没有这么多的现金储备,那么自动取款机该怎样反应?”对这个问题,“使用条件”可以指示自动取款机在那种“条件”下的正确操作。

  应尽可能总结出自己系统的一套完整的“使用条件”或者“应用场合”。一旦完成这个工作,就相当于摸清了想让系统完成的核心任务。由于将重点放在“使用条件”上,一个很好的效果就是它们总能让你放精力放在最关键的东西上,并防止自己分心于对完成任务关系不大的其他事情上面。也就是说,只要掌握了一套完整的“使用条件”,就可以对自己的系统作出清晰的描述,并转移到下一个阶段。在这一阶段,也有可能无法完全掌握系统日后的各种应用场合,但这也没有关系。只要肯花时间,所有问题都会自然而然暴露出来。不要过份在意系统规格的“完美”,否则也容易产生挫败感和焦燥情绪。 在这一阶段,最好用几个简单的段落对自己的系统作出描述,然后围绕它们再进行扩充,添加一些“名词”和“动词”。“名词”自然成为对象,而“动词”自然成为要整合到对象接口中的“方法”。只要亲自试着做一做,就会发现这是多么有用的一个工具;有些时候,它能帮助你完成绝大多数的工作。

  尽管仍处在初级阶段,但这时的一些日程安排也可能会非常管用。我们现在对自己要构建的东西应该有了一个较全面的认识,所以可能已经感觉到了它大概会花多长的时间来完成。此时要考虑多方面的因素:如果估计出一个较长的日程,那么公司也许决定不再继续下去;或者一名主管已经估算出了这个项目要花多长的时间,并会试着影响你的估计。但无论如何,最好从一开始就草拟出一份“诚实”的时间表,以后再进行一些暂时难以作出的决策。目前有许多技术可帮助我们计算出准确的日程安排(就象那些预测股票市场起落的技术),但通常最好的方法还是依赖自己的经验和直觉(不要忘记,直觉也要建立在经验上)。感觉一下大概需要花多长的时间,然后将这个时间加倍,再加上10%。你的感觉可能是正确的;“也许”能在那个时间里完成。但“加倍”使那个时间更加充裕,“10%”的时间则用于进行最后的推敲和深化。但同时也要对此向上级主管作出适当的解释,无论对方有什么抱怨和修改,只要明确地告诉他们:这样的一个日程安排,只是我的一个估计!

  阶段2:如何构建?

  在这一阶段,必须拿出一套设计方案,并解释其中包含的各类对象在外观上是什么样子,以及相互间是如何沟通的。此时可考虑采用一种特殊的图表工具:“统一建模语言”(UML)。请到http://www.rational.com/去下载一份UML规格书。作为第1阶段中的描述工具,UML也是很有帮助的。此外,还可用它在第2阶段中处理一些图表(如流程图)。当然并非一定要使用UML,但它对你会很有帮助,特别是在希望描绘一张详尽的图表,让许多人在一起研究的时候。除UML外,还可选择对对象以及它们的接口进行文字化描述(《Thinking in C++》里说的那样,但这种方法非常原始,发挥的作用亦较有限。

  我曾有一次非常成功的咨询经历,那时涉及到一小组人的初始设计。他们以前还没有构建过OOP(面向对象程序设计)项目,将对象画在白板上面。我们谈到各对象相互间该如何沟通(通信),并删除了其中的一部分,以及替换了另一部分对象。这个小组(他们知道这个项目的目的是什么)实际上已经制订出了设计方案;他们自己“拥有”了设计,而不是让设计自然而然地显露出来。我在那里做的事情就是对设计进行指导,提出一些适当的问题,尝试作出一些假设,并从小组中得到反馈,以便修改那些假设。这个过程中最美妙的事情就是整个小组并不是通过学习一些抽象的例子来进行面向对象的设计,而是通过实践一个真正的设计来掌握OOP的窍门,而那个设计正是他们当时手上的工作!

  作出了对对象以及它们的接口的说明后,就完成了第2阶段的工作。当然,这些工作可能并不完全。有些工作可能要等到进入阶段3才能得知。但这已经足够了。我们真正需要关心的是最终找出所有的对象。能早些发现当然好,但OOP提供了足够完美的结构,以后再找出它们也不迟。

  阶段3:开始创建

  读这本书的可能是程序员,现在进入的正是你可能最感兴趣的阶段。由于手头上有一个计划——无论它有多么简要,而且在正式编码前掌握了正确的设计结构,所以会发现接下去的工作比一开始就埋头写程序要简单得多。而这正是我们想达到的目的。让代码做到我们想做的事情,这是所有程序项目最终的目标。但切不要急功冒进,否则只有得不偿失。根据我的经验,最后先拿出一套较为全面的方案,使其尽可能设想周全,能满足尽可能多的要求。给我的感觉,编程更象一门艺术,不能只是作为技术活来看待。所有付出最终都会得到回报。作为真正的程序员,这并非可有可无的一种素质。全面的思考、周密的准备、良好的构造不仅使程序更易构建与调试,也使其更易理解和维护,而那正是一套软件赢利的必要条件。

  构建好系统,并令其运行起来后,必须进行实际检验,以前做的那些需求分析和系统规格便可派上用场了。全面地考察自己的程序,确定提出的所有要求均已满足。现在一切似乎都该结束了?是吗?

  阶段4:校订

  事实上,整个开发周期还没有结束,现在进入的是传统意义上称为“维护”的一个阶段。“维护”是一个比较暧昧的称呼,可用它表示从“保持它按设想的轨道运行”、“加入客户从前忘了声明的功能”或者更传统的“除掉暴露出来的一切臭虫”等等意思。所以大家对“维护”这个词产生了许多误解,有的人认为:凡是需要“维护”的东西,必定不是好的,或者是有缺陷的!因为这个词说明你实际构建的是一个非常“原始”的程序,以后需要频繁地作出改动、添加新的代码或者防止它的落后、退化等。因此,我们需要用一个更合理的词语来称呼以后需要继续的工作。 这个词便是“校订”。换言之,“你第一次做的东西并不完善,所以需为自己留下一个深入学习、认知的空间,再回过头去作一些改变”。对于要解决的问题,随着对它的学习和了解愈加深入,可能需要作出大量改动。进行这些工作的一个动力是随着不断的改革优化,终于能够从自己的努力中得到回报,无论这需要经历一个较短还是较长的时期。

  什么时候才叫“达到理想的状态”呢?这并不仅仅意味着程序必须按要求的那样工作,并能适应各种指定的“使用条件”,它也意味着代码的内部结构应当尽善尽美。至少,我们应能感觉出整个结构都能良好地协调运作。没有笨拙的语法,没有臃肿的对象,也没有一些华而不实的东西。除此以外,必须保证程序结构有很强的生命力。由于多方面的原因,以后对程序的改动是必不可少。但必须确定改动能够方便和清楚地进行。这里没有花巧可言。不仅需要理解自己构建的是什么,也要理解程序如何不断地进化。幸运的是,面向对象的程序设计语言特别适合进行这类连续作出的修改——由对象建立起来的边界可有效保证结构的整体性,并能防范对无关对象进行的无谓干扰、破坏。也可以对自己的程序作一些看似激烈的大变动,同时不会破坏程序的整体性,不会波及到其他代码。事实上,对“校订”的支持是OOP非常重要的一个特点。

  通过校订,可创建出至少接近自己设想的东西。然后从整体上观察自己的作品,把它与自己的要求比较,看看还短缺什么。然后就可以从容地回过头去,对程序中不恰当的部分进行重新设计和重新实现(注释⑩)。在最终得到一套恰当的方案之前,可能需要解决一些不能回避的问题,或者至少解决问题的一个方面。而且一般要多“校订”几次才行。

  构建一套系统时,“校订”几乎是不可避免的。我们需要不断地对比自己的需求,了解系统是否自己实际所需要的。有时只有实际看到系统,才能意识到自己需要解决一个不同的问题。若认为这种形式的校订必然会发生,那么最好尽快拿出自己的第一个版本,检查它是否自己希望的,使自己的思想不断趋向成熟。

  反复的“校订”同“递增开发”有关密不可分的关系。递增开发意味着先从系统的核心入手,将其作为一个框架实现,以后要在这个框架的基础上逐渐建立起系统剩余的部分。随后,将准备提供的各种功能(特性)一个接一个地加入其中。这里最考验技巧的是架设起一个能方便扩充所有目标特性的一个框架(对这个问题,大家可参考第16章的论述)。这样做的好处在于一旦令核心框架运作起来,要加入的每一项特性就象它自身内的一个小项目,而非大项目的一部分。此外,开发或维护阶段合成的新特性可以更方便地加入。OOP之所以提供了对递增开发的支持,是由于假如程序设计得好,每一次递增都可以成为完善的对象或者对象组。

  这有点类似“快速造型”。此时应着眼于建立一个简单、明了的版本,使自己能对系统有个清楚的把握。再把这个原型扔掉,并正式地构建一个。快速造型最麻烦的一种情况就是人们不将原型扔掉,而是直接在它的基础上建造。如果再加上程序化设计中“结构”的缺乏,就会导致一个混乱的系统,致使维护成本增加。
计划的回报

  如果没有仔细拟定的设计图,当然不可能建起一所房子。如建立的是一所狗舍,尽管设计图可以不必那么详尽,但仍然需要一些草图,以做到心中有数。软件开发则完全不同,它的“设计图”(计划)必须详尽而完备。在很长的一段时间里,人们在他们的开发过程中并没有太多的结构,但那些大型项目很容易就会遭致失败。通过不断的摸索,人们掌握了数量众多的结构和详细资料。但它们的使用却使人提心吊胆在意——似乎需要把自己的大多数时间花在编写文档上,而没有多少时间来编程(经常如此)。我希望这里为大家讲述的一切能提供一条折衷的道路。需要采取一种最适合自己需要(以及习惯)的方法。不管制订出的计划有多么小,但与完全没有计划相比,一些形式的计划会极大改善你的项目。请记住:根据估计,没有计划的50%以上的项目都会失败!

  非常佩服作者对软件构建过程的精辟见解,软件工程是一门内容非常繁杂的学科,但是作者能够用浅显易懂的句子把它描述出来,真的是非常不简单。软件工程最早的提出者并不是计算机的专业人士,而是一位建筑设计师,所以软件工程的很多思想来自于建筑学。经过了几十年的发展,软件工程经历了很多次的蜕变。形成了今天的世界上以一些大公司提出的架构为主的形式:比如微软提出的COM及COM+以及基于其上的DNA体系,SUN提出的EJB,CORBA,还有BEA、WebLogic、IBM等公司的架构。虽然架构有不同,但是他们的思想都是相通的,架构的作用都是起到辅助开发者实现规范的、科学的软件开发过程。至于谈软件项目的管理和开发,那么Rational公司就是这方面的鼻祖。综合来说,目前世界范围内的软件工程提倡的就是以渐进的、螺旋式的开发方法构建基于组件的软件产品。现在说这些东西可能有些画饼的嫌疑,随着我们专题讨论的继续深入,这些概念就会很清晰的展现在面前。

  虽然很希望能够继续的讨论软件工程方面的东东,但是我们的这个专题毕竟是讨论如何编写优美的程序的,离题还是不要太过分的好,至于软件工程的详细讨论,我会在接下去的专题中继续。在接下去的篇幅中,我们会继续讨论程序员和系统分析员之间的差别。

  四个阶段

  这里我不想举一大堆的数字和实例来描述软件危机和论证软件工程的重要性,这方面的资料有很多,如果一一列举的话,会被怀疑别有用心。事实上,建造狗舍和写一个小的软件没有很大的区别。虽然你认为你的能力可以很轻松的完成小型的软件系统,根本不需要任何的计划。好!我来问问你,你在写程序代码的时候,有没有过漏这漏那,程序快接近完成的时候却发现少了一个很重要的模块;有没有过在书写了大量的代码之后觉得自己写出来的东西不堪入目,恨不得重头开始;有没有过写程序花了两天的时间,但是Debug却花了一个星期的时间;有没有过听到软件的使用者说要改需求,你就恨不得狠狠揍他一顿。如果都没有,那么只有两种可能:你是个超级天才,所有人类能够想到的美好品质你都具有,另一种可能:你根本没有开发过软件。

  即便是个人开发的软件,软件工程科学中也有相应的方法来指导软件的开发过程,这种方法叫做PSP(个人软件开发过程),与此相对的,还有TSP(小组软件开发过程)。这些被事实证明行之有效的方法包括了一整套的规范,帮助你开发你的软件,不让你的程序变得无法控制。可以说,对于任何一个软件系统来说,只要你花一些时间去设计,即便你的设计仅仅只是在草稿纸上随便的涂抹,在软件开发完成后,你就会惊喜的发现,你在软件开发早期的小小投入,已经为你带来了额外的好处。

  路标和RUP

  在阶段0中,我想最重要的思想就是就是“路标”的概念了,和这个概念相类似的概念还有“周期”和“里程碑”的概念,这些概念在Rational公司的RUP(Rational Unified Process 软件统一过程)中有详细的论述。不论这些概念叫做什么,他们体现出来的是一种迭代开发的思想。面对当今的复杂的软件系统,使用连续的开发方法:如首先定义整个问题,设计完整的解决方案,编制软件并最终测试产品,是不可能的。需要一种能够通过一系列细化,若干个渐进的反复过程而生成有效解决方案的迭代方法。

  Rational Unified Process支持专注于处理生命周期中每个阶段中最高风险的迭代开发方法,极大地减少了项目的风险性。迭代方法通过可验证的方法来帮助减少风险--经常性的,可执行版本使最终用户不断的介入和反馈。因为每个迭代过程以可执行版本告终,开发队伍停留在产生结果上,频繁的状态检查帮助确保项目能按时进行。迭代化方法同样使得需求、特色、日程上战略性的变化更为容易。(出自《Rational Unified Process白皮书》)

  上面这段好像很复杂,但是他所要说明的思想却是很简单的,就拿搭建狗舍来说,你的第一个“路标”可能是要搭一个框架,这个框架是由几根结实的木头组成,等到框架完成之后,你会把你的小白叫来,让他试一下,糟糕的是,这个框架对于小白来说小了一些,这时候你嘘了一口气,因为你原来是打算把整个狗舍搭好以后再叫小白来试一下的,如果你那样做的话,你剩下的木头可能就不够再盖一间狗舍了。好吧,既然有了些问题,我们就把框架调整一下,可能这个过程也花了你一些木头,不过所幸木头还够。在修整完毕后,你觉得第一步的计划虽然有些挫折,不过仍可以算是成功的,接下来你就要建立第二个“路标”了:第二个的“路标”是为狗舍钉上墙板和做出一个底座,你可能花了一些时间来思考以及和你的小白商量是否要在墙壁上开一个窗户和给底座加上轮子,在决定之后,你很快的达成了第二个“路标”。而且在经过了小白的测试后,你发现完全没有问题,你自己都觉得有些佩服自己了,很快的,你又完成刷油漆等“路标”。整个过程进展的非常顺利,而你在做狗舍方面很有天赋的名声也在你的街坊四邻间不胫而走。

  很简单是吧,其实本来就是简单的,软件工程的目的就是要把复杂的软件开发过程条理化,简单化。记住,在你使用迭代开发方法的时候,它在每个周期后的产品是一份可执行代码,是一份可以让你的用户品头论足的东西。而这份可执行代码不仅包括了程序本身,可能还有其他的产成品,例如:文档等。

  问题和场景

  在阶段1中,非常重要的一点是问题描述,在多数情况下,问题描述来自于你的软件的使用者,就是用户。用户的需求决定了问题描述,糟糕的是,用户多半不懂计算机,对他们来说,他们只能够用日常的语言来表达自己的需要。而你的任务就是要把他们的语言翻译成计算机语言,不过并不是指象C那样的高级语言,而是便于你构造系统的需求描述语言。这同样很简单:你只需要问自己几个问题就可以:在什么场合?有什么条件?做些什么事?回答好这三个问题,你就完成了一个完整的问题描述了。

  举一个简单的例子:一个银行的信贷系统有这样的问题描述:

  “若顾客采取信用贷款方式,销售员就请求信用部门的审核人员查核顾客的信用,此时审核人员会向销售员取得顾客信用编号和销货总金额。”

  在某种条件下应该做什么事情,这就是这个问题描述的表现形式,很简单是吧。

  实际上,这里可以引申出两个概念:场景(context)和问题(problem),场景指的是一种特定的情况,会导致某种问题的发生;而问题是在某个场景之中,但它也有可能产生出新的场景。

  过程和对象

  有必要说明一下以前基于过程的软件开发和目前基于对象的软件开发的不同。在没有OO的年代里,DFD(Data Flow Diagram 数据流程图)是一份软件设计中的非常重要的文档,注意力的关键也是集中在数据如何在各个系统之间传递。可是在现在的OO概念中,数据大有为消息(message)所替代的趋势。比如你到麦当劳快餐店,要花10块钱买一份汉堡。在DFD的时代,就是这样表示的:

  如果你用消息表示法来表示话,就是另一种方式:

  怎么样,你觉得那一种方式更自然呢。(如果你敢回答第一种的话我就...)。再比如上一段话中关于问题描述的例子,如果用传统的数据描述的方法的话,就会是这样子的:

  “若采取信用贷款方式,销售员就将顾客信用编号及总金额交给信用部门的信用审核人员。”

  请比较其中的两句话:

  “若采取信用贷款方式,销售员就将顾客信用编号及总金额交给信用部门的信用审核人员。”

  “若顾客采取信用贷款方式,销售员就请求信用部门的审核人员查核顾客的信用,此时审核人员会向销售员取得顾客信用编号和销货总金额。”

  了解其中的不同之处了吗,用自然的语言去描述你的问题,这是写出好的软件的第一步。

系统分析员的语言

  场景描述是一个很不错的方法,可是随着你对系统的分析的深入,参与开发的人员的增加,你渐渐的感觉这种方法不够用了。原因有很多:文字的描述不够直观,不可能到达一种很细致的程度。这时候就需要一种能够描述问题、描述解决方案、起沟通作用的语言。这就是UML。

  UML(Unified Modeling Language 统一建模语言)是由Rational公司发明,目前由OMG(标准化对象管理机构)维护。作为一种建模语言,UML的定义包括UML语义和UML表示法两个部分:

  UML语义 

  描述基于UML的精确元模型定义。元模型为UML的所有元素在语法和语义上提供了简单、一致、通用的定义性说明,使开发者能在语义上取得一致,消除了因人而异的最佳表达方法所造成的影响。此外UML还支持对元模型的扩展定义。

  UML表示法 

  定义UML符号的表示法,为开发者或开发工具使用这些图形符号和文本语法为系统建模提供了标准。这些图形符号和文字所表达的是应用级的模型,在语义上它是UML元模型的实例。标准建模语言UML的重要内容可以由下列五类图(共9种图形)来定义:用例图、静态图、行为图、交互图、实现图。

  从应用的角度看,当采用面向对象技术设计系统时,首先是描述需求;其次根据需求建立系统的静态模型,以构造系统的结构;第三步是描述系统的行为。其中在第一步与第二步中所建立的模型都是静态的,包括用例图、类图(包含包)、对象图、组件图和配置图等五个图形,是标准建模语言UML的静态建模机制。其中第三步中所建立的模型或者可以执行,或者表示执行时的时序状态或交互关系。它包括状态图、活动图、顺序图和合作图等四个图形,是标准建模语言UML的动态建模机制。因此,标准建模语言UML的主要内容也可以归纳为静态建模机制和动态建模机制两大类。

  这样说,你可能还是不了解UML到底是什么,不过UML并不是我们讨论的重点,你只需要知道UML是一种建模语言,他的目的就是在开发团队之间提供一种通用的、简单的沟通机制,并且UML是面相对象的。

  上面所说的这些就是阶段2的重点所在。使用UML语言对阶段1中提出的问题描述进行深化,从各方各面看待问题:顺序、流程、数据、状态、接口等。最终你得到的是一套完整的文档,做为详细的程序设计的参考和标准。

  不要累坏自己

  上面说了这么多在编程之前要做的工作,但是要注意的是,不是所有的开发工作都必须做这么多的工作的。对于你来说,抓住最重要的内容,不要涉及到太多的细节。对你写出来问题描述,必须要仔细的分析,而这里的分析呢,并不是要你用程序来实现你的问题描述,而是你必须从问题描述中分析出对象(object)、事件(event)和消息(message)。例如上面所提到的例子:

  “若采取信用贷款方式,销售员就将顾客信用编号及总金额交给信用部门的信用审核人员。”

  你可能看到这句话以后就会在想做一个审核信用的函数,参数部分有信用编号和总金额,然后开始设计程序的细节。如果你是这样干的话,这说明你还没有真正了解OO的思想,你的开发方式仍然停留在以前基于过程的开发方式中。那么面相对象的分析是怎样进行的呢。我们还是来看这段话,撇开你的程序,你的语言,你从这句话中获得了什么信息?是的,你知道说这个企业中有销售员、有信用审核人员;要求审核顾客信用是他们的工作之一;顾客的信用编号和总金额是审核顾客信用的时候的重要信息。这是任何一个普通的人看到这段话后的感觉,这就是OO的分析(在OO中这种的分析方法称为OOAD)。其实是很简单的,和你采用什么样的程序一点关系都没有。

  软件重用(Reuse)是软件工程中最重要的思想之一,只有软件重用,才能降低软件成本,提高软件的质量。你在对一个软件进行分析的时候,找出可以重用的对象,有助于你开发高效的软件系统。正如前面所说的,你不必把软件分析的过分细致,你只需从中找出关键性的、能够重用的对象就足够了。剩下的事情,就是对这些对象分配属性和方法,并充分的使用这些对象就好了。

  还是那个例子:我们已经从中看到了一些对象:销售员、信用审核人员。但是他们在系统中不具备重用性,至少是重用性不强。信用部门这个对象也存在这个问题。所以不能够把他们作为底层的对象,我们用一个术语Entity Objects 来称呼底层对象。那什么是软件系统中的Entity Objects呢?毛主席教导我们要透过现象看本质。上面的描述中有很多隐含的信息:

  顾客请求信用贷款:这里就包括两个Entity Objects:顾客、信用贷款。

  销售员、信用审核人员都是员工,所以员工也是一个Entity Objects。

  观察一个Entity Objects,你会发现,Entity Objects已经是最小的单位,是使用软件的用户(这里是银行)最小的单位。当然不但是这个软件有Entity Objects,比如我们最常用的Word,我们可以想象一下它的Entity Objects有什么,可能是一种叫做图元的Entity Objects,一个图元包括了文字、图象以及其他的对象。

  用OO的方法分析你的软件,从设计到实现都是非常的自然,可能你学习面向对象时是从C++开始的,被其中的虚函数、多态性、多基类继承搞得一头雾水。但是实际上OO的方法要比以前的方法简单的多,不相信?试一试,你就知道了。

  校订、校订再校订

  第3个阶段称为校订,这个称呼真是太贴切了,既不是测试,也不是返工。任何事物从诞生起都必须经历不断的改进才有可能成熟。(呵呵,说出这么有哲理的话,我都很佩服自己啊。)软件同样不例外。事实上,在阶段0中,我们就讨论了“路标”的概念,当你的第一个路标达成之后,剩下的应该都是属于校订的事了。通过和用户的交互,确定新的“路标”,不断的改进系统功能,优化系统结构,修正系统Bug。

  正如作者所说的那样,真正OO的软件是经得起修改的,由于采用了多层的结构以及面向对象的思想,以前软件的致命伤(修改)在新的开发方法面前不会是个大问题。(注意,这并不是说你可以无规划构建你的软件)

  由于以前的系统大多会将GUI、事务处理、数据存储都做在一起,当你需要修改一个地方的时候,你就会发现问题的严重性:一个小小的需求变动都意味着你的系统将面临伤筋动骨般的修改。在OO的时代里,所有的对象的访问都是通过接口(Interface)进行的,对象中方法的改变并不会接口产生影响,也就是说,调用该接口的模块不用做任何的修改。另外,如果你打算把你的系统的界面从C/S方式向B/S方式改变,同样不会有问题,你可以很容易的把GUI部分从原有的系统中剥离出来。软件的修改对你来说不再是一个恶梦。



microgao - by - 16 六月, 2006 17:02

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Diagnostics;
using System.Drawing;
using System.IO;

using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using DesktopSearch1.Icons;
using DesktopSearch1.Parsing;

namespace DesktopSearch1
{

public partial class _Default : System.Web.UI.Page
{
private string pathIndex;
private IndexWriter indexWriter;
private string[] patterns = { "*.doc", "*.xls", "*.ppt", "*.htm", "*.txt" };
public System.Web.UI.WebControls.Label labelStatus;
private IndexSearcher searcher = null;


protected void Page_Load(object sender, EventArgs e)
{
Messagebox.Text = " ";
this.pathIndex = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DesktopSearch");
checkIndex();
}
protected void TextBox1_TextChanged(object sender, EventArgs e)
{

}

private void addFolder(DirectoryInfo directory)
{
// find all matching files
foreach (string pattern in patterns)
{
foreach (FileInfo fi in directory.GetFiles(pattern))
{
// skip temporary office files
if (fi.Name.StartsWith("~"))
continue;

try
{
//addOfficeDocument(fi.FullName);

// update statistics
// this.countTotal++;
//this.bytesTotal += fi.Length;

// show added file
Response.Write(fi.FullName);
}
catch (Exception)
{
// parsing and indexing wasn't successful, skipping that file
// this.countSkipped++;
Response.Write("Skipped: " + fi.FullName);
}
}
}
// add subfolders
foreach (DirectoryInfo di in directory.GetDirectories())
{
addFolder(di);
}
}

protected void create_Click(object sender, EventArgs e)
{
indexWriter = new IndexWriter("c:index", new StandardAnalyzer(), true);

//bytesTotal = 0;
//countTotal = 0;
//countSkipped = 0;

//enableControls(false);

DirectoryInfo di = new DirectoryInfo("c:gaoyu");

DateTime start = DateTime.Now;

addFolder(di);

//string summary = "成功建立索引";
//status(summary);
Messagebox.Text = "成功建立索引";
//enableControls(true);

indexWriter.Optimize();
indexWriter.Close();
}

private void search1()
{
DateTime start = DateTime.Now;

try
{
searcher = new IndexSearcher("c:index");


// this.listViewResults.Items.Clear();

// Parse the query, "text" is the default field to search
if (this.TextBox1.Text.Trim(new char[] { ' ' }) == String.Empty)
return;

Query query = QueryParser.Parse(this.TextBox1.Text, "text", new StandardAnalyzer());

// Search
Hits hits = searcher.Search(query);

Response.Write("ddd");
// Optionally limit the result count
// int resultsCount = smallerOf(20, hits.Length());

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

// get the document from index
Document doc = hits.Doc(i);

// create a new row with the result data
string filename = doc.Get("title");
string path = doc.Get("path");
string folder = Path.GetDirectoryName(path);
DirectoryInfo di = new DirectoryInfo(folder);
Response.Write(filename);
Messagebox.Text = (filename);
//ListViewItem item = new ListViewItem(new string[] { null, filename, di.Name, hits.Score(i).ToString() });
//item.Tag = path;
//item.ImageIndex = imageListDocuments.IconIndex(filename);
//this.listViewResults.Items.Add(item);
//Application.DoEvents();
}
// searcher.Close();
}
catch (IOException ex)
{
Response.Write("The index doesn't exist or is damaged. Please rebuild the index.rnrnDetails:rn" + ex.Message);
return;
}
finally
{
searcher.Close();
}
// Response.Write("Search took {0}. Found {1} items.");
// status(searchReport);
}


public void search_Click(object sender, EventArgs e)
{
//Response.Write("The index");
search1();
}

private void checkIndex()
{
try
{
searcher = new IndexSearcher("c:index");
searcher.Close();
}
catch (IOException)
{
Response.Write("索引已经损坏或丢失.请重建.");
return;
}

string msg = String.Format("索引已经存在", searcher.MaxDoc());
Response.Write(msg);
}
}
}



microgao - by - 16 六月, 2006 16:55

页面1:

<%@ page language="java" contentType="text/html;charset=gb2312"%>
<%@ page import="org.apache.lucene.analysis.*"%>
<%@ page import="org.apache.lucene.analysis.standard.*"%>
<%@ page import="org.apache.lucene.search.*"%>
<%@ page import="org.apache.lucene.queryParser.*"%>
<%@ page import="org.apache.lucene.document.*"%>
<%@ page import="java.util.*"%>


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>


<title></title>

<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">

<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<link href="../../include/style.css" rel="stylesheet" type="text/css">
<body>
<br>
<br>
<%
Analyzer anlzr = new StandardAnalyzer();

try{


String contentget =request.getParameter("content");
//
Query q = QueryParser.parse(contentget, "contents", anlzr);

//System.out.println("Searching for : " + q.toString("contents"));



Searcher serch = new IndexSearcher("C:eclipseworkspacegyfedaWebRootbusinessdocumentmyindex");


Hits hts = serch.search(q);

for(int i=0; i<hts.length(); i++){
Document doc = hts.doc(i);
String path = doc.get("path");
// System.out.println("Find: " +(i+1)+": "+ path);
//System.out.println("Find: " + doc.get("modified"));
//System.out.println("Find: " + doc.get("path"));
String[] pathes=path.split("");
path=pathes[pathes.length-1];
out.print("<a href='files"+path+"'>"+path+"</a><br>");
out.print("<br>");

}
out.print("一共找到: " + hts.length()+"个文件");
//System.out.println("Find Total: " + hts.length());
serch.close();
}catch(Exception e){
System.out.println(e);
}
%>
</body>
</html>
*************************************************************

页面2:


<%@ page language="java" contentType="text/html;charset=gb2312"%>
<%@ page import="org.apache.lucene.index.*"%>
<%@ page import="org.apache.lucene.analysis.standard.*"%>
<%@ page import="org.apache.lucene.document.*"%>
<%@ page import="java.io.*"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>


<title>My JSP 'search_main.jsp' starting page</title>

<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">

<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<link href="../../include/style.css" rel="stylesheet" type="text/css">
<body>

<p>
<%

try{

boolean indexExist = IndexReader.indexExists("C:eclipseworkspacegyfedaWebRootbusinessdocumentmyindex");
if(indexExist)
{
String path = "C:eclipseworkspacegyfedaWebRootbusinessdocumentmyindex";
java.io.File[] entries = new java.io.File(path).listFiles();
for (int index = 0; index < entries.length; index++)
{
//System.out.print("index is Exist,need delte");
entries[index].delete();
}
response.sendRedirect("search_main.jsp");
// IndexWriter writer = new IndexWriter("C:eclipseworkspacegyfedaWebRootbusinessdocumentmyindex", new StandardAnalyzer(), true);
}
else
{

IndexWriter writer = new IndexWriter("C:eclipseworkspacegyfedaWebRootbusinessdocumentmyindex", new StandardAnalyzer(), true);


//IndexWriter writer = new IndexWriter("C:eclipseworkspacegyfedaWebRootbusinessdocumentmyindex", new StandardAnalyzer(), true);

File files = new File("C:eclipseworkspacegyfedaWebRootbusinessdocumentfiles");

String[] Fnamelist = files.list();
for (int i = 0; i < Fnamelist.length; i++){

File file = new File(files,Fnamelist[i]);

Document doc = new Document();
Field fld = Field.Text("path", file.getPath());
doc.add(fld);

fld = Field.Keyword("modified", DateField.timeToString(file.lastModified()));
doc.add(fld);

FileInputStream in = new FileInputStream(file);
Reader reader = new BufferedReader(new InputStreamReader(in));
fld = Field.Text("contents", reader);

doc.add(fld);

writer.addDocument(doc);
//System.out.println("Added : " + doc.get("path"));

}

writer.optimize();
writer.close();

// System.out.println("Has Added Total: " + Fnamelist.length);



}

}catch(Exception e){
System.out.println(e);
}
%>
</p>
<p>&nbsp; </p>
<form action="search_creat.jsp" method="post" name="form1" id="form1" >
<p></p>
<p>&nbsp; </p>

<table width="95%" height="170" border="0" align="center" cellpadding="0" cellspacing="0">
<tr>
<td><p> </p>
<p align="center">
<input type="text" name="content">
<input type="submit" name="Submit2" value="搜索">
</p></td>
</tr>
</table>
</form>
<br>
</body>
</html>



microgao - by - 16 六月, 2006 16:54

为什么用JSPWiki?

选择wiki时候的可以参考http://c2.com/cgi/wiki?ChoosingaWiki,其中包含了大部分开源的wiki产品,并且提供了各种选型参考和特性比较。

  • snipsnap
    非常强大的一个wiki+blog的工具,用java写的,支持大部分主流wiki的功能。但是存在一个小问题,就是中文支持有限,可以做中文的条目,但是一旦进入编辑这个条目的时候就出现乱码,而且乱码条目编辑之后无法保存。
  • jspwikiJava写的。支持各种插件,好的让人喜不自禁。不过缺点就是版本管理上不完善, c2说它一旦删除页面了就再也无法恢复。
  • usemodj
    c2上评论是最好用而且最好装的java wiki了,安装确实方便,但是在tomcat5.5上面无法正常运行。

    JSPWiki的安装

    Linux服务器(Fedora Core 4+J2SE 5.0 + Apache 2 + Tomcat 5.5)上的安装步骤:
    1. 下载JSPWiki的发布包
    2. 复制JSPWiki安装包到Tomcat的webapps中;
    3. 重命名JSPWiki.war为你所需要的名字,比如JackWiki.war
    4. 启动Tomcat,JSPWiki会自动部署到Web Application中
    5. 访问http://localhost:8080/JackWiki, 如果成功显示Main页面,说明安装成功;
    6. Tomcat会创建/p/web/www-data/jspwiki作为wiki的page data文件;可以把安装包中的JSPWiki-samplepages.zip解压到这个目录中;这样你就可以拥有一个缺省的Wiki了;

    JSPWiki的配置

    1. 访问http://localhost:8080/JSPWiki/Install.jsp,可以修改jspwiki中的配置信息;

    2. JSPWiki的配置在jspwiki.properties里面,下面介绍几个常用的:

    • jspwiki.pageProvider = FileSystemProvider ——页面存储方式。推荐使用VersioningFileProvider,提供简单的版本控制功能
    • jspwiki.usePageCache = true ——页面缓存,缺省值就是了
    • jspwiki.fileSystemProvider.pageDir = /p/web/www-data/jspwiki/ ——wiki内容存储的位置,也就是那一堆txt文件所在的位置
    • jspwiki.encoding = UTF-8 ——如果你想使用中文的话,那么这个配置项前面的注释必须去掉
    • jspwiki.translatorReader.allowHTML = false ——是否允许wiki里面支持html,强烈推荐不要允许,因为wiki是协同编辑的,如果有人恶意使用js的话,就惨了,呵呵。
    • jspwiki.templateDir = default ——wiki的模板。可以到jspwiki上下载模板,放到templates目录下,然后在这里改一下名字就可以了
    • jspwiki.translatorReader.inlinePattern.1 = *.jpg ——如果想让wiki支持更多的图片格式,在这儿改就是了
    • jspwiki.lucene.analyzer = org.apache.lucene.analysis.cjk.CJKAnalyzer --支持中文搜索

    3. 选择一个模板JSPWiki支持使用第三方模板替换掉发布包自带的"defalut"模板;比较流行的有MGRNexB;(我采用了NexB,这个更酷一些)

    jspwiki.applicationName = JSPWiki ——这里可以改为自己的wiki名字


  • microgao - by - 16 六月, 2006 16:39

    如何在Web页面上做到像SharePoint中的效果一样,能直接激活客户端的Word来打开.doc文件,而不是类似直接点击.doc文档链接时Word在IE中被打开那样。想想这个问题应该很多人都会感兴趣,大致描述一下方法。

    在安装Office2003以后,有一个ActiveX控件被安装到了系统中,这个控件位于“Program FilesMicrosoft OfficeOFFICE11owssupp.dll”。通过这个控件,客户端页面上的JavaScript就可以激活本地的Office软件,来实现打开、编辑Office文档。(另,Office XP应该就已经包含这个ActiveX控件了。)

    首先,用Script创建一个本地的对象:

    openDocObj = new ActiveXObject("SharePoint.OpenDocuments.2"); // 为了兼容Office XP,可以创建“SharePoint.OpenDocuments.1”

    然后,调用openDocObj的相应的方法。比如打开服务器上的一个Office文档:

    openDocObj.ViewDocument("http://www.abc.com/documents/sample.doc");

    openDocObj对象会根据参数中不同的Office文档类型(.doc、.xls、.ppt)来打开不同的程序(Word、Excel、PowerPoint)。ViewDocument()方法还有一个重载签名,可以让我们手工指定激活哪个程序来打开文档:

    openDocObj.ViewDocument("http://www.abc.com/documents/sample.doc", 要激活的程序的ProgID);

    那么要打开Office程序在线编辑文件又如何?

    openDocObj.EditDocument("http://www.abc.com/documents/sample.doc");

    就可以直接激活Word,在Word里面编辑文档,然后直接点击Word里面的保存功能,就可以将文件保存会服务器上了。注意:为了让Word能将编辑后的文档直接保存会服务器,访问Web站点的当前上下文的Windows Identity必须对服务器的相应目录(即“http://www.abc.com/documents”这个虚拟目录所对应的服务器上的物理路径)有相应的写权限,否则保存动作会失败。编辑完成后,EditDocument()会返回一个bool值,来反映编辑操作是否成功。

    我们还可以通过打开服务器上的一个文档模版,来创建一个新的文档:

    openDocObj.CreateNewDocument("http://www.abc.com/documents/sampleTemplate.dot", "http://www.abc.com/documents/");

    就可以使用“http://www.abc.com/documents/sampleTemplate.dot”这个模版来创建一个新的文档,默认新文档的保存地点是“http://www.abc.com/documents/”。创建新文档时使用的程序取决于模版文件的类型(比如.dot模版会对应Word)。新文档的保存同样需要注意权限问题。CreateNewDocument()方法同样会返回一个bool值来反映操作是否成功。

    CreateNewDocument()方法的第一个参数,除了可以使用一个模版的地址外,还可以直接指定为希望用来创建新文档的客户端程序的ProgID。

    如果服务器是windows2003则无法编辑,2000可以编辑和保存。

    参考:http://blog.joycode.com/kaneboy/archive/2004/11/03/37889.aspx



    microgao - by - 14 六月, 2006 09:19

    //**********************Created by Chen**************************
    using System;
    using System.IO;
    using System.Text;
    using System.Net;
    using System.Net.Sockets;
    using System.Collections;
    using System.Collections.Specialized;
    using KSN.Exceptions;
    using KSN.Validate;
    namespace KSN.Web.Mail
    {
    /// <summary>
    /// 邮件内容
    /// </summary>
    public class MailMessage
    {
    private string sender=null;
    private StringCollection receivers=new StringCollection();
    private string subject="";
    private string xMailer="";
    private StringCollection attachments=new StringCollection();
    private MailEncodings mailEncoding=MailEncodings.GB2312;
    private MailTypes mailType=MailTypes.Html;
    private byte[] mailBody=null;
    /// <summary>
    /// 获取或设置发件人
    /// </summary>
    public string Sender
    {
    get{return this.sender;}
    set{this.sender=value;}
    }
    /// <summary>
    /// 获取收件人地址集合
    /// </summary>
    public StringCollection Receivers
    {
    get{return this.receivers;}
    }
    /// <summary>
    /// 获取或设置邮件主题
    /// </summary>
    public string Subject
    {
    get{return this.subject;}
    set{this.subject=value;}
    }
    /// <summary>
    /// 获取或设置邮件传送者
    /// </summary>
    public string XMailer
    {
    get{return this.xMailer;}
    set{this.xMailer=value;}
    }
    /// <summary>
    /// 获取附件列表
    /// </summary>
    public StringCollection Attachments
    {
    get{return this.attachments;}
    }
    /// <summary>
    /// 获取或设置邮件的编码方式
    /// </summary>
    public MailEncodings MailEncoding
    {
    get{return this.mailEncoding;}
    set{this.mailEncoding=value;}
    }
    /// <summary>
    /// 获取或设置邮件格式
    /// </summary>
    public MailTypes MailType
    {
    get{return this.mailType;}
    set{this.mailType=value;}
    }
    /// <summary>
    /// 获取或设置邮件正文
    /// </summary>
    public byte[] MailBody
    {
    get{return this.mailBody;}
    set{this.mailBody=value;}
    }
    }
    /// <summary>
    /// 邮件编码
    /// </summary>
    public enum MailEncodings
    {
    GB2312,
    ASCII,
    Unicode,
    UTF8
    }
    /// <summary>
    /// 邮件格式
    /// </summary>
    public enum MailTypes
    {
    Html,
    Text
    }
    /// <summary>
    /// smtp服务器的验证方式
    /// </summary>
    public enum SmtpValidateTypes
    {
    /// <summary>
    /// 不需要验证
    /// </summary>
    None,
    /// <summary>
    /// 通用的auth login验证
    /// </summary>
    Login,
    /// <summary>
    /// 通用的auth plain验证
    /// </summary>
    Plain,
    /// <summary>
    /// CRAM-MD5验证
    /// </summary>
    CRAMMD5
    }
    /// <summary>
    /// 邮件发送类
    /// </summary>
    public class KSN_Smtp
    {
    #region "member fields"
    /// <summary>
    /// 连接对象
    /// </summary>
    private TcpClient tc;
    /// <summary>
    /// 网络
    /// </summary>
    private NetworkStream ns;
    /// <summary>
    /// 错误的代码字典
    /// </summary>
    private StringDictionary errorCodes=new StringDictionary();
    /// <summary>
    /// 操作执行成功后的响应代码字典
    /// </summary>
    private StringDictionary rightCodes=new StringDictionary();
    /// <summary>
    /// 执行过程中错误的消息
    /// </summary>
    private string errorMessage="";
    /// <summary>
    /// 记录操作日志
    /// </summary>
    private string logs="";
    /// <summary>
    /// 主机登陆的验证方式
    /// </summary>
    private StringCollection validateTypes=new StringCollection();
    /// <summary>
    /// 换行常数
    /// </summary>
    private const string CRLF="rn";
    private string serverName="smtp";
    private string logPath=null;
    private string userid=null;
    private string password=null;
    private string mailEncodingName="GB2312";
    private bool sendIsComplete=false;
    private SmtpValidateTypes smtpValidateType=SmtpValidateTypes.Login;
    #endregion
    #region "propertys"
    /// <summary>
    /// 获取最后一此程序执行中的错误消息
    /// </summary>
    public string ErrorMessage
    {
    get{return this.errorMessage;}
    }
    /// <summary>
    /// 获取或设置日志输出路径
    /// </summary>
    public string LogPath
    {
    get
    {
    return this.logPath;
    }
    set{this.logPath=value;}
    }
    /// <summary>
    /// 获取或设置登陆smtp服务器的帐号
    /// </summary>
    public string UserID
    {
    get{return this.userid;}
    set{this.userid=value;}
    }
    /// <summary>
    /// 获取或设置登陆smtp服务器的密码
    /// </summary>
    public string Password
    {
    get{return this.password;}
    set{this.password=value;}
    }
    /// <summary>
    /// 获取或设置要使用登陆Smtp服务器的验证方式
    /// </summary>
    public SmtpValidateTypes SmtpValidateType
    {
    get{return this.smtpValidateType;}
    set{this.smtpValidateType=value;}
    }
    #endregion
    #region "construct functions"
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="server">主机名</param>
    /// <param name="port">端口</param>
    public KSN_Smtp(string server,int port)
    {
    tc=new TcpClient(server,port);
    ns=tc.GetStream();
    this.serverName=server;
    this.initialFields();
    }
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="ip">主机ip</param>
    /// <param name="port">端口</param>
    public KSN_Smtp(IPAddress ip,int port)
    {
    IPEndPoint endPoint=new IPEndPoint(ip,port);
    tc=new TcpClient(endPoint);
    ns=tc.GetStream();
    this.serverName=ip.ToString();
    this.initialFields();
    }
    #endregion
    #region "methods"
    private void initialFields() //初始化连接
    {
    logs="================"+DateTime.Now.ToLongDateString()+" "+DateTime.Now.ToLongTimeString()+"==============="+CRLF;
    //*****************************************************************
    //错误的状态码
    //*****************************************************************
    errorCodes.Add("421","服务未就绪,关闭传输通道");
    errorCodes.Add("432","需要一个密码转换");
    errorCodes.Add("450","要求的邮件操作未完成,邮箱不可用(如:邮箱忙)");
    errorCodes.Add("451","放弃要求的操作,要求的操作未执行");
    errorCodes.Add("452","系统存储不足,要求的操作未完成");
    errorCodes.Add("454","临时的认证失败");
    errorCodes.Add("500","邮箱地址错误");
    errorCodes.Add("501","参数格式错误");
    errorCodes.Add("502","命令不可实现");
    errorCodes.Add("503","命令的次序不正确");
    errorCodes.Add("504","命令参数不可实现");
    errorCodes.Add("530","需要认证");
    errorCodes.Add("534","认证机制过于简单");
    errorCodes.Add("538","当前请求的认证机制需要加密");
    errorCodes.Add("550","当前的邮件操作未完成,邮箱不可用(如:邮箱未找到或邮箱不能用)");
    errorCodes.Add("551","用户非本地,请尝试<forward-path>");
    errorCodes.Add("552","过量的存储分配,制定的操作未完成");
    errorCodes.Add("553","邮箱名不可用,如:邮箱地址的格式错误");
    errorCodes.Add("554","传送失败");
    errorCodes.Add("535","用户身份验证失败");
    //****************************************************************
    //操作执行成功后的状态码
    //****************************************************************
    rightCodes.Add("220","服务就绪");
    rightCodes.Add("221","服务关闭传输通道");
    rightCodes.Add("235","验证成功");
    rightCodes.Add("250","要求的邮件操作完成");
    rightCodes.Add("251","非本地用户,将转发向<forward-path>");
    rightCodes.Add("334","服务器响应验证Base64字符串");
    rightCodes.Add("354","开始邮件输入,以<CRLF>.<CRLF>结束");
    //读取系统回应
    StreamReader reader=new StreamReader(ns);
    logs+=reader.ReadLine()+CRLF;
    }
    /// <summary>
    /// 向SMTP发送命令
    /// </summary>
    /// <param name="cmd"></param>
    private string sendCommand(string cmd,bool isMailData)
    {
    if(cmd!=null && cmd.Trim()!=string.Empty)
    {
    byte[] cmd_b=null;
    if(!isMailData)//不是邮件数据
    cmd+=CRLF;

    logs+=cmd;
    //开始写入邮件数据
    if(!isMailData)
    {
    cmd_b=Encoding.ASCII.GetBytes(cmd);
    ns.Write(cmd_b,0,cmd_b.Length);
    }
    else
    {
    cmd_b=Encoding.GetEncoding(this.mailEncodingName).GetBytes(cmd);
    ns.BeginWrite(cmd_b,0,cmd_b.Length,new AsyncCallback(this.asyncCallBack),null);
    }
    //读取服务器响应
    StreamReader reader=new StreamReader(ns);
    string response=reader.ReadLine();
    logs+=response+CRLF;
    //检查状态码
    string statusCode=response.Substring(0,3);
    bool isExist=false;
    bool isRightCode=true;
    foreach(string err in this.errorCodes.Keys)
    {
    if(statusCode==err)
    {
    isExist=true;
    isRightCode=false;
    break;
    }
    }
    foreach(string right in this.rightCodes.Keys)
    {
    if(statusCode==right)
    {
    isExist=true;
    break;
    }
    }
    //根据状态码来处理下一步的动作
    if(!isExist) //不是合法的SMTP主机
    {
    this.setError("不是合法的SMTP主机,或服务器拒绝服务");
    }
    else if(!isRightCode)//命令没能成功执行
    {
    this.setError(statusCode+":"+this.errorCodes[statusCode]);
    }
    else //命令成功执行
    {
    this.errorMessage="";
    }
    return response;
    }
    else
    {
    return null;
    }
    }
    /// <summary>
    /// 通过auth login方式登陆smtp服务器
    /// </summary>
    private void landingByLogin()
    {
    string base64UserId=this.convertBase64String(this.UserID,"ASCII");
    string base64Pass=this.convertBase64String(this.Password,"ASCII");
    //握手
    this.sendCommand("helo "+this.serverName,false);
    //开始登陆
    this.sendCommand("auth login",false);
    this.sendCommand(base64UserId,false);
    this.sendCommand(base64Pass,false);
    }
    /// <summary>
    /// 通过auth plain方式登陆服务器
    /// </summary>
    private void landingByPlain()
    {
    string NULL=((char)0).ToString();
    string loginStr=NULL+this.UserID+NULL+this.Password;
    string base64LoginStr=this.convertBase64String(loginStr,"ASCII");
    //握手
    this.sendCommand("helo "+this.serverName,false);
    //登陆
    this.sendCommand(base64LoginStr,false);
    }
    /// <summary>
    /// 通过auth CRAM-MD5方式登陆
    /// </summary>
    private void landingByCRAMMD5()
    {
    //握手
    this.sendCommand("helo "+this.serverName,false);
    //登陆
    string response=this.sendCommand("auth CRAM-MD5",false);
    //得到服务器返回的标识
    string identifier=response.Remove(0,4);
    //用MD5加密标识
    KSN_MACTripleDES mac=new KSN_MACTripleDES();
    mac.Key=this.Password;
    mac.Data=Encoding.ASCII.GetBytes(identifier);
    string hash=mac.GetHashValue();
    //发送用户帐号信息
    this.sendCommand(this.UserID+" "+hash,false);
    }
    /// <summary>
    /// 发送邮件
    /// </summary>
    /// <returns></returns>
    public bool SendMail(MailMessage mail)
    {
    bool isSended=true;
    try
    {
    //检测发送邮件的必要条件
    if(mail.Sender==null)
    {
    this.setError("没有设置发信人");
    }
    if(mail.Receivers.Count==0)
    {
    this.setError("至少要有一个收件人");
    }
    if(this.SmtpValidateType!=SmtpValidateTypes.None)
    {
    if(this.userid==null || this.password==null)
    {
    this.setError("当前设置需要smtp验证,但是没有给出登陆帐号");
    }
    }
    //开始登陆
    switch(this.SmtpValidateType)
    {
    case SmtpValidateTypes.None:
    this.sendCommand("helo "+this.serverName,false);
    break;
    case SmtpValidateTypes.Login:
    this.landingByLogin();
    break;
    case SmtpValidateTypes.Plain:
    this.landingByPlain();
    break;
    case SmtpValidateTypes.CRAMMD5:
    this.landingByCRAMMD5();
    break;
    default:
    break;
    }
    //初始化邮件会话(对应SMTP命令mail)
    this.sendCommand("mail from:<"+mail.Sender+">",false);
    //标识收件人(对应SMTP命令Rcpt)
    foreach(string receive in mail.Receivers)
    {
    this.sendCommand("rcpt to:<"+receive+">",false);
    }
    //标识开始输入邮件内容(Data)
    this.sendCommand("data",false);
    //开始编写邮件内容
    string message="Subject:"+mail.Subject+CRLF;
    message+="X-mailer:"+mail.XMailer+CRLF;
    message+="MIME-Version:1.0"+CRLF;
    if(mail.Attachments.Count==0)//没有附件
    {
    if(mail.MailType==MailTypes.Text) //文本格式
    {
    message+="Content-Type:text/plain;"+CRLF+" ".PadRight(8,' ')+"charset=""+
    mail.MailEncoding.ToString()+"""+CRLF;
    message+="Content-Transfer-Encoding:base64"+CRLF+CRLF;
    if(mail.MailBody!=null)
    message+=Convert.ToBase64String(mail.MailBody,0,mail.MailBody.Length)+CRLF+CRLF+CRLF+"."+CRLF;
    }
    else//Html格式
    {
    message+="Content-Type:multipart/alertnative;"+CRLF+" ".PadRight(8,' ')+"boundary"
    +"="=====003_Dragon310083331177_=====""+CRLF+CRLF+CRLF;
    message+="This is a multi-part message in MIME format"+CRLF+CRLF;
    message+="--=====003_Dragon310083331177_====="+CRLF;
    message+="Content-Type:text/html;"+CRLF+" ".PadRight(8,' ')+"charset=""+
    mail.MailEncoding.ToString().ToLower()+"""+CRLF;
    message+="Content-Transfer-Encoding:base64"+CRLF+CRLF;
    if(mail.MailBody!=null)
    message+=Convert.ToBase64String(mail.MailBody,0,mail.MailBody.Length)+CRLF+CRLF;
    message+="--=====003_Dragon310083331177_=====--"+CRLF+CRLF+CRLF+"."+CRLF;
    }
    }
    else//有附件
    {
    //处理要在邮件中显示的每个附件的数据
    StringCollection attatchmentDatas=new StringCollection();
    foreach(string path in mail.Attachments)
    {
    if(!File.Exists(path))
    {
    this.setError("指定的附件没有找到"+path);
    }
    else
    {
    //得到附件的字节流
    FileInfo file=new FileInfo(path);
    FileStream fs=new FileStream(path,FileMode.Open,FileAccess.Read);
    if(fs.Length>(long)int.MaxValue)
    {
    this.setError("附件的大小超出了最大限制");
    }
    byte[] file_b=new byte[(int)fs.Length];
    fs.Read(file_b,0,file_b.Length);
    fs.Close();
    string attatchmentMailStr="Content-Type:application/octet-stream;"+CRLF+" ".PadRight(8,' ')+"name="+
    """+file.Name+"""+CRLF;
    attatchmentMailStr+="Content-Transfer-Encoding:base64"+CRLF;
    attatchmentMailStr+="Content-Disposition:attachment;"+CRLF+" ".PadRight(8,' ')+"filename="+
    """+file.Name+"""+CRLF+CRLF;
    attatchmentMailStr+=Convert.ToBase64String(file_b,0,file_b.Length)+CRLF+CRLF;
    attatchmentDatas.Add(attatchmentMailStr);
    }
    }
    //设置邮件信息
    if(mail.MailType==MailTypes.Text) //文本格式
    {
    message+="Content-Type:multipart/mixed;"+CRLF+" ".PadRight(8,' ')+"boundary="=====001_Dragon320037612222_=====""
    +CRLF+CRLF;
    message+="This is a multi-part message in MIME format."+CRLF+CRLF;
    message+="--=====001_Dragon320037612222_====="+CRLF;
    message+="Content-Type:text/plain;"+CRLF+" ".PadRight(8,' ')+"charset=""+mail.MailEncoding.ToString().ToLower()+"""+CRLF;
    message+="Content-Transfer-Encoding:base64"+CRLF+CRLF;
    if(mail.MailBody!=null)
    message+=Convert.ToBase64String(mail.MailBody,0,mail.MailBody.Length)+CRLF;
    foreach(string s in attatchmentDatas)
    {
    message+="--=====001_Dragon320037612222_====="+CRLF+s+CRLF+CRLF;
    }
    message+="--=====001_Dragon320037612222_=====--"+CRLF+CRLF+CRLF+"."+CRLF;
    }
    else
    {
    message+="Content-Type:multipart/mixed;"+CRLF+" ".PadRight(8,' ')+"boundary="=====001_Dragon255511664284_=====""
    +CRLF+CRLF;
    message+="This is a multi-part message in MIME format."+CRLF+CRLF;
    message+="--=====001_Dragon255511664284_====="+CRLF;
    message+="Content-Type:text/html;"+CRLF+" ".PadRight(8,' ')+"charset=""+mail.MailEncoding.ToString().ToLower()+"""+CRLF;
    message+="Content-Transfer-Encoding:base64"+CRLF+CRLF;
    if(mail.MailBody!=null)
    message+=Convert.ToBase64String(mail.MailBody,0,mail.MailBody.Length)+CRLF+CRLF;
    for(int i=0;i<attatchmentDatas.Count;i++)
    {
    message+="--=====001_Dragon255511664284_====="+CRLF+attatchmentDatas[i]+CRLF+CRLF;
    }
    message+="--=====001_Dragon255511664284_=====--"+CRLF+CRLF+CRLF+"."+CRLF;
    }
    }
    //发送邮件数据
    this.mailEncodingName=mail.MailEncoding.ToString();
    this.sendCommand(message,true);
    if(this.sendIsComplete)
    this.sendCommand("QUIT",false);
    }
    catch
    {
    isSended=false;
    }
    finally
    {
    this.disconnect();
    //输出日志文件
    if(this.LogPath!=null)
    {
    FileStream fs=null;
    if(File.Exists(this.LogPath))
    {
    fs=new FileStream(this.LogPath,FileMode.Append,FileAccess.Write);
    this.logs=CRLF+CRLF+this.logs;
    }
    else
    fs=new FileStream(this.LogPath,FileMode.Create,FileAccess.Write);
    byte[] logPath_b=Encoding.GetEncoding("gb2312").GetBytes(this.logs);
    fs.Write(logPath_b,0,logPath_b.Length);
    fs.Close();
    }
    }
    return isSended;
    }
    /// <summary>
    /// 异步写入数据
    /// </summary>
    /// <param name="result"></param>
    private void asyncCallBack(IAsyncResult result)
    {
    if(result.IsCompleted)
    this.sendIsComplete=true;
    }
    /// <summary>
    /// 关闭连接
    /// </summary>
    private void disconnect()
    {
    try
    {
    ns.Close();
    tc.Close();
    }
    catch
    {
    ;
    }
    }
    /// <summary>
    /// 设置出现错误时的动作
    /// </summary>
    /// <param name="errorStr"></param>
    private void setError(string errorStr)
    {
    this.errorMessage=errorStr;
    logs+=this.errorMessage+CRLF+"【邮件处理动作中止】"+CRLF;
    this.disconnect();
    throw new ApplicationException("");
    }
    /// <summary>
    ///将字符串转换为base64
    /// </summary>
    /// <param name="str"></param>
    /// <param name="encodingName"></param>
    /// <returns></returns>
    private string convertBase64String(string str,string encodingName)
    {
    byte[] str_b=Encoding.GetEncoding(encodingName).GetBytes(str);
    return Convert.ToBase64String(str_b,0,str_b.Length);
    }
    #endregion
    }
    }



    microgao - by - 08 六月, 2006 14:25

    1 0×00000001 不正确的函数。
    2 0×00000002 系统找不到指定的档案。
    3 0×00000003 系统找不到指定的路径。
    4 0×00000004 系统无法开启档案。
    5 0×00000005 拒绝存取。
    6 0×00000006 无效的代码。
    7 0×00000007 储存体控制区块已毁。
    8 0×00000008 储存体空间不足,无法处理这个指令。
    9 0×00000009 储存体控制区块地址无效。
    10 0×0000000A 环境不正确。
    11 0×0000000B 尝试加载一个格式错误的程序。
    12 0×0000000C 存取码错误。
    13 0×0000000D 资料错误。
    14 0×0000000E 储存体空间不够,无法完成这项作业。
    15 0×0000000F 系统找不到指定的磁盘驱动器。
    16 0×00000010 无法移除目录。
    16 0×00000010 无法移除目录。
    17 0×00000011 系统无法将档案移到 其它的磁盘驱动器。
    18 0×00000012 没有任何档案。
    19 0×00000013 储存媒体为写保护状态。
    20 0×00000014 系统找不到指定的装置。
    21 0×00000015 装置尚未就绪。
    22 0×00000016 装置无法识别指令。
    23 0×00000017 资料错误 (cyclic redundancy check)
    24 0×00000018 程序发出一个长度错误的指令。
    25 0×00000019 磁盘驱动器在磁盘找不到 持定的扇区或磁道。
    26 0×0000001A 指定的磁盘或磁盘无法存取。
    27 0×0000001B 磁盘驱动器找不到要求的扇区。
    28 0×0000001C 打印机没有纸。
    29 0×0000001D 系统无法将资料写入指定的磁盘驱动器。
    30 0×0000001E 系统无法读取指定的装置。
    31 0×0000001F 连接到系统的某个装置没有作用。
    32 0×00000020 The process cannot acce