villa123/
共1628个网摘 [
1 2 3 4 5 6 ...
55 ]
下一页 |
访问villa123的个人空间
villa123收录,使用标签:Java,时间:2008-10-10 13:59:11 | 相关网摘,我也收藏
mengweilil:MyEclipse
优点:因为专心J2EE方面的项目,所以不需要Swing支持。MyEclipse在UML、Struts、Hibernate方面都还可以,而且也不是很贵,万一有朝一日不得不正版的时候,也还买得起。
缺点:DB管理方面,实体类映射方面,PowerDesigner的地位始终无法替代。其他很多支持UML的工具,但都要收费。
Landor2004:
eclipse:现在的eclipse也相当好用,附带的插件也已经很齐全,我们主要适用这个
netbean:我比较喜欢netbean的代码辅助能力,从后台代码,到前台表单提示,应有尽有;我最喜欢的还是or映射的自动生成,我一般都是用netbean来生成映射,再拷贝到eclipse里,虽然有些麻烦,不过习惯了!
joejoe1991:jbuilder 2006
原因是代码提示功能强.很人性化.准确地说是很符合我的习惯.无论是从界面风格还是字体颜色,都是我比较喜欢的那种,各种功能集成的也不错.用着方便.
第一眼见eclipse就觉得太难看了.而且代码提示功能太烂,一般我觉得不好看的软件我都不用..
microns:JDeveloper
集成的东西很多,用起来比较方便,因为我一般都是使用oracle数据
用ORACLE的JDeveloper更方便,不用像eclipse安装那么多插件,麻烦!
主要原因是学校和oracle公司有合作,捐赠了不少软件,所以我们学校都选的ORACLE的开发套件
todaydiy:
初学java的时候用的是jCreator,感觉蛮好的,很轻便。但是设计swing的时候,没有设计视图,所以一切都要靠手写代码来确定控件的位置。
再后来用的是Jbuilder,这个就可以很方便的开发j2ee了,但占用系统资源太多,并且那个时候害怕在里面打中文,一打中文就关了。好像可以设置的。
现在一直用的是myeclipse,比起前面的两种IDE方便多了,就是开发ejb有点麻烦。
……
http://topic.csdn.net/u/20081004/22/cd3980b0-8e57-40bf-92dc-22f65a491ab4.html
http://topic.csdn.net/u/20081004/22/cd3980b0-8e57-40bf-92dc-22f65a491ab4.html
villa123收录,使用标签:开源,时间:2008-10-10 10:10:35 | 相关网摘,我也收藏
OpenOffice原是Sun公司的一套商业级Office软件:StarOffice,经过Sun公司公开程序码之后,正式命名为 OpenOffice发展计划,并由许许多多热心于自由软件的人士共同来维持。让大家能在MS OFFICE之外,还能有免费的Office可以使用。OpenOffice 是个整合性的软件,里面包含了许许多多的工具,其功能绝对不比微软的MS Office还差,不但可以有Word一样的字处理,制作简单的图形,更有功能强大的图表功能,也能编写网页,还可以做出MS Office中很难处理的数学符号等等,支持了XML、微软的doc、Excel、ppt文件等格式。
下载:OpenOffice.org 3.0.0 Final for Windows(128 MB)
http://ftp.stardiv.de/pub/OpenOffice.org/stable/3.0.0/OOo_3.0.0_Win32Intel_install_en-US.exe
下载:OpenOffice.org 3.0.0 Final for Linux
http://ftp.stardiv.de/pub/OpenOffice.org/stable/3.0.0/OOo_3.0.0_LinuxIntel_install_en-US.tar.gz
http://www.cnbeta.com/articles/66645.htm
villa123收录,使用标签:项目管理,时间:2008-10-9 17:20:29 | 相关网摘,我也收藏
和 GTK、QT、SWT、SWING 一样,wxWidgets 也是一种开源跨平台的 GUI 框架。因 wxWidgets 和 MFC 有些渊源,有些亲近,故要体验一番 wxWidgets。现在最新版是 2.8.7。wxWidgets 程序还可运行于 Windows CE、Palm OS。不但包括 GUI,还有 Media、Socket、ODBC 等库。
详情请见 wxWidgets 的官方网站是:http://www.wxwidgets.org
本篇介绍 VC++6.0 下如何搭建 wxWidgets 的开发环境,包括编译 wxWidgets 源码和配置 VC 环境。所用 wxWidgets 的版本是 2.8.7。
第一步:下载并安装 wxWidgets
在 http://www.wxwidgets.org/downloads/ 下载到 wxWidgets 源码。
你可以下载 Windows 安装版 http://prdownloads.sourceforge.net/wxwindows/wxMSW-2.8.7-Setup.exe (11.9M)
或者是 Zip 压缩版 http://prdownloads.sourceforge.net/wxwindows/wxMSW-2.8.7.zip (15.9M)
假如我们下载的是 wxMSW-2.8.7-Setup.exe,就可运行它,过程中我们假定选择的安装目录是 d:\wxWidgets-2.8.7。 完成后,我们看到安装目录所在空间的大不是 127M。
注意要给 wxWidgets 有足够的剩余空间,单是编译一个版本的静态库就会增至300多M,如果还要编译动态库再选择上 DEBUG 或者 RELEASE、ASCII 或是 UNICODE 等,还要编译 sample 的话,最后空间能够暴到 2G。
第二步:编译 wxWidgets
编译方式有两种,一种是在 VC++ 中打开 wx.dsw 进行编译,另一种是用 nmake 在命令行下编译。
1) nmake 编译
运行 cmd.exe 来到命令行下,进入到目录 d:\wxWidgets-2.8.7,然后执行
D:\wxWidgets-2.8.7>nmake -f makefile.vc
默认是编译成 DEBUG 版的静态库。也许我们在编译之前需要根据自己的需要进行一些设置:
打开 D:\wxWidgets-2.8.7\include\wx\msw\setup.h
保证第 110 行的 #define wxUSE_DEBUG_NEW_ALWAYS 设置为 0。默认是 0 的。
将 1137 行的 #define wxUSE_IOSTREAMH 设置为 0,否则无法使用 VC 的模板库(不能用 std 命名空间下的)。默认是 1。
如果要使用 wxWidgets 提供支持数据库的 ODBC 类,将 #define wxUSE_ODBC 设为 1。默认是 1。
如果你想生成动态库,或生成的是 Release 版的,你可以修改 D:\wxWidgets-2.8.7\build\msw\config.vc 文件。打开该文件你能看到可以更改的选项,其中重要的几个如下:
# What type of library to build? [0,1] -- 生成库的类型,0为静态库,1为动态库
SHARED = 0
# Compile Unicode build of wxWidgets? [0,1] -- 是否使和 UNICODE,0 否,1 是
UNICODE = 0
# Type of compiled binaries [debug,release] -- 是 debug 还是 release
BUILD = debug
其中还有许多顾名思义的选项,请根据需求修改。
对于 config.vc 中的选项也可以通过命令行覆盖掉配置文件中的值,如编译命令用
D:\wxWidgets-2.8.7>nmake -f makefile.vc SHARED=1 UNICODE=1 BUILD=release
就是编译 release 版,支持 UNICODE 的动态库了。
动态库和静态库的不同体现在使用上,静态库中的代码能编译进应用程序的 exe 文件中,这个 exe 文件可单独发布,但体积较庞大。动态库可让 exe 文件执行时加载,虽然 exe 文件体积小了,但发布时必须带着相应的动态库文件。
2) VC 编译
还可用 VC++ 打开 D:\wxWidgets-2.8.7\build\msw\wx.dsw,然后选择 Build->Batch Build... ,我们看到每个工程都有 16 个配置,请根据应用需求勾选你的配置,如所有工程的 Win32 Release 和 Win32 Debug 版本,然后 Build,如果你照单全收,编译将非常耗时而占空间。这种编译方式就是可以一下操作帮你编译出所有需要的版本。
好,等等编译完之后,在目录 D:\wxWidgets-2.8.7\lib\ 会有产生一个目录:vc_lib 是静态库的目录,默认编译有 17 个静态。如果是编译成动态库则会产生目录 vc_dll。在 vc_lib 中有 mswd 目录,release 版对应的是 msw,里面含有 Debug 和 Release 版的 setup.h。
注意到 Debug 版和 Release 以及和 UNICODE 、动态库、静态库组合条件编译生成的文件和目录名。
静态库编译到 D:\wxWidgets-2.8.7\lib\vc_lib 目录中
动态库编译到 D:\wxWidgets-2.8.7\lib\vc_dll 目录中
文件命名为 wxmsw28d_core.lib 文件为例:
wxmsw28_core.lib ---- Release,非 UNICODE 版
wxmsw28d_core.lib ---- Debug,非 UNICODE 版
wxmsw28u_core.lib ---- Release,UNICODE 版
wxmsw28ud_core.lib ---- Debug,UNICODE 版
对于 dll 文件的命名规范也一样的。
在 vc_lib 和 vc_dll 下如果全编译了会有四个目录,msw、mswd、mswu、mswud 意义同上。
编译完后可以把那些过程中的目标文件删了,它们占的空间实在是太大。要是你还想以后重新编译用就留着吧。
第三步:创建 wxWidgets 项目
创建一个 Win32 Application 的空项目,项目名为 wxHello
Proejct->Settings (Alt+F7) 进入项目设置
1. C/C++选项卡->Code Generation -> use run-time library 设置为: Debug MutilThread DLL
2. C/C++选项卡-> PreProcessor -> PreProcessor Definitions 设置加上 __WXMSW__,__WXDEBUG__ ,如果是 Release 版,则只需加上 __WXMSW__。
3. C/C++选项卡-> PreProcessor -> Additional include directories 设置为 D:\wxWidgets-2.8.7\include,D:\wxWidgets-2.8.7\include\lib\vc_lib\mswd, 这是设置附加 Include 路径。
4. Link 选项卡-> Input -> Object/library modules 设置加上 wxmsw28d_core.lib wxbase28d.lib wxtiffd.lib wxjpegd.lib wxpngd.lib wxzlibd.lib wxregexd.lib wxexpatd.lib 这些 Lib(没有这四个 lib comctl32.lib rpcrt4.lib winmm.lib wsock32.lib 的话请加上,关键是前两个)。如果要使用 wxODBC 就加上wxbase28d_odbc.lib。
5. Link 选项卡-> Input -> Addtional library path 设置为: D:\wxWidgets-2.8.7\lib\vc_lib。
注意:编译成不同版的执行代码应填入相应的 Additional include directories 和 Addtional library path。
编译成依赖 DLL 的执行文件还需要加上预处理指令 WXUSINGDLL
用UNICODE 编译则需要加上预处理指令 _UNICODE
还有一种 Universal 编译,要加上预处理指令 __WXUNIVERSAL__
这样一个普通的WxWdigets项目就设置好了.
我们测试 WxWidgets 自带的例子 minimal。新建一个 minimal.cpp 文件,内容同 D:\wxWidgets-2.8.7\samples\minimal\minimal.cpp,编译,不出意外,成功了!
也可设置全局的目录
前面我们是为项目配置 Additional Include Directories 和 Additional library path。我们可以把这设置成全局的。就可以不用在项目中单独设置。全局设置有其缺陷,无法分开是 vc_lib 还是 vc_dll 中的 lib,是 mswd 还是 msw 中的 wx/setup.h 文件。在这里设置 Source Files 还是必要的。并且在下面前两个 Include Files 放在全局设置里也较合适。
进到 VC++ 的 Tools->Options->Directories,选择 "Include Files" 后,添加
D:\wxWidgets-2.8.7\include
D:\wxWidgets-2.8.7\contrib\include
D:\wxWidgets-2.8.7\lib\vc_lib\mswd
对应于 Project Settings -> C/C++ Tab->Preprocessor 中的 Additional include directories
再选择 "Libraries Files",添加
D:\wxWidgets-2.8.7\vc_lib
对应于 Project Settings -> Link Tab -> Input 中的 Additional library path
再选择 "Source Files",添加
D:\wxWidgets-2.8.7\src
最后大致比较一下生成程序的大小
Release 静态库版(vc_msw) minimal.exe 1.11M,可单独发布
Debug 静态库版(vc_mswd) minimal.exe 2.52M,可单独发布
Release 动态库版(vc_mswdll) minimal.exe 68K,加上wxmsw28_core_vc_custom.dll (2.85M) 和 wxbase28_vc_custom.dll (1.11M),共 4M
Debug 动态库版(vc_mswddll) minimal.exe 96K,加上wxmsw28d_core_vc_custom.dll (4.26M) 和 wxbase28d_vc_custom.dll (1.82M),共 6.1M
这是一个简单的 wxWidgets 程序,只用到了两个动态库的情况。在用到 wxWidgets 库较少时用静态库来编译发布文件会小一些,若是用到组件多的时候,可能用动态库方便些,请酌情决定。
参考:1. C++开源跨平台类库集
2. wxWidgets-2.8.3编译和在VC中的配置
3. vc++6.0下wxWidgets程序开发环境的设置
4. 在Visual C++ 6 中配置wxWidgets 项目
5. 如何调用DLL (基于Visual C++6.0的DLL编程实现)
http://blog.csdn.net/kypfos/archive/2008/10/08/3030951.aspx
villa123收录,使用标签:.NET,时间:2008-10-9 17:19:13 | 相关网摘,我也收藏
原文地址:http://www.west-wind.com/WebLog/posts/127340.aspx
[译者改后源码下载]
原文发布日期:2007.08.02
作者:Rick Strahl
翻译:webabcd
介绍
今天,我花了几个小时的时间研究了一下ASP.NET 3.5中的ListView控件和DataPager控件。这两个控件是ASP.NET中新增的、非常受欢迎的控件。 ListView控件集成了DataGrid、DataList、Repeater和GridView控件的所有功能。它可以像Repeater控件那样,让我们在控件内写任何HTML代码。
可以说,ListView就是DataGrid和 Repeater的结合体,它既有Repeater控件的开放式模板,又具有DataGrid控件的编辑特性。这绝对是一个可以引起你兴趣的好东东,因为它给你提供了比DataGird丰富得多的布局手段,同时又具有DataGrid的所有特性。 ListView控件本身并不提供分页功能,但是我们可以通过另一个控件 – DataPager来实现分页的特性。把分页的特性单独放到另一个控件里,会给我们带来很多好处,比如说可以让别的控件使用它,又比如说我们可以把它放在页面的任何地方。实质上,DataPager就是一个扩展ListView分页功能的控件。
ListView控件
ListView是用来显示数据的,它的使用类似于Repeater控件。 ListView控件中有n多模板,出示如下:
·LayoutTemplate
·ItemTemplate
·AlternatingItemTemplate
·SelectedItemTemplate
·EmptyItemTemplate
·EmptyDataTemplate
·ItemSeparatorTemplate
·GroupTemplate
·GroupSeparatorTemplate
·EditItemTemplate
·InsertItemTemplate
它有很多的模板。 其中有许多新增的模板,如GroupTemplate和InsertItemTemplate。现在我们可能还无法了解GroupTemplate是如何工作的(后面会有介绍),但是对于InsertItemTemplate来说,一看就知道它是用于添加记录的(在之前的DataGird中是没有这个模板的)。
继续摸索这个控件后,我发现它可以让你在它的模板内写任何HTML标记或控件,这将给我们带来很大的自由度。
用ListView显示数据
开始,你可以把ListView当作是Repeater来使用,也就是说它是模板驱动型的控件,其中的LayoutTemplate是ListView的一个布局模板。 参考如下示例:
[复制到剪贴板]
CODE:
<%# Eval("Sku") %>
<%# Eval("Abstract") %>
LayoutTemplate 用来决定包裹着详细内容的容器的标记。 你可以在布局模板内放置任何控件,不过它必须要是服务端控件(runat=”server”)。另外,你还需要指定ListView控件的ItemContainerID属性,它用来告知ListView在哪个容器下显示详细内容。在上面的例子中,LayoutTemplate其实并没有起到什么作用,因为它只是将ListView显示的详细内容放到了一个标记下而已。 但是,我们也可以用它来显示复杂的布局,如。 请看下面的例子,它就是用来做ListView显示的详细内容的容器的,并且它还有一个固定表头的功能。
[复制到剪贴板]
CODE:
SkuAbstract
<%# Eval("Sku") %>
<%# Eval("Abstract") %>
请注意一下上面的布局模板,特别是其中的部分。 ItemTemplate会将其内生成的详细内容插入到之中。
增加分页功能
如果你想为ListView增加分页功能的话,那么就需要使用DataPager控件了。这个分页控件是一个独立的控件,你可以把它放到页面的任何位置,然后使其联到你的ListView控件就可以完成分页的工作了。该分页控件所呈现出来的HTML标记为内联(Inline)元素,所以如果你想精确地设置其位置的话,可以参考下面的代码,为其包裹一个标记。
你可以像下面这样设置分页控件,并可以把其放到页面的任何位置。
[复制到剪贴板]
CODE:
通过上面的代码你会发现,我们可以通过设置DataPager控件的Fields,从而达到手动设置分页布局的目的。 另外还有一个关键点,就是DataPager控件的PagedControlID属性,你需要把它设置为ListView的ID。
当然你也可以把DataPager控件放到布局模板内。
把分页功能作为一个单独的控件分离出来是一个非常好的注意 – 它会让我们有更多的布局和 显示上的自由度。 但是,目前的分页控件还是有其局限性的。它只能结合ListView控件一起工作 – 如果能用在Repeater或GridView上就更好了。另外,它也是要依赖于ViewState的。
还有,现在的DataPager控件没有分页事件,也没有SelectedPageIndex属性。
还有一点需要注意的是,ListView没有内置排序功能。
在ListView中添加和编辑数据
ListView通过EditItemTemplate和InsertItemTemplate来提供编辑数据和添加数据的功能。 这个功能的使用非常类似于GridView的编辑特性的使用,只不过它用的都是自定义模板。
[复制到剪贴板]
CODE:
<%# Eval("Sku") %>
<%# Eval("Abstract") %>
<%# Eval("Sku") %>
<%# Eval("Abstract") %>
Sku:
Abstract:
在本例中我使用的是SqlDataSource(我比较懒),SqlDataSource中的Insert和Update语句是你必须要提供的。 InsertItemTemplate是ListView中新增的非常受欢迎的模板,我们可以把它的UI设置成与编辑模板相一致。我们还可以通过InsertItemPosition属性来指定插入模板的位置,它可以是FirstItem、LastItem或None。一般来说,应该把它设置为None,然后通过某个按钮来设置插入模板的显示位置(FirstItem或LastItem)。示例代码如下:
[复制到剪贴板]
CODE:
protected void btnAddItem_Click(object sender, EventArgs e)
{
this.lvItems.InsertItemPosition = InsertItemPosition.FirstItem;
}
protected void lvItems_ItemCommand(object sender, ListViewCommandEventArgs e)
{
if (e.CommandName == "Update")
{
TextBox tb = e.Item.FindControl("txtSku") as TextBox;
this.lvItems.InsertItemPosition = InsertItemPosition.None;
Response.Write(tb.Text);
}
if (e.CommandName == "Cancel")
{
this.lvItems.InsertItemPosition = InsertItemPosition.None;
}
}
你可以在OnItemCommand中写上自己的逻辑,使得一旦执行了Update或Cancel命令就设置InsertItemPosition为None。
分组
大概介绍一下,最后生成的HTML代码会先用GroupTemplate分组,然后再以LayoutTemplate做容器包裹起来。
Group Header:
你可以设置ListView的GroupItemCount属性,来指定每组显示多少条记录。
总结
ListView 是ASP.NET中新增的一个非常酷的控件。在本文中我已经介绍过了,相对于GridView来说它有着更为丰富的布局手段,你可以在它的模板内写任何HTML标记或者控件。如果你使用过Repeater和GridView的话,那么你将会轻松的上手ListView,不过很明显地,你也将要手写更多的HTML标记。但是,它也将会给我们带来更多的布局上的自由度,同时也具有编辑、插入等特性。 这就是ASP.NET 3.5给我们带来的非常棒的控件。
http://blog.csdn.net/skyaspnet/archive/2008/10/07/3029424.aspx
villa123收录,使用标签:中间件,时间:2008-10-9 17:16:48 | 相关网摘,我也收藏
前几天才刚闭幕的 Oracle OpenWorld 盛会中,Fusion Middleware 融合中间件产品部门的老大 -- 全球资深副总 Thomas Kurian 在 keynote 演讲中,突出一个重点 -- 在完成整并 BEA 产品之后,Oracle 中间件在针对开放标准支持方面,更为全面而完整,可说居于业界领先的地位;包括对 JavaEE 5.0 和 JAX 一系列 XML API 的支持。此外针对 SOA 相关标准方面,则包括了 WS-ReliableMessaging,WS-Security 和 WS-Addressing,以及(国内许多朋友持续关注,)目前正在 OASIS 进行标准化过程的 SCA(Service Component Architecture;服务组件架构)。
说到这儿,不禁想起,过去一阵子和一些客户交流时,发现他们在 Oracle 和 BEA 两家公司正式完成合并之后,关于产品线调整、存废,和路线图等相关问题,非常关注、且仍存有不少疑惑,少部分甚至于有「好像除了 Tuxedo 和 WebLogic 之外,其余的都没留下来」的错误印象。事实上,除了应用服务器和交易中间件之外,在 SOA 和 BPM 的领域,原本两家公司的产品,便有很高的互补性;换句话说,此次产品线的调整和未来发展路线图的规划,不管对原本是 Oracle 或 BEA 的客户来说,所受的影响和冲击,都已降到最低。
就拿上面提到的 SCA 标准来讲,恰可用来说明 Oracle 新的 SOA Suite 套件中的 ESB 部件的发展方向。原本 Oracle 的 ESB 产品和 BEA 的 AquaLogic Service Bus (ALSB),都相当重视对 SCA 规范的支持,但先前各自的侧重点和优先级,有所不同 -- Oracle 将重点放在以 ESB 为工具,做服务组装、编制、打包这方面(这可以从去年早在宣布收购 BEA 之前即发布的 11g beta 版 ESB 中即可看出。至于原来的 ALSB 和整个 AquaLogic 产品线,则选择优先实现围绕以企业资产库产品(ALER; 现已更名为 Oracle Enterprise Repository)为中心的 SCA 视图,方便 SOA 架构师检视服务间的组合、调用关系。现在两家的产品合并之后,恰好两相互补,在 SCA 支持上,不但可基于图形化界面对服务进行组装,更可配合资产库,达到 SOA 全生命周期的监管和治理 (governance)。
不管是原来的 Oracle ESB (OESB),或是原名 ALSB 的 Oracle Service Bus (OSB),二者都继续保持战略性产品的地位。在明年 11g 版本正式推出时,除了计划将继续长期支持目前版本中,客户已经在使用的绝大多数功能之外,同样重要的是,将二者整合为更紧密的单一化产品。
在 SCA 的部分,如上所述,功能恰好互补、不重叠。除此之外,在服务路由、调度、编制,和异构连接协议(Web services, FTP, MQ, Socket, SMTP, JDBC...)支持方面,以 OSB 为主。格式转换方面,OESB 的基于 XSLT 的转换将继续长期支持,而 OSB 上基于 XQuery 的转换,包括图形映射界面,由于更为先进(例如能处理 XSLT 做不到的一变多、将单个消息拆成多份),是推荐客户今后尽量采用的方式。
工具界面方面,将本着过去的做法和产品策略,采用基于浏览器、基于 Web 的简易图形化界面,使 ESB 的主要使用对象 -- 负责服务、IT 运营的人员(而非开发人员),不需要先熟悉 Eclipse 或 JDeveloper 等 IDE 工具,不需要具备编程技能,便可快速上手,在 ESB 上进行各种设置的操作。
http://blog.csdn.net/hu_zhenghui/archive/2008/10/08/3035006.aspx
villa123收录,使用标签:项目管理,时间:2008-10-9 17:16:19 | 相关网摘,我也收藏
设计模式之观察者(Observer)模式与其C++通用实现分上、中、下三篇。上篇详细讲解何为观察者模式以及其特点,并给出一个应用实例与其实现。中篇研究如何运用C++各种技术实现一个通用/万能的观察者模式。下篇讨论中篇所给出的实现可能遇到的问题及解决方案。
设计模式之观察者(Observer)模式与其C++通用实现(下)
——林石 2008-10-08
我们在《设计模式之观察者(Observer)模式与其C++通用实现(中)》一文中给出了一个以C++语言实现的通用观察者模式方案骨架。然而,实际的工程项目需求往往要比理想状态复杂得多,此篇便是与读者一起探讨在现实世界中可能遇到的各种棘手问题及解决方案。
我把目前为止我所遇到的问题罗列如下:
复合主题
多线程
更新方法修改观察者链表
接下来我们一一给予讨论。
(一)复合主题
考虑GUI的组件设计,我习惯用Widget类代表之,它需要处理许多用户交互以及系统事件,其中最常见的用户交互事件有鼠标及键盘事件。倘若架构师决定以事件监听方式设计整个UI框架,那么Widget便具有主题的角色,相应的,鼠标及键盘事件便是观察者角色。实际上,一个主题对应多种(不是多个)观察者的现象很普遍。
我们借助中篇所给的观察者模式骨架实现这类应用。
借助多继承机制,很容易办到:
struct MouseListener {
void mouseMoved(int x, int y) {}
};
struct KeyListener {
void keyPressed(int keyCode) {}
};
class Widget : public BasicSubject, public BasicSubject{...};
添加事件监听器的伪代码大致如下:
MouseListener mel;
KeyListener kel;
Widget w;
w.addObserver(mel);
w.addObserver(kel);
为了使Widget添加/移除事件监听器的方法更加友好,我们可以为Widget提供addXXXListener/removeXXXListener 方法,这些方法会把调用转给基类。有了这些相对较友好的接口后,基类的addObserver/removeObserver接口对用户已经没有用了,所以我们可改用protected继承。综合起来,代码看起来大致像这样:
class Widget : protected BasicSubject,protected BasicSubject{
typedef BasicSubject MouseSubject;
typedef BasicSubject KeySubject;
public:
inline void addMouseListener(MouseListener &mel) {
MouseSubject::addObserver(mel);
}
inline void removeMouseListener(MouseListener &mel) {
MouseSubject::removeObserver(mel);
}
inline void addKeyListener(KeyListener &kel) {
KeySubject::addObserver(kel);
}
inline void removeKeyListener(KeyListener &kel) {
KeySubject::removeObserver(kel);
}
void handleMsg(int msg) {
if (msg == 0) {
MouseSubject::notifyAll(&MouseListener::mouseMoved, 1, 1);
} else if (msg == 1) {
KeySubject::notifyAll(&KeyListener::keyPressed, 100);
}
}
};
当然,你也可以不使用继承改而使用组合技术实现,这完全取决于你的爱好。组合版本的实现大致是像这样的:
class Widget {
public:
inline void addMouseListener(MouseListener &mel) {
ms_.addObserver(mel);
}
inline void removeMouseListener(MouseListener &mel) {
ms_.removeObserver(mel);
}
...
private:
BasicSubject ms_;
BasicSubject ks_;
};
(二)多线程
倘若我们的应用程序运行在多线程环境中,那你可要谨慎了。试想线程A正在添加观察者的同时另一线程B也试图添加观察者吧。我们默认使用的容器std::list是线程非安全的,所以我们的BasicSubjcet也会是线程非安全的。要解决此问题,有两种途径。一是使用线程安全容器,另一种是我们在BasicSubject的适当地方放置锁。我只讨论后一种情况。
为了让代码具有一定的灵活性,我们使用泛型编程中常用的Policies技术。第一步将锁类定义出来:
struct NullLocker{
inline void lock() {};
inline void unlock() {};
};
struct CriticalSectionLocker{
CriticalSectionLocker() {::InitializeCriticalSection(&cs_);}
~CriticalSectionLocker() {::DeleteCriticalSection(&cs_);}
inline void lock() {::EnterCriticalSection(&cs_);}
inline void unlock() {::LeaveCriticalSection(&cs_);}
private:
CRITICAL_SECTION cs_;
};
前者为空锁,用于单线程环境中。后者借助Windows平台中的临界区实现进程内的锁语义。你也可以再增加进程间的锁语义。
接着便是将我们的BasicSubject类修改成如下样子:
template <
class ObserverT,
class LockerT = NullLocker,
class ContainerT = std::list
>
class BasicSubject : protected LockerT {
public:
inline void addObserver(ObserverT &observer) {
lock();
observers_.push_back(&observer);
unlock();
}
inline void removeObserver(ObserverT &observer) {
lock();
...
unlock();
}
protected:
template
inline void notifyAll(ReturnT (ObserverT::*pfn)()) {
lock();
for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
((*it)->*pfn)();
unlock();
}
...
};
默认的锁类是NullLocker,也就是运行在单线程环境中。需要工作在多线程中时可像这样使用:
class Widget : protected BasicSubject {...};
(三)更新方法修改观察者链表
想像一下当观察者在接收到通知而立即修改主题中的观察者链表时会发生什么?因为主题是通过对已注册的观察者链表迭代而逐个通知观察者的相应更新方法的,换句话说,在迭代进行中观察者就去修改观察者链表。这个问题类似于这样的代码设计:
std::list is = ...
for (std::list::iterator it = is.begin(); it != is.end(); ++it) {
is.erase(std::remove(is.begin(), is.end(), 2), is.end());
}
危险!迭代器在链表被修改后有可能失效。
也许你会疑虑,在使用了(二)中所提的锁机制之后不就不会有此问题了吗?实际情况是,锁对于此类问题没有任何作用。
解决此类问题的最好办法是使用不会因容器本身被修改而促使迭代器失效的容器。然而,就目前来说,标准STL库中的所有容器都不属此类。因此,我们有必要花点心思处理此类问题。
当链表处于被迭代过程中时,对链表的修改动作先被记录下来,等到链表迭代完毕后再回过头执行先前记录下来的修改动作,如果对链表的修改动作不是发生在迭代过程中,就按普通方式处理。依据此思想,代码可像这样实现:
template <
...
>
class BasicSubject : protected LockerT
{
public:
BasicSubject() : withinLoop_(false) {}
void addObserver(ObserverT &observer) {
lock();
if (withinLoop_)
modifyActionBuf_.insert(std::make_pair(true, &observer));
else
observers_.push_back(&observer);
unlock();
}
void removeObserver(ObserverT &observer) {
lock();
if (withinLoop_)
modifyActionBuf_.insert(std::make_pair(false, &observer));
else
observers_.erase(
remove(observers_.begin(), observers_.end(), &observer),
observers_.end());
unlock();
}
protected:
template
void notifyAll(ReturnT (ObserverT::*pfn)()) {
lock();
beginLoop();
for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
((*it)->*pfn)();
endLoop();
unlock();
}
...
private:
inline void beginLoop() {
withinLoop_ = true;
}
void endLoop() {
if (!modifyActionBuf_.empty()) {
for (std::multimap::iterator it = modifyActionBuf_.begin(),
itEnd = modifyActionBuf_.end(); it != itEnd; ++it) {
if (it->first)
observers_.push_back(it->second);
else
observers_.erase(
remove(observers_.begin(), observers_.end(), it->second),
observers_.end());
}
modifyActionBuf_.clear();
}
withinLoop_ = false;
}
protected:
ContainerT observers_;
private:
bool withinLoop_;
std::multimap modifyActionBuf_;
};
我使用了STL中的multimap模板类来储存修改动作。其中key被设为bool类型,true表明是添加动作,false表明是移除动作。此外,因代码量的增加,内联函数已无必要,故移除了所有的inline关键字。
后记:编写通用库时不能假定用户所处某一特定环境中,因而须谨慎应对各种可能遇到的问题,这便是为什么我们常说库的实现往往比为特定应用而编写的模块要复杂得多的缘故,加之C++语言本身的复杂性以及局限性,以致我们设计一个相对完美的观察者模式是何其困难。
鉴于以上情况,我相信问题远不止如此,真诚希望读者提出你所遇到的各种问题,以便我们一起讨论学习。
http://blog.csdn.net/carylin/archive/2008/10/08/3035528.aspx
villa123收录,使用标签:项目管理,时间:2008-10-9 17:15:58 | 相关网摘,我也收藏
设计模式之观察者(Observer)模式与其C++通用实现分上、中、下三篇。上篇详细讲解何为观察者模式以及其特点,并给出一个应用实例与其实现。中篇研究如何运用C++各种技术实现一个通用/万能的观察者模式。下篇讨论中篇所给出的实现可能遇到的问题及解决方案。
设计模式之观察者(Observer)模式与其C++通用实现(中)
——林石 2008-10-03
通过上篇的介绍我们知道了观察者模式的基本特点、使用场合以及如何以C++语言实现。有过多次编写观察者模式代码经验的你也许会发现,几乎所有的案例存在为数相当可观的重复性代码:定义一个观察者接口;定义一个主题并实现其诸如注册一/多个观察者,移除一/多个观察者,广播至所注册的观察者等基本行为。既然如此,我们有没有可能为所有观察者模式抽象出共有的接口与行为,以便日后复用呢?
此篇文章便是探讨如何实现一个通用或称为万能的观察者模式库。
我们为所有的观察者/订阅者抽象出一个共有的接口IObserver:
struct IObserver {
virtual void update() = 0;
virtual ~Observer() {}
};
当主题状态发生改变时IObserver对象的update方法会被自动调用。IObserver的子类会实现update方法,以便具有其特定的行为。考虑到update方法的具体实现,大部分情况下我们需要查询主题的状态,从而做出反应。这有多种实现方案:一是生成全局或类似全局性(如Singleton技术)的主题对象:
Subject g_subject;
...
struct ConcreteObserver : public IObjserver {
virtual void update() {
if (g_subject.getStatus() == xxx) {
...
}
};
因为“尽可能的不要使用全局对象”缘故,这种方式不常用。二是为update方法增加一个参数,以便告知update某些必要的信息,为具有普遍性,我以Event代表此类,定义如下:
struct Event {
Event(Subject &subject);
BasicSubject *getSubject();
virtual ~Event() {}
};
很明显,这应该是个基类,所以具有需析构方法,此外,Event还提供一个获取主题的方法。BasicSubject类是我们随后要说到的主题基类。这样,IObserver接口的定义看起来应该是这样:
struct IObserver {
virtual void update(Event &event) = 0;
virtual ~IObserver() {}
};
接下来处理我们的主题,根据前面所提到的它应该具有的行为,它的定义应该大致像这样:
class BasicSubject{
public:
virtual ~BasicSubject() {}
void addObserver(IObserver &observer);
void removeObserver(IObserver &observer);
protected:
void notifyAll(Event &event);
protected:
std::list observers_;
};
BasicSubject基类有三个方法,分别是增加一个观察者,移除一个观察者以及通知已注册观察者。至于其实现,我留给读者,当作练习。
现在让我们通过以上三个基类(Event、IObserver及BasicSubject)来重新实现在上篇中所给出的例子:
struct MMEvent : public Event {
MMEvent(MMInteligenceAgent &sub) : Event(sub) {}
};
struct MMInteligenceAgent : public BasicSubject{
MMStatus getStatus() const {return status_;}
void trace() {notifyAll(MMEvent(this));} // for demonstrating how to use nofifyAll method.
private:
MMStatus status_;
};
struct Larcener : public IObserver {
virtual void update(MMStatus status) {
if (status == Sleeping) {
...
}
}
};
现在是不是简单了许多?
不要停止你的脚步,更不要高兴的过早。
我们事先定义了三个接口让我们的客户遵循,约束太多了。
主题Subject与观察者Observer之间虽然已是抽象耦合(相互认识对方的接口基类),但仍可改进,使两者间的耦合度更低。
考虑到UI中的窗口设计,需要监视的窗口事件可能有:
windowOpened
windowClosing
windowIconified
windowDeiconified
windowActivated
windowActivated
windowDeactivated
倘若代码全由你一人设计,你大可将以上7个事件合并为一个粗事件并通过窗口(也就是这里的Subject了)提供一个标志表明目前发生的是这7个中的哪一个事件,这没什么问题。但是,我相信并不是所有代码都由你一人包办,设想你的同事或是客户将WindowEventListener(也就是这里的Observer)设计成几个独立的更新方法的情况吧(java便是如此)。糟糕,我们目前定义的IObserver接口只支持单一更新方法。
是时候将我们的设计改进了。
事实上,在我们定义的三个基类当中最没有意义的便是IObserver接口,它什么也没帮我们实现,仅是个Tag标记,以便我们能为BasicSubject类指明addObserver及removeObserver方法的参数。通过模板技术,我们不必定义IObserver接口:
template <
class ObserverT,
class ContainerT = std::list
>
class BasicSubject
{
public:
inline void addObserver(ObserverT &observer);
inline void removeObserver(ObserverT &observer);
protected:
ContainerT observers_;
};
BasicSubject不需要虚析构函数,因为客户不需要知道BasicSubject类的存在。类模板参数ContainerT的存在是为了让客户可以选择容器类型,默认容器类型是std::list,也许你的客户更喜欢std::vector,于是他便可这样使用:
class MyBasicSubject : public BasicSubject
当BasicSubject状态改变时需要通知观察者,所以notifyAll方法仍不可缺少。考虑到观察者可能具有多个更新方法,我们可以通过notifyAll方法的参数来指定要更新的方法。是的,就是函数指针了。所以nofifyAll方法可能是这样的:
template
void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)(Arg1T), Arg1T arg1) {
for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it) {
((*it)->*pfn)(arg1);
}
}
其中pfn是指向ObserverT类的、具有ReturnT返回类型的、接收一个类型为Arg1T参数的函数的指针。
现在连Event基类都不需要了,其角色完全由模板参数类型Arg1T所取代。
问题远没有结束。
仔细想想Arg1T参数类型的推导,编译器既可选择从pfn函数所声明的形参类型中推导也可选择从arg1实参推导,当实参(arg1)类型可唯一推导且与pfn函数声明的形参类型完全匹配时没问题。当实参类型与形参类型不匹配时编译器报错。如:
struct MyObserver {
void increment(int &val) {++val;}
};
struct MySubject : public BasicSubject {
void trigger() {
int i = 10;
notifyAll(&MyObserver::increment, i);
}
};
我的编译器上的报错信息大致是:"template parameter 'Arg1T' is ambiguous" ... "could be 'int' or 'int &'"。编译器不知道Arg1T是int(从实参i推导)还是int&(从函数increment形参val推导)。编译器真傻。
此问题的根源是模板参数多渠道推导的不匹配性所致。为避免多渠道推导,聪明的你可能想到这样定义notifyAll方法:
template
void BasicSubject::notifyAll(const MemFunT &pfn, Arg1T &arg1);
值得表扬。
设想pfn所声明的形参类型是const引用类型(如const int&)而用户把常量(如10)直接用作实参的情形吧:
struct MyObserver {
void increment(const int &val) {}
};
struct MySubject : public BasicSubject {
void trigger() {
notifyAll(&MyObserver::increment, 10);
}
};
编译器会抱怨不能把实参(10)类型(int)转换到形参(val)类型(const int&)。
那能否将arg1声明为const引用类型呢,即:
template
void BasicSubject::notifyAll(const MemFunT &pfn, const Arg1T &arg1);
这会限制观察者更新方法对参数进行任何修改,不可接受。
按着你的思路,我可以给你一种解决方案,不过要将notifyAll方法声明为:
template
inline void notifyAll(const MemFunT &pfn, Arg1T arg1) ;
是的,arg1前少个引用(&)符号。当观察者更新方法的形参类型为非引用类型时没任何问题,仅仅是多了一次拷贝而使效率稍微低下而已:
struct MyObserver {
void increment(int val) {}
};
struct MySubject : public BasicSubject {
void trigger() {
notifyAll(&MyObserver::increment, 10); // OK
}
};
但是当形参类型为引用类型时直接使用的结果与预期行为不符:
struct MyObserver {
void increment(int &val) {++val;}
};
struct MySubject : public BasicSubject {
void trigger() {
int i = 10;
notifyAll(&MyObserver::increment, i);
cout << i << endl; // 输出10,但我们期望是11
}
};
我们可以通过一个额外的辅助类将其解决:
template
class ref_holder
{
T& ref_;
public:
inline ref_holder(T& ref) : ref_(ref) {}
inline operator T& () const {return ref_;}
};
template
inline ref_holder ByRef(T& t) {
return ref_holder(t);
}
函数ByRef的存在仅仅是为了方便生成ref_holder对象(类似STL中的make_pair)。当需要引用传递时以ByRef函数作用到实参上:
struct MyObserver {
void increment(int &val) {++val;}
};
struct MySubject : public BasicSubject {
void trigger() {
int i = 10;
notifyAll(&MyObserver::increment, ByRef(i));
cout << i << endl; // 输出11,OK
}
};
现在没问题了,前提是能正确使用。但是,我敢打赌,你的客户会经常忘记ByRef函数的存在,以致最终放弃你所提供的解决方案。
我会给出另外一种更完美的方案。
实际上,此处的notfiyAll方法是个转发函数,对其的调用会转发给已向BasicSubject注册了的所有观察者对象的相应更新方法(我称之为目的函数)。为了具有正确的转发行为以及较高的效率,转发函数的形参类型声明与目的函数的形参类型声明必须遵循一定的对应规则。篇幅所限,这里直接给出结论(以下将“转发函数形参”简称为“转发形参 ”,将“目的调用函数形参”简称为“目的形参”。):
目的形参类型为const引用类型时,转发形参类型也是const引用类型;
目的形参类型为non-const引用类型时,转发形参类型也是non-const引用类型;
目的形参类型为其它类型时,转发形参类型是const引用类型。
我们通过模板traits技术可实现上面所提的转发——目的函数形参类型对应规则:
template
struct arg_type_traits {
typedef const T& result;
};
template
struct arg_type_traits {
typedef T& result;
};
template
struct arg_type_traits {
typedef const T& result;
};
最后一个traits的存在是必须的,因为引用引用类型(如int&&)在C++中是不合法的。现在我们可以定义我们的notifyAll方法了:
template
inline void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)(Arg1T),
typename arg_type_traits::result arg1) {
for (ContainerT::iterator it = observers_.begin(),itEnd = observers_.end(); it != itEnd; ++it)
((*it)->*pfn)(arg1);
}
聪明的你可能会问,万一观察者的更新方法参数不是一个呢?说真的,我也很想确定到底具有几个参数,令我悲伤的是我的客户经常这样回答:“我也不知道有几个。”
我使用了一种比较简单、笨拙却行之有效的手段解决了这一问题。我通过重载notifyAll方法,使其分别对应更新方法是0、1、2、3……个参数的情况。
template
inline void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)()) {
for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
((*it)->*pfn)();
}
template
inline void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)(Arg1T),
typename arg_type_traits::result arg1) {
for (ContainerT::iterator it = observers_.begin(),itEnd = observers_.end(); it != itEnd; ++it)
((*it)->*pfn)(arg1);
}
template
inline void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)(Arg1T, Arg2T),
typename arg_type_traits::result arg1,
typename arg_type_traits::result arg2 ) {
for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
((*it)->*pfn)(arg1, arg2);
}
...
template
inline void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)(Arg1T, Arg2T, Arg3T, Arg4T, Arg5T),
typename arg_type_traits::result arg1,
typename arg_type_traits::result arg2,
typename arg_type_traits::result arg3,
typename arg_type_traits::result arg4,
typename arg_type_traits::result arg5) {
for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
((*it)->*pfn)(arg1, arg2, arg3, arg4, arg5);
}
按我的经验,超过5个参数的类方法不常见,要是你真的有幸遇到了,你大可让实现作者与你共进晚餐,当然,账单由他付。你也大可再为notifyAll增加几个重载方法。
代码看起来有点复杂,但你的客户却很方便:
struct MyObserver {
void copy(int src, int &dest) {dest = src;}
};
struct MySubject : public BasicSubject {
void trigger() { // demonstrate how to use notifyAll method.
int i= 0;
notifyAll(&MyObserver::copy, 100, i);
assert(i == 100);
}
};
int main(){
MyObserver obs;
MySubject sub;
sub.addObserver(obs);
sub.trigger();
}
以上便是我所实现的通用观察者模式库的骨架。之所以称为骨架,是因为还有许多诸如多线程等现实问题没有考虑,我将在下篇中与读者一起探讨现实世界中可能遇到的问题。
http://blog.csdn.net/carylin/archive/2008/10/03/3013725.aspx
villa123收录,使用标签:项目管理,时间:2008-10-9 17:15:37 | 相关网摘,我也收藏
设计模式之观察者(Observer)模式与其C++通用实现分上、中、下三篇。上篇详细讲解何为观察者模式以及其特点,并给出一个应用实例与其实现。中篇研究如何运用C++各种技术实现一个通用/万能的观察者模式。下篇讨论中篇所给出的实现可能遇到的问题及解决方案。
设计模式之观察者(Observer)模式与其C++通用实现(上)
——林石 2008-09-30
观察者模式
意图:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
结构:
优点:
目标(Subject)与观察者(Observer)间抽象耦合
支持广播通信/通知
缺点:
会带来意外更新问题
示例:
考虑这样一个例子:想知道公司最新的MM情报吗?加入我们MM情报邮件组吧。您只需要向我们发送一封订阅邮件即可,我们会把最新的MM情报以电子邮件形式通知您。
现在我们来一步一步实现。很明显,示例中关心MM情况的人物即为订阅者,我们以Subscriber表示这一类人。在定义Subscriber之前我们先定义一个MMStatus枚举,用以表示MM状态:
enum MMStatus {Dining, Sleeping, Working};
这里定义了三个常量用以简单地模拟MM所处的状态。现在我们可以定义Subscriber类了:
struct Subscriber
{
virtual void action(MMStatus status) = 0;
virtual ~Subscriber() {}
};
有经验的读者知道,这样定义Subscriber表明它是个基类,也是个抽象类,或者称之为接口(java中就有 interface关键字)。之所以这样设计,是因为可能存在很多种类型的订阅者(Subscriber接口的子类),每种订阅者对MM的同一种状态可能会有不同的处理方式。这里把多种类型的订阅者抽象出相同的接口方法,也就是Subscriber定义的第3行。第4行虽然只是个空定义,但这是不可缺少的,防止在用基类指针指向子类而delete基类指针时子类的析构行为能正确调用。我们继续定义我们的目标类。没错,就是MM情报组,我们以 MMIntelligenceAgent类表示。
class MMInteligenceAgent
{
public:
void subscribe(Subscriber &subscriber);
void desubscribe(Subscriber &subscriber);
private:
void notifyAll(MMStatus status);
private:
std::list subscribers_;
};
MMInteligenceAgent类有两个公有方法(4、5行),分别用以增加和移除一个订阅者。订阅者可能有多个,我们选择以链表存储之(9行)。当MM状态变更后,借助notifyAll方法,链表中的所有订阅者都会得到通知。
目标类的实现很简单:
void MMInteligenceAgent::subscribe(Subscriber &subscriber)
{
subscribers_.push_back(&subscriber);
}
void MMInteligenceAgent::desubscribe(Subscriber &subscriber)
{
subscribers_.erase(
std::remove(subscribers_.begin(), subscribers_.end(), &subscriber),
subscribers_.end());
}
void MMInteligenceAgent::notifyAll(MMStatus status)
{
for (list::iterator it = subscribers_.begin();
it != subscribers_.end(); ++it) {
(*it)->action(status);
}
}
主要的基类及方法都写好了,接下来我们示例个具体Subscriber类:偷窃者。偷窃者一般在偷盗目标熟睡时比较容易下手,于是偷窃者可使用MM情报组提供的服务,以便知道MM何时在睡觉:
struct Larcener : public Subscriber
{
virtual void action(MMStatus status)
{
if (status == Sleeping) {
// steal something ...
}
}
};
为了使代码更完整,我们可以为MMInteligenceAgent增加一个对MM的跟踪方法,当发现MM状态改变时发出通知。
void MMInteligenceAgent::trace()
{
...
MMStatus status = ...;
notifyAll(status);
}
最后以一个调用示例作为此篇的结束:
int main()
{
...
Larcener l;
MMInteligenceAgent mia;
mia.subscribe(l);
mia.trace();
...
mia.desubscribe(l);
...
}
http://blog.csdn.net/carylin/archive/2008/09/30/3003305.aspx
villa123收录,使用标签:项目管理,时间:2008-10-9 17:15:04 | 相关网摘,我也收藏
项目经理:是否实现了公司的战略目标,或者项目目标. 具体的目标,一般是用利润来衡量,项目的合同价格主要由销售部门来谈,项目经理必须准确的计算成本来配合报价,并且在项目过程中控制成本。有的时候利润并不是优先目标,其他可能的目标是:赢得客户;打响品牌;锻炼队伍等,如果你以打响品牌为第一目标,那你就要严格控制质量,不太考虑成本和利润。公司可以把若干目标按优先顺序列出,项目经理能实现前几个即为成功,如果全部实现,那此项目经理就很了不起了。
需求分析师:项目实施后客户对需求变更的多少,变更越少,需求分析师业绩越好。需求分析师的工作对项目成败有极大的影响,它对人的要求很高,比如沟通能力,对业务的熟悉程度和判断能力(潜在需求),对客户组织(谁说了算)/人员(性格等)的掌握程度,对系统运行环境的了解等。
系统架构师:项目整个过程的架构保持不变,如果有变化,那么架构师的工作即为失败。系统架构基于需求分析的结果,所以有时候架构更改要归咎于需求分析师。其实目前软件架构的资料信息很多,基本上都是知识,创新的机会不多,像David H.Hansson不满web开发的烦琐,创建了Rails on Ruby的例子属于极少数,我们多数都选择成熟的框架和技术。架构师需要有广泛的知识和长期的经验。并且能追踪软件技术的最新发展。
系统设计师:项目实施后维护开发(针对新需求)工作量的多少,改动越少,设计师的业绩越好。设计师的工作是项目中最具创新潜力的部分,精妙的模型,算法开发,公司的核心技术都来源于此处,不同设计师的工作成果可能是天上地下,当然了,缺少巧妙设计的软件系统也可以跑,但后续的维护开发必定会成为一个成本黑洞。
软件开发师:软件的bug数量和修复bug的时间以及bug的严重程度,公司可以有一个公式来量化这些指标。软件开发师的工作是项目质量的基本保证,软件系统最终要在这里变成成品。很多时候软件开发师也兼着设计工作,那么他们的重要性就更大了,好的开发人员多数是好的设计人员,因为写代码本身也是在做设计。
测试工程师:软件实施后bug的数量和严重程度。考核开发人员和测试人员都用了bug指标,但这些bug应该是独立计算的。这里bug是广义的,比如压力测试不过关,也算一严重的bug。
最后是架构/设计/开发/测试的反复,这里情况比较复杂,比如代码质量低下造成测试人员工作量的剧增,测试人员是比较冤枉的,这里就需要项目经理的智慧了,具体情况具体分析,对项目经理的要求是全面的,项目经理需要对团队士气负责,要知道,软件开发是智力活(体力活的观点非常错误),人的因素最重要。
项目经理负责真实而准确的记录项目所有数据,这是业绩考核的根据,他必须做到公平、公正、公开!
http://blog.csdn.net/kevinkevin/archive/2008/10/08/3035792.aspx
villa123收录,使用标签:项目管理,时间:2008-10-9 17:14:35 | 相关网摘,我也收藏
题记:
在与Ivar的访谈之后,我一直想把这一段过程写出来。我尝试拟过许多个题目,最后都写不成文章。几乎在我要放弃的时候,BLOG读者在评论中,对我所解释的“函数式语言”的置疑提醒了我:很多时候不是问题的答案令人置疑,而是问题的思想方法令人置疑。如同我问Ivar的问题,他的答案“令人怀疑的正确”,其实是思想方法的问题。不站在Ivar的历史,以及Ivar的成就的角度上去思考,你会认为Ivar是在应付我的责难。
事实上,那个访谈中,Ivar非常慎重地面对这个问题,并仔细地解释了他所提供的答案。可惜后来CSDN录制时,正好漏掉了这一段。非常遗憾,此回顾这些经历,既以钦佩,亦复深研。
像大师们一样思考
——从“UML何时死掉”谈起
得了一个机会(1),我问Ivar:“UML什么时候才会死掉呀”。我无意用这个
透着促狭味道的问题去为难大师,实在是因为这是我一直以来思考着的问题。向
UML之父去求解,自然是最好。
Ivar细毫没有认为我是在为难他,他诚恳的回答让我在那个会议中陷入了
深思。他说:“什么时候面向对象死掉了,UML就死掉了”。(2)
一个问题看起来很复杂,但它的答案可能非常简单。一个答案看起来非常简
单,但它可能是最正确的。一个正确的答案,也许毫无意义,但也许,那就是大
师的答案。
很多我们现在看起来是非常“理所当然”的事情,就曾经困扰着大师们。比
如说,我们现在都知道程序的基本逻辑是顺序、分支与循环。那么,“为什么顺
序、分支与循环是基本逻辑呢”?“作为基本逻辑,它们充备吗?”谁能回答我?
如何回答我?
这是一个艰深的问题吗?我们知道答案,但即使知道答案,我们也答不出
“为什么”。然而,真正的大师们是论证过这个问题的。那个提出“GOTO有害”
的大师Edsgar Wybe Dijkstra(戴克斯特拉/迪杰斯特拉)就为此写了篇“札
记”。他是怎么论证的呢?他说计算机可以理解的人的思想方式,有三种。分别
是枚举、归纳与抽象。而,重要的是,Dijkstra进一步的说明,分支(if)是
计算机实现枚举的方法、循环(for)是实现归纳的方法。当他进一步的解释“抽
象”时,他说“在现阶段,我发现很难把抽象的作用说得非常清楚”。
Dijkstra大概是做好全部的准备,来完成这篇札记。他通过数学方法来
证明了“分支如何以及为什么能实现枚举”。也就是说明分支对于“枚举”这种思
想方法来说,是否是完备的。Dijkstra在写出了大量的数学推理之后,说“上
述的笨重证明,也使我自己感到烦恼!但是,在现在,如果真的希望证明这个程
序的正确性,我确实没有更好的办法。”Dijkstra引以为佐证的是,“以前,
平面几何里的第一批定理的荒诞证明,也常常使我感到同样的愤怒,因为这些定
理所论证的事情几乎和欧几里得公理自身一样的‘明显’”。
Oh! Dijkstra愤怒的原因,在于原本看起来是如此“显然”、“理所当然”
的事情,却需要无比笨重的过程去证明它!如同我们明明知道“1+1=2”,但证
明这一点,既无趣又令人愤恨。
Dijkstra这样的证明过程,奠定了“程序正确性证明”这门学科的基础;
它的证明结果,是说明了程序的结构性是有限的,例如顺序、分支与循环。这个
有“结构有限”的理论,开创了“结构化编程”这样的一个时代。我在《代码之美》
的序中说“我们如今仍然在这个时代之中而不知觉于这本书的深远影响”,是意
有所指的。因为所谓的“面向对象程序设计”,其根基就是“结构化程序设计 + 在
结构上更高层次的抽象”。而这“更高层次的抽象”,就是“对象”。
Dijkstra在1970年前后就完成了这篇札记,而我们接下来这40年的时
间,仍未逃离大师最初的思想。然而,对这所有的一切,大师最初创见性的想法
可以归结于一句:“可用来理解一个程序的种种思维方法之中,我提及以下三种:
枚举法、数学归纳法、抽象。”
为什么是这三种?是不是只有这三种?能不能有更多种?大师没有解释,他
只是“提及以下三种”而已。Dijkstra一方面给我们留下了空间,一方面,他
足够完备的“论证”说明了基础逻辑必须至少具备“顺序、分支、循环”。而我们,
40年来,无有突破。
Ivar把UML之死,归于一种抽象的失败,或其被更高的抽象所替代。实
在是无比正确的,因为UML也是建立在结构化、抽象这样一些基元的理论之上。
Dijkstra的那篇札记,被收入《结构程序设计》一书,书里的另外两篇,一
篇是“层次结构设计”,讨论的是面向对象程序设计;另一篇是“数据结构札记”,
讨论的是数据基本抽象。三部文章,三位图灵奖得主,一个跨越40年的时代,
以及程序设计语言分类中的1/2(命令式语言)都承受着这种影响。
然而,Dijkstra仍然无法论证,或未曾说明过“三种基础逻辑的唯一性”,
他只是想当然地说“我认为”而已。
同样只是从“我认为”开始的,还有图灵机。如果有一个毛头小子跳出来说,
“我认为”计算机应该象一个大笨象吃意大利面条;大笨象要有肚子,面条上要
打孔。Oh,很好,这个毛头小子立即会被哄出“计算机科学”的神圣殿堂。问题
是,图灵就是这样一个毛头小子。于是他的这一构想就被称为“天才式的创想”。
然而,真的只是这样吗?
算盘用了几千年,谁问过“算盘为什么能算东西”?算珠、进位、栏,这些
东西,是不是基本的存储结构?用算盘的“我们”,是不是计算单元?珠算表是
不是运算规则?那些珠子表达出来的“0~9”的排列,是不是输入输出的界面?
“我们+算盘”就是一个完整的计算系统。这个计算系统的完整性,是图灵用
了一个假想来说明的。图灵不过是用一个假想描述了一个事实,而这个事实,“看
起来”能被机器实现。于是,我们的计算机时代就开始了。
图灵是否证明过“大笨象吃意大利面条为什么是一个完备的计算机系统”
呢?不,最初等的问题,往往难于证明。往往,他的证明过程,或应用过程,只
是触发了一个想象。
对计算机根本问题的思考,许多会追溯到哲学思想的层面。IOPD和PDIO
的问题,程序=算法+结构的问题等等,就是这一类。还有一些会追溯到人类行
为学、语言学等层面,例如语言、语法、语义,以及有没有语用这样的问题。大
多数时候,真正推动了计算机发展的,不是对具体问题的推理求解,而是对问题
本身的抽象。在Dijkstra的叙述中,抽象更象是终极武器。按照Brooks的
观点,“数据的表现形式(数据结构,抽象的结果之一)是编程的根本”;按照
Dijkstra的引述,“引用未解释过的名词阐述公理或定理和作用于未解析过的
操作数的(命了名的)运算两者之间有着某种平行的相似性”。
无论如何,我们“做一个计算机”,原始的目的不外两个。其一是“让它计算
数学”,其二是“让它像人一样思考”。注意,我的确是说“计算数学”,数学是人
类的理论学科,“怎么算”,以及算的内容等等,都是我们自己设定的。而计算
机的能力,只是计算“数学”这个它未知的对象而已。事实上,我们现在在讲的“命
令式”,以及“函数式”,或“说明式”,就只是我们为计算机设定的“最基础的运
算方式”。在这个“运算系统”中,“数学”并不是最初设定的。
命令式如何计算,函数式如何计算……类似于此的问题了解清楚了,我们对
这类语言也就了解了。再谈什么高阶函数(higher-order function)、克
里化(Currying)、延续(Continuation),或发生-迭代器
(Generator-Iterator)之类,那已经是具体语言的表象,而非“这一类语
言”的本质了。举例来说,JavaScript 1.5还没有实现过“生成器对象
(Generator Object)”,但并没有人否认它是函数式语言。反过来说,
Generator Object原本就不是函数式语言的必备要素。
LISP表达了函数式语言的全部“必备要素”,然而LISP七个原子运算也是
针对于“LIST”这个结构抽象来说的。对于一个“(顺序的)表”,这七个原子运
算是必须的,而对于另一个“(关系的)表”就未必如此了。所以,那这些原子
运算,也不必放在函数式的必备要素中。象LUA这样的函数式语言实现方法的
出现,也证明了这一点(3)。
函数式还剩什么?
真正理解函数式的秘密,是要一个语言一个语言的学习下去么?是要一种运
算法一种运算法地学习下去么?我们听完人家说“持续”,于是就开始了解持续,
而不去问持续为什么出现在函数式里面?亦或是不是函数式的必备要素?还是
函数式运算系统的自身的“问题”?我们正是迷失于种种语言和概念的表象,而
最终没能象大师一样去思考“计算机不过是大笨象吃意大利面条”这样的抽象层
面的问题。
我们要改变的是思想,我们要增强的是能力。大多数人只是增强能力,而不
改变思想。这就是“我们”——大多数人不是大师的原因。
感谢Ivar。并不仅仅是因为他给出了一个问题的答案,以及他的谦谨和微
笑,还感谢他告诉我们:答案并不是表面上的正误,真正的答案是答案背后的思
想。 (我与Ivar Jacobson先生,2008.07.21,CSDN软件工程研讨)
注:
~~~~~~
(1)CSDN围绕“软件工程40年”进行的一个软件工程研讨会。相关的视频在
http://live.csdn.net/Issue519/LivePlay.aspx
但是由于录制技术上的问题,这个研讨会丢失了1/2多的内容。
(2)当然,在那个会议上,我的提问与Ivar的回复都不会如此轻率。我原始的
问题是:“既然——包括自然语言在内的——所有的语言最终都会消亡,
那么UML作为一门建模语言,它什么时候会消亡呢?以及,新的替代它的
建模语言又应该具备那些特性呢?”Ivar当时的答案是:“如果作为UML
核心思想的OOP方法没有被推翻,那么UML就不会消亡”。因此他对这个时
间的预期是:“接下来10~15年,或更长的时间,不会有更新的、标准
化的建模语言。”
(3)LISP的基础数据结构索引数组(表,LIST),LUA的基础数据结构是关
联数组(表,MAP)。
http://blog.csdn.net/aimingoo/archive/2008/10/09/3037952.aspx
villa123收录,使用标签:.NET,时间:2008-10-9 17:08:27 | 相关网摘,我也收藏
C#3.0新体验(四)
maotin
20081008
前言:
放了一个长假,很是郁闷,休息没休息好,学习没学上,玩也没怎么玩。。。。这7天怎么就没了?
哎,看来人生没计划,没安排就会混混沌沌的过一天又一天,都不知道自己到底在干嘛。这样下去只会一事无成!计划好每一天的工作、学习、生活,或者安排好每一件事情,每一个目标的计划,对取得成功还是非常有帮助的!
五.匿名函数
//匿名函数
var p1 = new {Name="张三",Age=28};
var p2 = new {Name="李四",Age=29};
var intArr = new[] {1,2,3,4,5}; 首先我们来看一下上面这段代码,比较简单,和我们平时定义一个实例基本没什么区别,只是new后面没有跟具体定义的类型;这里我们使用new关键字调用匿名初始化器创建了一个匿名类型对象;匿名类型直接继承自System.Object;匿名类型的成员是编译器根据初始化器推断而来的一些读写属性。
这里我们注意到匿名函数基本上是配合隐式类型var来使用的,而且定义的顺序也是一定要注意,上面p1和p2初始化时定义的属性名、类型和顺序一致,因此编译器认为他们是同一个类型,可以使用p1=p2这样的赋值语句;特别是顺序需要注意,如果初始化时名称类型一致而顺序不一致,则p1和p2就是两个类型,如果使用p1=p2则编译时会抛出错误 :无法将类型“AnonymousType#1”隐式转换为“AnonymousType#2” 。定义匿名函数时还需要注意,不能用null赋初始值。
匿名函数解析:
匿名函数的基础是对象初始化器,匿名类型从对象初始化器(object initializer)自动推断和生成的元组类型。下面我们来看看匿名函数到底怎么生成的和我们原来的定义方式有什么区别:
var p1 = new {Name="张三",Age=28}; 我们给p1赋了一个匿名类型,在编译时,编译器使用对象初始化器推断的属性来创建见一个新的匿名类型,该类型拥有Name和Age的属性,在运行时,会创建新类型的一个实例同时Name和Age属性将会被设置为对象初始化器中指定的值“张三”、28;和上面几节里描述的一样这里大家一定会想到,肯定又是在编译器里封装了一些处理;确实是这样,下面这段代码描述编译器针对匿名函数语句具体做了哪些工作:
class __Anonymous1
{
private string name;
private int age;
public string Name{get{return name;} set{name=value;}}
public int Age{get{return age;} set{age=value;}}
}
__Anonymous1 p1 = new __Anonymous1();
p1.Name="张三";
p1.Age=28; 这段代码就是我们非常熟悉的写法,编译器就是在后台依据匿名函数解析类型,创建新类,初始化对象;如果你创建了多个相似的匿名类型,C#编译器会聪明的发现这一点,只生成一个类和它的多个实例;
小结
匿名函数多数是和var隐式类型一起使用,因此多在局部(方法内部)内使用。同样匿名函数是编译器编译时创建,内部实现与我们原来的写法没有本质区别。
参考:http://dev.21tx.com/2006/03/13/10957.html
http://www.cnblogs.com/allenlooplee/archive/2008/06/01/1211520.html
http://blog.csdn.net/maotin/archive/2008/10/09/3040632.aspx
villa123收录,使用标签:Web开发,时间:2008-10-9 17:07:52 | 相关网摘,我也收藏
关于Perl里面正则规范
关于Perl正则的一些规范,很多妙用。
1,定界符
=~ m/there/;
=~ s/there/here/;
还有类型的定界符:
=~ /there/
=~ m#there#;
=~ s#there#here#;
=~ m(there);
=~ s(there)(here);
=~ m{there};
=~ s{there}{here};
=~ m[there];
=~ s[there][here];
=~ m,there,;
=~ s,there,here,;
=~ m.there.;
=~ s.there.here.;
=~ m|there|;
=~ s|there|here|;
=~ m'there';
=~ s'there'here';
2,修改符
修改符一般放在语句最后一个正斜杠(或者其他分隔符)的后面,修改符还可在匹配范式内定义,这是用(? 修改符)来实现的。
/x 允许在范式中加上注释和额外的空白字符,以提高程序的可读性。
/i 允许不分大小写的匹配范式。
/s 单行方式,决定了圆点 . 是否匹配换行符,使用了/s,圆点就匹配换行符,否则就不匹配。
比如:
#!/usr/bin/perl
use strict;
use warnings;
my $string = ".\n.";
print "The original string is: $string\n";
my $num = $string =~ s|(.)|#|sg;
print "$num occurences change, and be changed to: $string\n";exit;
/m 多行方式,决定了脱字号 ^ 和美圆符 $ 是否匹配换行符,如果不用/s,^和$只能定位在字串的开始和结束处,它们并不匹配嵌入的换行符,这种情况等同于\A和\Z,否则不仅仅匹配字串的开始和结束,也匹配刚好处在嵌入换行符前后的一个位置。
/o 仅仅一次计算表达式的值
/e 将替代字符串作为一个表达式(仅仅在替代操作时有效)
/g 是一个全局修改符。另外,/g与while使用能在字串的所有匹配中进行遍历。修改符/G必须与/g一起使用,用来匹配前一个/g匹配的停止位置。
如
#!/usr/bin/perl
use strict;
use warnings;
my $string = "~32sda13dAZ.'sDa#!3_C-!";
print "The original string is: $string\n";
my $num = $string =~ s.\w.#.g;
print "$num occurences change, and be changed to: $string\n";
exit;
3,正则里一些特殊字符
() 将表达式结组
[] 寻找一组字符
\d 等于 [0-9]
\D 等于 [^0-9]
\w 等于 [0-9A-Za-z_]
\W 等于 [^0-9A-Za-z_]
\s 等于 [\f\n\r\t ]
\S 等于 [^\f\n\r\t ]
. 等于 [^\n]
4,关于一些特殊符号
\b 不属于空白字符,向前缩进一个字符
\t 属于空白字符,匹配制表符
\r 属于空白字符,匹配回车符
\a 不属于空白字符,匹配闹钟符
\e 不属于空白字符,匹配转义符
\033 不属于空白字符,匹配八进制符
\x1B 不属于空白字符,匹配十六进制符
\c[ 不属于空白字符,匹配控制字符
属于空白字符,匹配空格
属于空白字符,匹配制表符
\f 属于空白字符,匹配换页符
\n 属于空白字符,匹配换行符
\0 不属于空白字符,功能不详
\c 不属于空白字符,功能不详
\x 不属于空白字符,功能不详
5,注意正则里的选择符的特殊性
选择运算符是所有运算符中优先级最低的,这意味着它最后执行。
6,正则里的限定符的一些经典用法
限定符常常与一些字符或词联合使用
* 匹配任意数个;
+ 匹配一个或多个;
? 匹配零个或一个;
{n} 匹配 n 个;
{n,m} 匹配 n 至 m 个;
{n,} 匹配 n 和 n 个以上;
限定符贪婪好像与生俱有的。在缺省状态下,*或+限定符匹配满足正则表达式的一个范式的最大实例数。可用?号显式的规定限定符的不贪婪。如果问号放在另一个限制符之后(甚至另一个问号之后),都可以使限定符不贪婪。
7,声明与断言
首先注意声明的长度为 0;
Perl种有一组控制大小写和换码的声明:
\u 使下一个字母变大写;
\l 使下一个字母变小写;
\U 使文本的剩余字符变成大写;
\L 使文本的剩余字符变成小写;
\Q 会除字母之外的其他字符进行换码处理,直至遇到 \E 声明、常规表达式结束或者字串结束。
\A声明和脱字符号(^)匹配字串的开始;
\Z声明和美元符号($)匹配字串的结束或刚好在字串结束前的换行符;
\z 只匹配字串的结束;
\b 匹配一个单词(字)边界;
\B 匹配一个非单词(字)边界;
(?#text) 忽略括号内的注释文本;
(?:pattern) 与组一致,但匹配时不生成$1,$2;
(?imsx:pattern) 与组一致,但匹配时不生成$1,$2,在特定的风格有效期间,内嵌风格匹配修饰符;
(?=pattern) 前看声明,如果正则表达式在下一次匹配 pattern 风格,就开始匹配,而且不影响匹配效果。如/\w+(?=\t)/将匹配制表符是否恰好在一个字\w+后面出现,并且制表符不添加到$&的值中;
(?!pattern) 如果正则表达式在后面不匹配 pattern ,才会开始匹配。如/foo(?!bar)/,只有当出现 foo,并且后面不出现 bar 时才开始匹配;
(?<=pattern) 后看声明,只有在pattern已经匹配下面的表达式,并且不将 pattern 的结果放入$&变量中,才匹配下面的语句。如/(?<=\t)\W+/匹配制表符是否恰好在\W+前出现,但又不将制表符送到$&中;
(? (?{code}) 表示对 code 的使用是试验性的。如果返回真,就认为是与(?:pattern)断言同一行里的匹配。code 不插入变量。这个断言仅仅在 use re 'eval' 编译指示符时才有效;
(?>pattern) 如果类型锁定在当前位置,就使用单独的 pattern 匹配子字符串。如正则表达式/^(?>a*)ab/永远不会匹配,因为语句(?>a*)将匹配字符串开头所有的 a 字符,并删除与 ab 匹配的字符 a;
(!<=pattern) 非后看声明,与后看声明意思相反;
(!=pattern) 非前看声明,与前看声明意思相反;
(?(condition)yes-pattern|no-pattern) 条件表达式——条件语句或者是一个圆括号中的整数,或者是一个断言;
(?(condition)yes-pattern)
(?imsx) 嵌入风格匹配修饰符。当要把表达式修改符嵌入在变量中,然后把变量用在不指定自己的修饰符的一般规则表达式中;
(?-imsx) 这个断言很有用——后面带任何内容都会关闭修饰符,直到出现另一个嵌入的修饰符。
8,向后引用
Perl的正则表达式引擎允许使用前面匹配好的值,这些值叫做向后引用。
例如:
=~ m/(\w)\W*(\w)\W*(\w)\W*(\w)\W*\4\W*\3\W*\2\W*\1/;
=~ s/(\w)\W*(\w)\W*(\w)\W*(\w)/$4$3$2$1/;
http://blog.csdn.net/fibbery/archive/2008/10/09/3041066.aspx
villa123收录,使用标签:Java,时间:2008-10-9 17:06:51 | 相关网摘,我也收藏
Groovy探索之Map与DSL
在Java语言编程中,从Java5开始引入了可变参数的概念;引入可变参数的目的,是为了解决形如下面代码的编码冗余。
public class VarArgument {
private int a,b,c,d;
private void p()
{
System.out.println(a+","+b+","+c+","+d);
}
public void foo(int a)
{
this.a = a;
p();
}
public void foo(int a,int b)
{
this.a = a;
this.b = b;
p();
}
public void foo(int a,int b,int c)
{
this.a = a;
this.b = b;
this.c = c;
p();
}
public void foo(int a,int b,int c,int d)
{
this.a = a;
this.b = b;
this.c = c;
this.d = d;
p();
}
public static void main(String[] args) {
VarArgument va = new VarArgument();
va.foo(1,4);
}
}
上面的代码中,是一个面向对象编程中的常用的方法重载,目的是为了解决“foo”方法输入参数的个数是可以变化的问题,也就是可变参数的问题。我们可以看到,利用方法重载来解决可变参数的问题,我们需要写出多个方法来做同一件事情,这使得我们的编码效率大大降低。
为了提高编码效率,我们从Java5开始引入了可变参数的概念,通过可变参数上面的代码可以写成下面的样子:
public class VarArgument {
private int a,b,c,d;
public void foo(int... nums)
{
int len = nums.length;
if(len>0) a = nums[0];
if(len>1) b = nums[1];
if(len>2) c = nums[2];
if(len>3) d = nums[3];
//todo
System.out.println(a+","+b+","+c+","+d);
}
public static void main(String[] args) {
VarArgument va = new VarArgument();
va.foo(1,4);
}
}
可以看出,随着Java5引入的可变参数概念,的确使得我们的编码量大大减轻,使得我们朝着敏捷编程的方向在前进。但是,可变参数的引入,仍然有以下三个问题没有解决:一是,可读性的问题,作为VarArgument类的使用者,我们不知道“va.foo(1,4)”中的“1”和“4”代表的含义。二是,参数顺序的问题,比如我们想将1赋给a,4赋给b,当然只能严格写成“va.foo(1,4)”,而不能写成“va.foo(4,1)”。三是,灵活性不足,比如,我们只想给a赋值1,c赋值3,我们就得写成这样的“va.foo(1,0,3)”,而不能写成这样“va.foo(1,3)”。
为了解决上面的问题,我们在Groovy语言中来使用Map对象作为“foo”方法的参数,请看下面的示例:
class Testor4 {
private int a,b,c,d
def foo(Map map)
{
this.a = map['a']?map['a']:a
this.b = map['b']?map['b']:b
this.c = map['c']?map['c']:c
this.d = map['d']?map['d']:d
//todo
println "$a,$b,$c,$d"
}
static void main(args) {
def t = new Testor4()
t.foo([a:1,b:4])
}
}
可以看到,引入了Map作为参数,我们在Java编程中的可变参数所没有解决的三个问题都得到了解决。
你可能会说,这没有什么啊,Java语言也可以使用Map作为参数啊。是的,Java语言的确可以使用Map作为参数,但Groovy语言对Map参数所做的如下简化,就是Java语言所不及的了。
def t = new Testor4()
t.foo(a:1,b:4)
看到了吗?作为“foo”方法的使用者,我们可以完全不知道我们输入的参数是Map类型的,我们只知道把我需要给方法的两个参数a和b使用“t.foo(a:1,b:4)”的方式给方法;如果我们只想给a和c赋值,则可以使用这样的形式“t.foo(a:1,c:3)”。这真的是在可读性和敏捷性上大大提高。
看到了上面的代码,我们立刻会想起我们在GroovyBean中是如何初始化一个对象的。比如有如下一个GroovyBean:
class Person
{
String id
String name
String age
}
我们会使用如下的方式来初始化它:
def person = new Person(name:'Tom',age:23)
现在我们就可以知道其中的奥秘了。
正是因为在Groovy语言中使用Map对象作为输入参数大大增强了代码的可读性,我们把它作为DSL编程实现的一种方式。
下面,我们使用一个求两点之间的距离的方法来演示使用Map对象作为输入参数的DSL编程方式的例子。
class Testor5 {
def length(Map map)
{
def fx = map['x1']
def fy = map['y1']
def tx = map['x2']
def ty = map['y2']
return Math.sqrt((fx-tx)*(fx-tx)+(fy-ty)*(fy-ty))
}
static void main(args) {
def mc = new ExpandoMetaClass(Integer.class,true)
mc.getM <<
{
->
delegate as float
}
mc.getCm <<
{
->
def cm = delegate as float
cm/100
}
mc.initialize()
def t = new Testor5()
println t.length(x1:1.m,y1:200.cm,x2:4.m,y2:500.cm)
}
}
在上面的例子中,我们使用数学坐标来表达点,比如“x1:1.m,y1:200.cm”代表起点,而“1.m”代表1米的地方,“200.cm”代表200厘米。“x2:4.m,y2:500.cm”代表终点的坐标。这种表达法的可读性就非常强,对于“1.m”和“200.cm”这样的表达式,我们在《Groovy探索之运算符的重载 二》里详细的讲过,没看过的可以点击http://blog.csdn.net/hivon/archive/2008/07/15/2651693.aspx。
可以看到,“length(x1:1.m,y1:200.cm,x2:4.m,y2:500.cm)”用来表达两点(x1:1.m,y1:200.cm)和(x2:4.m,y2:500.cm)之间的距离,这样的可读性比较好,而且使用Map作为输入参数,实现起来也比较简单。
总之,在Groovy语言中使用Map作为输入参数,给我们带来编码的可读性,可以作为DSL的一种实现方式。
http://blog.csdn.net/hivon/archive/2008/10/07/3025552.aspx
villa123收录,使用标签:数据库,时间:2008-10-9 17:01:50 | 相关网摘,我也收藏
据国际媒体报道,10月7日消息,Oracle发布了Oracle JDeveloper 11g版本和可从OTN上免费下载的OracleADF11g。这个新版本经过几年的精心打造,包括超过200项新功能,这里我们择其主要亮点:
★ADF Faces 富客户端——最全面的支持AJAX的JSF 组件,并引入ADF数据可视化组件
★支持JDK 6 和Java EE 5.0
★集成用于调试和测试的WebLogic 10.3服务器
★扩展的数据库开发和建模特征
★ADF任务流——扩展JSF控制器,加入可重复使用的任务流,支持书签和回退,附加存储范围等。
★ JSF模板
★JSF页面片段,JSF区域以及声明的JSF组件
★最新的代码编辑器(快捷键,更容易的导航等)
★ 最新的Java内存和CPU 内存监视器
★增强的EclipseLink/TopLink集成
★ 带有调试和重构功能的JavaScript编辑器
★ 最新支持JUnit, Subversion, Ant, Facelets
★ 对ADF框架的更多其它改进,包括数据定制到UI,重构,调试,ADF商务组件
http://news.csdn.net/n/20081009/119741.html
villa123收录,使用标签:Web开发,时间:2008-10-9 17:01:26 | 相关网摘,我也收藏
10月8日消息,OpenLayers开发组自豪地声明OpenLayers 2.7的发布。作为最终版本,OpenLayers 2.7涵盖了201个显著特点。
OpenLayers.js的地址在:
现在开始使用2.7API。
源码下载地址不变:http://openlayers.org/download/.
新版本的主要特征,包括:
• 向量-行为:策略,协议,过滤器
• 改进的更高性能的向量变现
• 画布表现类
• 向量层的Z轴排序和Y轴排序
• 最新基础度量协议
• 最新OpenLayers(公开层)。需要AJAX接口
• 更巧妙的弹出功能……以及大量其他新特征和bug修改。详情请见:http://trac.openlayers.org/wiki/Release/2.7/Notes
OpenLayers简介
OpenLayers是一个开源的js框架,用于在您的浏览器中实现地图浏览的效果和基本的zoom,pan等功能。OpenLayers支持的地图来源包括了WMS,GoogleMap,KaMap,MSVirtualEarth等等,您也可以用简单的图片作为源,在这一方面OPenLayers提供了非常多的选择。
http://news.csdn.net/n/20081009/119750.html
villa123收录,使用标签:Web开发,时间:2008-10-9 17:00:50 | 相关网摘,我也收藏
【CSDN编译】10月8日消息,Adobe AIR在过去几个月里非常受欢迎。凭借其受欢迎程度,发布了许多新的应用。在此期间,我把在开发过程中遇到的一些错误总结了一下,希望这个清单可以帮助您在建立AIR应用时避免范同样的错误。
建立AIR应用的10个普遍错误
1、 应用程序过于依赖平台
2、 应用程序没有更新能力
3、 改变应用程序之后没有改变其ID
4、 没有脱机支持
5、 没有深入了解AIR
6、 用Custom chrome建立令人费解的用户界面
7、 没有使用无缝安装包
8、 没有加密敏感数据
9、 没有保留本地交互
10、 没有考虑脱离浏览器是否还能很好运行
原文链接:http://www.adobe.com/devnet/air/articles/10_common_mistakes_air.html
http://news.csdn.net/n/20081009/119751.html
villa123收录,使用标签:游戏开发,时间:2008-10-8 17:46:03 | 相关网摘,我也收藏
NPD Group今天公布了9月21日至9月27日一周北美PC游戏销量排行,本周排名是第一是依旧是EA其下策略作品《孢子》,排名第二位是EA的最新在线游戏《战锤在线:决战世纪》,本周意外地看到《乐高蝙蝠侠》上榜,这款游戏在xbox360平台上也卖得不错,本周EA占据着5款游戏的排名,暴雪只占据3 款作品。
Spore_front
1.《孢子》(Spore) - EA
2.《战锤在线:决战世纪》(Warhammer Online: Age of Reckoning) - Electronic Arts
3.《文明4之殖民统治》(Civilization IV: Colonization)- Firaxis
4.《孤岛危机:弹头》(Crysis Warhead) - Electronic Arts
5.《模拟人生2:公寓生活》(The Sims 2 Apartment Life Expansion Pack) - Electronic Arts
6.《魔兽世界:典藏版》(World Of Warcraft: Battle Chest)- Vivendi (Activision Blizzard)
7.《魔兽世界》(World Of Warcraft)- Vivendi (Activision Blizzard)
8.《模拟人生2 豪华版》(The Sims 2 Deluxe)- Electronic Arts
9.《乐高蝙蝠侠》(LEGO Batman)- Traveller's Tales
10.《魔兽世界:燃烧的远征》(World Of Warcraft: Burning Crusade)- Vivendi (Activision Blizzard)
http://www.cnbeta.com/articles/66510.htm
villa123收录,使用标签:.NET,时间:2008-10-7 18:00:40 | 相关网摘,我也收藏
Novell 旗下的 Mono 项目今日发布 2.0 版, 使在 Linux 平台上实现微软 .NET 框架项目获得巨大促进。Mono 2.0 是 Novell 主持的一个开源项目,目标是在 Linux 平台实现微软的 .NET 框架。随着最新的 2.0 版发布,二者之间的差距越来越小。
虽然 Mono 2.0 兼容微软 .NET 2.0,但和微软最新的 .NET 版本并不完全兼容。Mono 的努力非常重要,因为它的目标是在 Linux 上运行 .NET 程序。
Mono 项目主管 Miguel de Icaza 告诉 InternetNews.com,"我们正在某些地方赶上微软的 .NET。"
De Icaza 表示,Mono 项目的一直在做的是辨别开发者们用的是 .NET 的哪些部分。自 Mono 1.2.3 发布以来,MoMA (Mono 移植分析) 工具就出台,用来检查微软的 .NET 可执行程序,以便获得运行 .NET 所需要的所有东西。
得益于 MoMA 的统计结果,我们可以将人们最实用的东西优先对待。De Icaza 说,这意味着我们推迟了一些 API 的开发,比如,WPF 就不在我们目前的开发进度中。
WPF 是微软 .NET 3.0 的一部分,曾一度以代码名 Avalon 闻名。WPF 包含在 Windows Vista 和 Windows Server 2008 中,为开发者提供了一个图形子系统。 De Icaza 说,WPF 目前还没有被广泛采用。
IT 开发者和厂商之间的步调并不一致。他说,诚然,会有些早期采用者,他们喜欢最新的 API,但这些用户并不是 Mono 的用户,除此之外的普通开发者会和 Mono 保持一致。
除了不支持 WPF,Mono 2.0 还有其它一些问题。Novell 产品经理,Joseph Hill 表示,那些通过了 MoMA 分析的程序中,只有50%才100%兼容 .NET。50% 足可以说明 Mono 的进展状况,Mono 前面的版本的这一比例仅为10%。.NET 可以调用非托管代码以及老的函数库。这就带来一些麻烦,如果某个程序调用了老的函数库,而 Linux 上没有这种库,移植这样的程序就会出现问题。另外,有些函数是只用于 Windows 平台的,这样的函数,Mono 也不会移植。
从技术角度看,Mono 2.0 的主要改进是完全支持 Windows Form,这项工作 Mono 花费了几年的工夫。
Mono 2.0 对我们非常重要,我们已经完全支持 .NET 2.0。Hill 说,我们的 2.0 版工作已经做了很久,现在已经完全支持桌面组件,Windows Form,我们已经达到我们所认为的完全支持。
Windows Form 是 Windows 桌面平台的一个图形 API,Hill 承认,将 Windows Form 完整集成到 Mono 2.0 是一个艰巨的任务。可能其中最大组件就是内嵌的浏览器控件。Hill 解释,它是基于 Windows 平台的 IE 浏览器的,我们基于 Firefox 重写了这个控件,但必须保留原来 IE 控件的一些行为。
Novell 从2001年开始从事 Mono 项目,2004年推出第一个 Beta 版 Mono 1.0。Mono 2.0 是一个重大的版本升级,下一个版本 Mono 2.2 将于今年11月发布。Mono 2.2 会推出新的代码生成引擎,会产生更优质的代码。会对 Windows Form 以及其它内容做进一步改进。
http://www.cnbeta.com/articles/66425.htm
villa123收录,使用标签:游戏开发,时间:2008-10-7 13:34:38 | 相关网摘,我也收藏
暴雪10月份举办的第三届暴雪娱乐嘉年华会即将开幕,暴雪届时说不定会给我们来有关《暗黑3》的最新消息,事情刚好如我们所愿。近日暴雪在透露准备娱乐嘉年华会的事情的时候暗示了将在本届BlizzCon大会上宣布《暗黑3》中除已知的野蛮人及巫医之外的新职业的消息。届时还将讨论暴雪热门大作《星际争霸2》和《魔兽世界》等方面内容。
大会讨论内容如下:
《暗黑破坏神3》角色设计
《暗黑破坏神3》游戏设计
《暗黑破坏神3》的故事及艺术
《星际争霸II》的游戏形式
《星际争霸II》背景故事
《星际争霸II》美术创作
《魔兽世界》种族设计
《魔兽世界》PvP设计
《魔兽世界》美术创作
《魔兽世界》地下城及RAIDs
《魔兽世界》用户界面及插件
暴雪视觉艺术欣赏
暴雪音效及原声音乐创作
公司在“《Diablo III》角色设计”一栏中表示,玩家除了见证野蛮人原始蛮力迸发出的物理效果及巫医带来的可怖法力和瘟疫外,还将看到更多英雄涌现出来以保卫人类世界。
本届BlizzCon将于10月10号至11号在Anaheim会议中心举行,大会赞助商包括Intel、DELL、ATI,和Direct TV,后者将首次以高清信号转播这场盛会及幕后访谈(新签约用户免费,老用户需付39.95美元)。
http://www.cnbeta.com/articles/66347.htm
villa123收录,使用标签:Java,时间:2008-10-6 17:39:30 | 相关网摘,我也收藏
2002年2月发布的JDK 1.4已经服役了6.5年,将于今年的10月20日正式进入官方服务周期的终结流程(officially enter End of Service Life -EOSL)。JDK 1.4是运行时间最长和最成功的JDK版本之一,它的出现为JDK带来了很多新的功能,比如assert, regex, NIO, exception chaining等,以及java logging……回忆JDK 1.4的发布宛如昨日,岁月如梭,JDK 1.4也走到了尽头。
officially enter End of Service Life-EOSL意味着什么呢?
EOSL是Sun的产品生命周期政策的一部分,意味着官方的免费支持和更新将停止,不再针对这个版本进行bug fix和patch更新。如果你需要继续支持,只能购买Sun的附加服务,Sun能提供另外10年的附加服务。
现在使用JDK 1.4版本的用户仍然很多,而明年10月JDK 1.5同样也会进入EOSL,所以要为JDK的版本更换做准备了……
http://www.cnbeta.com/articles/66326.htm
villa123收录,使用标签:Web开发,时间:2008-10-6 15:09:30 | 相关网摘,我也收藏
从独立Flash平台专家——Colin Moock七月份在O’Reilly InsideRIA发表了一篇名为“The Charges against ActionScript 3.0”文章之后,Flash/Flex社区内的争论一时间硝烟四起。
“……很多Flash用户仍然对ActionScript 3.0中引入的一些工作流方面的变化望而生畏。这些改变本身真正存在问题的很少,但当他们集合到一起的时候,就对Flash用户典型的日常工作产生不可磨灭的影响。” Moock的这句话是引发整个争论的导火索。
Moock在文章中指出了9条对ActionScript 3.0的不满:
1. Flash CS3去掉on()/onClipEvent()以后,即使是简单的交互都很难创建。
2. 很难习惯没有加载的.swf文件。
3. 向上溯型 DisplayObject.parent使得父对象的clips很难控制。
4. 没有getURL()之后,连接比较困难。
5. 没有loadMovie(),加载.swf文件和图像都不方便。
6. ActionScript 3.0中其他一些错误导致编程非常麻烦。
7. 动态指向类库符号一点都不直接。
8. 向手动创建的文件域、所有影视片段、所有按钮添加定制功能很费时间。
9. 去掉duplicateMovieClip()之后,复制MovieClip实例变得非常困难。
Moock针对上面列出的每条都做了深刻的解释,也提出了一些建议。Atlanta Flash Community的Leif Wells表示有同感,他说:“毫不夸张地说,在我们向社区成员展示一些ActionScript 3.0的代码的时候,就遇到一些成员因此浑身冒冷汗。他们现在大都对 Flash Player 10的特性比较感兴趣,但很多人目前为止还无法掌握这些特性。”
http://www.infoq.com/cn/news/2008/10/actionscript3-debate
villa123收录,使用标签:Java,时间:2008-9-27 15:21:39 | 相关网摘,我也收藏
目前,JPA(Java Persistence API)的使用范围越来越广,作为Java EE 5.0平台标准的ORM规范,得到了诸如:Hibernate、TopLink、OpenJpa等ORM框架的支持,同时还是EJB 3.0的重要组成部分。JPA的宗旨是为POJO提供持久化标准规范。它能够脱离容器独立运行,方便开发和测试。本文将通过一个小实例来说明如何在Hibernate中使用JPA,来达到简化编程的目的。
开发环境 Eclipse 3.3.1 MyEclipse 6.0.1GA Tomcat 6.10 SQL Server 2000
hibernate-3.2.5.GA hibernate-annotations-3.3.0.GA
ejb3-persistence hibernate-commons-annotations-3.0.0.GA
本文是为后续的多种Ajax技术框架应用系列作一个前期准备,让大家先了解一下相比与以前的Hibernate ORM映射的不同之处,以及采用JPA所带来的好处。
为了保证程序能顺序运行,避免不同的Eclipse版本之间产生错误,大象强烈建议,下载源码后,按源码中的工程名,自己单独新建同一个工程,再将src和WEB-INF/lib目录下的所有文件COPY至对应的目录下。
1、创建Web Project
点击"File"->"New",选择"Web Project",在"Project Name"中输入ajax,点击"Finish"。下载本文后面需要用到的JAR包,加入到WEB-INF/lib目录下,在ajax工程中,文本采用的是UTF-8编码。
2、创建HibernateSessionFactory
传统的方法就是在工程名上点右键,选择”MyEclipse”->”Add Hibernate Capabilities”,然后就是按照提示一步一步做,不过在MyEclipse 6.0.1中添加Hibernate还是只能支持3.1,除非你选择” Add Spring Capabilities”,里面才有Hibernate 3.2的类库,要想完全兼容JPA,必须采用3.2以上版本。
这里大家直接使用源码中的HibernateSessionFactory,注意请先建包:com.ajax.core,HibernateSessionFactory中有一个地方需要改动,原来的写法是:
private static Configuration configuration = new Configuration(); 修改后为:
private static AnnotationConfiguration configuration = new AnnotationConfiguration(); 因为我们采用的是JPA注释方式来映射实体,另外AnnotationConfiguration这个类在hibernate-annotations.jar这个包中。
3、创建BaseDao
在com.ajax.core包下面新建BaseDao抽象类,里面定义的是持久化操作方法,有一点特别要注意,一定要在增加、删除、修改这几个方法中加入事务控制,不管是在BaseDao基类方法中加,还是在业务方法中加,一定要加事务控制,大象觉得在基类中加会比较好一点,这样做代码显得更少更简洁。如果不加事务控制,那么增、删、改这些操作都不会产生效果,因为默认情况下,它不会进行自动提交。在做这个例子的时候,这个问题曾经困扰了我好长时间。因此,请大家记住不要再犯和大象一样的错误!贴出部分代码,详情请看源码,里面有很全面的注释。
/**
* 抽象Dao类,用于持久化操作
* @author 菠萝大象
* @version 1.0
*/
public abstract class BaseDao {
private static Log log = LogFactory.getLog(BaseDao.class);
/**
* 获取Hibernate的Session对象
*/
public Session getSession(){
return HibernateSessionFactory.getSession();
}
/**
* 根据主键得到对象
*/
public T getObject(Class clazz, Serializable id){
return (T)getSession().get(clazz, id);
}
/**
* 保存对象
*/
public void saveObject(T t) {
Session session = getSession();
Transaction tx = beginTransaction(session);
try{
session.saveOrUpdate(t);
tx.commit();
}catch(Exception e){
tx.rollback();
log.error("保存对象失败");
}
}
/**
* 创建事务
*/
private Transaction beginTransaction(Session session){
return session.beginTransaction();
}
} 4、创建Employee
在com.ajax.employee.mode包下新建Employee类,这个就是POJO类,下面来详细说明里面的含义。
@Entity
@Table(name = "EMPLOYEE")
public class Employee implements java.io.Serializable{
private Integer employee_id; //人员ID(主键)
private String employee_name; //人员姓名
private String sex; //性别
private String birthday; //出生日期
private String address; //地址
@Id
@Column(name = "EMPLOYEE_ID")
@TableGenerator(
name="tab-store",
table="GENERATOR_TABLE",
pkColumnName = "G_KEY",
pkColumnValue="EMPLOYEE_PK",
valueColumnName = "G_VALUE",
allocationSize=1
)
@GeneratedValue(strategy = GenerationType.TABLE,generator="tab-store")
public Integer getEmployee_id() {
return employee_id;
}
public void setEmployee_id(Integer employee_id) {
this.employee_id = employee_id;
}
} 其它几个属性的getter和setter省略,这里我们要用到ejb3-persistence.jar,JPA的注解类就在这个包中,下面详细说明上面使用到的注解。
@Entity:通过@Entity注解将一个类声明为一个实体bean
@Table:通过 @Table注解可以为实体bean映射指定表,name属性表示实体所对应表的名称,如果没有定义 @Table,那么系统自动使用默认值:实体的类名(不带包名)
@Id:用于标记属性的主键
@Column:表示持久化属性所映射表中的字段,如果属性名与表中的字段名相同,则可以省略@Column注解,另外有两种方式标记,一是放在属性前,另一种是放在getter方法前,例如:
@Column(name = "EMPLOYEE_NAME")
private String employee_name; 或者
@Column(name = "EMPLOYEE_NAME")
public String getEmployee_name() {
return employee_name;
} 这两种方式都是正解的,根据个人喜好来选择。大象偏向于第二种,并且喜欢将属性名与字段名设成一样的,这样可以省掉@Column注解,使代码更简洁。
@TableGenerator:表生成器,将当前主键的值单独保存到一个数据库表中,主键的值每次都是从指定的表中查询来获得,这种生成主键的方式是很常用的。这种方法生成主键的策略可以适用于任何数据库,不必担心不同数据库不兼容造成的问题。大象推荐这种方式管理主键,很方便,集中式管理表的主键,而且更换数据库不会造成很大的问题。各属性含义如下:
name:表示该表主键生成策略的名称,这个名字可以自定义,它被引用在@GeneratedValue中设置的"generator"值中
table:表示表生成策略所持久化的表名,说简单点就是一个管理其它表主键的表,本例中,这个表名为GENERATOR_TABLE
pkColumnName:表生成器中的列名,用来存放其它表的主键键名,这个列名是与表中的字段对应的
pkColumnValue:实体表所对应到生成器表中的主键名,这个键名是可以自定义滴
valueColumnName:表生成器中的列名,实体表主键的下一个值,假设EMPLOYEE表中的EMPLOYEE_ID最大为2,那么此时,生成器表中与实体表主键对应的键名值则为3
allocationSize:表示每次主键值增加的大小,例如设置成1,则表示每次创建新记录后自动加1,默认为50
@GeneratedValue:定义主键生成策略,这里因为使用的是TableGenerator,所以,主键的生成策略为GenerationType.TABLE,生成主键策略的名称则为前面定义的”tab-store”。
这里大象想说下,网上有很多文章写的是strategy = GenerationType.AUTO或是strategy = GenerationType.SEQUENCE,采用SEQUENCE序列是因为Oracle数据中不支持identity自动增长,要想使用它,还得在数据库中创建一个序列,如果要更换数据库,那将是一个非常麻烦的事情。SEQUENCE生成方式我们暂且不谈,这里说下采用AUTO和IDENTITY的生成方式,本例采用的是SQL Server 2000作为数据库,所以如果想使用AUTO或是IDENTITY生成策略,则一定要对主键加上identity标识,如identity(1,1)。不过对于AUTO来说,是根据不同的数据库选择最合适的自增主键生成策略。如果使用MySQL,则主键要定义AUTO_INCREMENT,如果是Oracle,则要创建Sequence来实现自增。不管采用何种生成策略,增、删、改这些方法中一定要加入事务,否则数据是不会添加到数据库中滴~~~这是大象反复测试过的结果!
5、创建数据库及表
接下来,我们需要为本例创建一个数据库及必要的表。数据库名为ajax,表只有两个EMPLOYEE和GENERATOR_TABLE,下面是SQL脚本:
CREATE TABLE EMPLOYEE(
EMPLOYEE_ID int not null,
EMPLOYEE_NAME varchar (20) null,
SEX char (2) null,
BIRTHDAY varchar(10) null,
ADDRESS varchar(50) null,
CONSTRAINT PK_EMPLOYEE PRIMARY KEY (EMPLOYEE_ID)
)
CREATE TABLE GENERATOR_TABLE(
ID int not null,
G_KEY varchar(20) null,
G_VALUE int null,
CONSTRAINT PK_GENERATOR_TABLE PRIMARY KEY (ID)
)
INSERT INTO GENERATOR_TABLE VALUES(1,EMPLOYEE_PK,1) 如果你