十年一剑

君子 自强不息 厚德载物

博客园 首页 新随笔 联系 订阅 管理
  7 Posts :: 0 Stories :: 1 Comments :: 0 Trackbacks

公告

2006年8月15日 #

Walter大师 发布了0.164版本的D语言编译器. 这次修改了很多Bug.加入了创建文档注释的宏. 最近我一直在使用.真心希望1.0版本的早日发布. 在http://larsivi.net/的blog中,我发现walter在讨论import的问题. 似乎就要发布1.0了.那里有好几个Pre1.0的帖子. 真让我狂喜. Jole有一篇文章,似乎关系到了D,收集. http://www.joelonsoftware.com/items/2006/08/01.html
posted @ 2006-08-15 22:53 葵花宝典 阅读(82) 评论(0) 编辑

2006年8月2日 #

做了4、5年的开发,大大小小的项目也做了不少,但以前都有一个致命的问题,不知不觉就会写出一个巨大的主程序出来,层次复杂,编码痛苦,调试困难。但似乎大家都认同这样的开发方式,虽然都知道界面和功能分离是好事情,但就是做不到。我自己也曾痛苦的思考过,但没有什么收效,似乎在Windows下的开发只能是这么痛苦。   一星期前买了<>,这一周可谓改天换地,每天都在阅读和思考中度过,想必武侠小说中的武功大进也就是这个意思了。虽然书还没看完,但是有些话实在是不吐不快。    什么是界面?界面就是功能的子集。没有哪个界面能反映所有的功能,但是若没有界面,对于最终用户来说又是不可忍受的,无论如何都不能指望让一个门卫学会输入复杂的命令来完成工作,虽然最终用户也包括专业人士,但这世界上终究普通人更多。在这样的前提下,可以认为功能永远比界面更宽泛,更有适应性,而GUI更狭窄,更具有特殊性,所以将界面和功能进行分拆也就成为一种必然趋势。   但是如何分拆?在Windows的世界里,一个普遍观点就是DLL。DLL很好,但是还不够好,因为无法直接使用、调试以及升级,带来的问题远比好处多。另一种方法就是在代码级进行分层,比如GUI一层,功能一层,再用胶合层将二者整合。且不论胶合层的不可复用和调试困难,就一条,如何能做到GUI崩溃的时候却不影响功能的实现?以前我做过的项目都是这样处理的,直接的后果就是项目越到后期问题越多,代码越不接受变化。调试花费了大量的人力物力,收效却未必好,功能的一点点小修改就会造成代码里出现意大利面条。你可以说只要前期的小心规划和仔细架构就能避免这些问题,但是谁能准确预测未来?无论做怎样的努力,你也不能保证现在的功能永远不变,永远不变的恰恰就是变。如果不能保持实现的稳定性和较好的移植性,这样的项目下场一般都不太好。   说了这么多废话,还是赶紧进入正题。谈谈这一周来的心得体会!   首先,变化是渐进的,非突变式的。如果能将变化的所在约束在一个比较小的代码范围内,修改就不会成为噩梦。怎么约束?就一个要求:在保证完整性的条件下让每个模块包含的功能尽量单一和足够小。首先是保证完整性,不是代码足够短,包含的实现足够少就是完整,要达到完整,就要让模块的各个部分做到不可分割和无需添加,按照古人的说法,就是增一分则太多,减一分则太少。这个要求虽然看起来很好理解,其实并没有什么标准答案,每个人心里都有自己的回答,正所谓仁者见仁,智者见智。其次是单一化和小型化,一般来说,范围太大的东西会造成人脑覆盖不全,比如一个功能,如果牵扯的部分过多,就会造成从底层到中间层,再到上层,全部都要思考到,估计没有几个人能做到,即使做到了,将来的维护和修改也会变成噩梦。相反,只要功能的涉及面够窄,就很容易进行思考和修改,这个道理应该没有什么问题。   既然要保证模块单一、小型化和保证完整,也就意味着这个模块可以认为是一个完整而单独的程序,无需外围程序的支持就可以单独运行和测试。从而引出我的最重要的观点:尽量用多进程来分拆程序。在Windows的世界里,多进程似乎是天生被忽略和鄙视的,从unix的观点看,其主要原因是Windows的设计中对进程的快速创建支持不够,造成对多进程的天然排斥和害怕。但是换一个思路看,多进程也许是目前最好的架构方式。底层的功能分拆成各个进程单独运行,通过ipc和上层的GUI进行交互,胶合层薄了,移植性增强了,调试容易了,功能演进也不再成为噩梦。需求永远是渐变的,所以进程的渐变也就成为可控的行为。   多进程间的传递方式一般有这么几种:共享内存,管道(pipe),信号,消息, socket。其中共享内存适宜于大量数据的即时传递,速度快,容量大。但使用共享内存时需要仔细考虑读写冲突问题,一般的解决办法是用全局锁,但是锁的存在必然会造成效率的下降,所以能不用锁就尽量不要用。pipe的速度和容量都没有共享内存好,但是用来传递命令和返回值还是很适合的。信号和消息的方式一般会和操作系统联系紧密,个人不太喜欢。最后是socket,对于异地交互而言,socket是目前很常用的手段,甚至本地进程间通讯也可以使用。但是由于和网络有关,所以同步性不好保证,需要辩证的使用。   说了这么多,举个例子说明一下。假设现在要做一套点菜软件供酒店使用,其基本功能包括人员管理,桌台管理,点菜管理,结账以及后台管理五个功能模块。按照单进程的方式就是将所有功能整合在一起,系统启动时加载所有的功能,一旦某个模块出现问题,则必须重新启动程序,而且各个模块之间很容易发生资源冲突和请求冲突。如果换成多进程方式,让我们看看有什么不同。首先是所有的功能最终目的地都是数据库,那么可以开发一个后台进程专门所有负责针对数据库的请求,通过pipe或者共享内存来接收命令和返回结果,那么程序或者说具体代码块之间的接口就是单一的pipe或共享内存了。同时,即使某一个程序运行错误也不会造成整体失败,只需要重起失败的部分即可。当然了,这种方式下存在一个问题,就是效率的降低,但是对于大多数的应用来说,稳定性的提高远比效率的降低要重要,而且随着硬件水平的不断提高,效率总是可以达标的。   数据库处理分拆出去后,剩下的就很好处理了,人员、点菜、桌台等管理模块都作为单独的后台程序出现,最后GUI部分只需要和各个共享内存和pipe打交道即可,无需只要具体的逻辑处理和功能实现,而且各个后台程序还可以复用,比如人员管理可以挪到客房服务系统中,甚至是其他系统。GUI随时可以替换,实现了功能和界面的分离,同时系统崩溃的几率大大降低,升级和售后也方便很多,永远不要把最终用户想的太愚蠢,很多时候人们还是蛮有求知欲的。   更多的细节需要自己整理,这里只是给出了一个框架,起码我现在的项目已经开始这样做,效果嘛,半年后知道了。
posted @ 2006-08-02 16:03 葵花宝典 阅读(207) 评论(0) 编辑

2005年11月19日 #

美国波士顿犹太人大屠杀纪念碑上刻着马丁·路德·金的一段话:

“当初他们追杀共产主义者,
我不是共产主义者,
我不说话;

接着他们追杀犹太人,
我不是犹太人,
我不说话;

后来他们追杀工会成员,
我不是工会成员,
我继续不说话;

此后,
他们追杀天主教徒,
我不是天主教徒,
我还是不说话;

最后,
他们奔我而来,
再没有人站起来为我说话了。”

中国新闻周刊:拘捕业主 http://www.chinanews.com/news/2005/2005-11-17/8/652699.shtml
律师李新生的公开信           http://blog.hexun.com/louzhu/viewarticle.aspx?articleid=1404686

posted @ 2005-11-19 10:39 葵花宝典 阅读(218) 评论(0) 编辑

2005年11月16日 #

转载 http://www.codeproject.com/cs/database/SqlWrapper.asp
源代码本地下载: 
使用SQLWrapper库,你可以写非常少的代码来创建你的数据访问类.

简介
你曾经在你的项目中创建数据访问层吗?你很可能创建了一个类或者一些类,他们包含几个方法,调用了存储过程或者执行一个SQL语句.如果数据库包含很多表,这是个很烦人的工作过程.最糟糕的是,这个方法要很多的同样的步骤(创建一个命令对象,填充它的属性,执行,然后返回结果).并且很少包含其他的逻辑.你有两个方法:手工写这些代码,或者(自动)生成他们.这两个方法,你会有很多源代码.

以前我在用这两个方法时,我就感觉在哪里有更容易的方法.AutoSprocTool给我一个开发SqlWrapper库的想法.

一个简单的例子
让我们写两个类,他们和Northwind数据库一起工作.一个类Orders1使用通常的方法.
差异是显而易见的.Orders2 包含了很少的代码但是他们使用起来差不多相同,仅仅是创建的方法不同.

public class Orders1
{
    
private SqlConnection m_connection = null;

    
public SqlConnection Connection
    
{
        
get{return m_connection;}
        
set{m_connection = value;}
    }


    
public DataSet CustOrdersDetail(int OrderID)
    
{
        SqlCommand cmd 
= new SqlCommand("CustOrdersDetail", m_connection);
        cmd.CommandType 
= CommandType.StoredProcedure;
        cmd.Parameters.Add(
"@OrderID", SqlDbType.Int);
        cmd.Parameters[
"@OrderID"].Value = OrderID;
        SqlDataAdapter da 
= new SqlDataAdapter(cmd);
        DataSet ds 
= new DataSet();
        da.Fill(ds);
        
return ds;
    }


    
public int CountByEmployee(int EmployeeID)
    
{
        SqlCommand cmd 
= new SqlCommand(
            
"select count(*) from Orders where EmployeeID=@EmployeeID"
            m_connection);
        cmd.CommandType 
= CommandType.Text;
        cmd.Parameters.Add(
"@EmployeeID", SqlDbType.Int);
        cmd.Parameters[
"@EmployeeID"].Value = EmployeeID;
        
int count = (int)cmd.ExecuteScalar();
        
return count;
    }

    
}


另一个使用SqlWrapper,
public abstract class Orders2 : SqlWrapperBase
{
    
public abstract DataSet CustOrdersDetail(int OrderID);

    [SWCommand(
"select count(*) from Orders where EmployeeID=@EmployeeID")]
    
public abstract int CountByEmployee(int EmployeeID);
}


现在让我们看看如何使用这些类.
SqlConnection cnn 
= new SqlConnection(
    ConfigurationSettings.AppSettings[
"ConnectionString"]);
cnn.Open();

// working with the ordinary class
Orders1 orders1 = new Orders1();
orders1.Connection 
= cnn;
DataSet ds1 
= orders1.CustOrdersDetail(10248);
int count1 = orders1.CountByEmployee(6);

// working with the wrapped class
Orders2 orders2 = (Orders2)WrapFactory.Create(typeof(Orders2));
orders2.Connection 
= cnn;
DataSet ds2 
= orders2.CustOrdersDetail(10248);
int count2 = orders2.CountByEmployee(6);

 

SqlWarpper如何工作
为了创建一个包装类,你要从SqlWrapperBase继承,并且定义抽象方法,使用自定义属性定义要执行什么,如何获得和获得什么结果.如果没有指定方法属性,那么方法的名称将被用做存储过程名称.你也可以定义任意数量的具体方法,如果你要在执行sql语句之外,还有一些更多的逻辑.然后,你可以通过调用WrapFactory.Create()方法,创建定义类的一个对象.这个方法使用System.Reflection.Emit 命名空间的类,为抽象方法添加执行.例如,下面的方法:

[SWCommand("select count(*) from Orders where EmployeeID=@EmployeeID")]
public abstract int CountByEmployee(int EmployeeID);

将被执行为:
[SWCommand(
"select count(*) from Orders where EmployeeID=@EmployeeID")]
public int CountByEmployee(int EmployeeID)
{
    MethodInfo method 
= (MethodInfo)MethodBase.GetCurrentMethod();
    
object[] values = new object[1];
    values[
0= EmployeeID;
    
object obj = SWExecutor.ExecuteMethodAndGetResult(
            m_connection, 
            m_transaction, 
            method, 
            values, 
            m_autoCloseConnection);

    
return (int)obj;
}


SWExecutor.ExecuteMethodAndGetResult() 方法执行了主要的工作.它创建了SqlCommand和返回了执行的结果. 为了这个用途,变量method提供了下面的信息:
command text (命令)
command type (命令类型)
execution method (执行的方法名称)
parameter names (参数名称)
parameter data types (参数类型)
parameter directions (参数方向)
parameter sizes, scales and precisions (参数的大小,范围,精度)
behavior in case null or DBNull is returned instead of a scalar value
如果没有返回一个精度值,而是返回了空或者DBNull时的行为.
behavior in case DBNull value must be passed to a parameter
如果必须传递一个空值作为参数时的行为.
所有的这些信息都将通过方法签名提供,其他可选的通过方法属性和参数属性.

下面的图显示了SqlWrapper类直接的联系
 


SqlWrapperBase

SqlWrapperBase类是所有的包装类的基本类,它包含了下面的属性:
Connection 属性
Transaction 属性
AutoCloseConnection 属性.如果为真,那么连接在每次的命令执行完毕就会自动关闭.
你可以在你的包装类中使用上面的属性,因为他们是protected.

SWCommandAttribute

这是一个可选的方法属性,包含了下面的属性.

CommandType可以是下面的值:SWCommandType.Text,SWCommandType.StoreProcedure和SWCommandType.InsertUpdate.这几个值类似于System.Data.CommandType枚举的值. SWCommandType.InsertUpdate 将在以后讲述.默认值是SWCommandType.Text.
CommandText 包含了一个命令文本.这依赖于CommandType属性值.
ReturnIfNull包含了一个值,如果执行了一个命令返回了空,那么将返回此值.
MissingSchemaAction是一个值,SqlDataAdapter.MissingShemaAction.默认是Add.

除了CommandText属性以外都是可选的属性.

重要:如果这个属性省略了,那么方法名称将被用做CommandText属性,CommandType属性将等于SWCommandTYpe.StoreProcedure.

SWParameterAttribute

这是一个可选的参数属性,包含下列属性:
Name:包含了参数名称,如果省略,使用方法参数名称
SqlDbType,包含了值的类型,对应了SqlParameter.SqlDbType属性.
Size 包含了命令参数的大小,对应了SqlParameter.Size属性.
Precision 包含了参数的精度.对应了SqlParameter.Presision
Scale 包含了参数的小数位数.对应了SqlParameter.Scale.
TreatAsNull 包含了被翻译成DBNull的值.在数字参数时很有用.
ParameterType 包含了下列值:SWParameterType.Default, SWParameterType.SPReturnValue, SWParameterType.Key 和 SWParameterType.Identity.默认值是SWParameterType.Default.

所有的属性都是可选的.
当ParameterType属性值是SWParameterType.SPReturnValue 时,即这个方法参数包含一个存储过程返回值,这个参数必须是传递参考的.

当SWCommandAttribute.CommandType值为SWCommandType.InsertUpdate时,ParameterType属性值可能是SWParameterType.Key 和 SWParameterType.Identity

InsertUpdate( 插入-更新 ) 命令
一个 INSERT 或者 UPDATE SQL语句,非常琐碎,但是却很常用.我在SqlWrapper库中添加下面的命令和参数,可以简单第创建插入和更新数据到表的方法.
SWCommandType.InsertUpdate标识创建一个 特定的  插入-更新 表达式.
SWCommandAttribute.CommandText 必须是一个表的名称.
SWParameterType.Identity标识 参数是一个标识表列,用来标识一个行.这个值必须被传递参考.
SWParameterType.Key 标识 这个参数是一个主键的一部分(不是标识表列),用来标识一个行.

下面是两个 插入-更新 的例子.

 

1.方法定义:
[SWCommand(SWCommandType.InsertUpdate, 
"Shippers")]
public abstract void ShippersInsertUpdate
    (
    [SWParameter(SWParameterType.Identity)]
ref int ShipperID,
    [SWParameter(
40)]string CompanyName,
    [SWParameter(
24)]string Phone
    );
SQL语句:
if(@ShipperID is NULL) 
begin  
    insert into [Shippers]([CompanyName], [Phone]) 
    values(@CompanyName, @Phone)  
    
    select @ShipperID 
= SCOPE_IDENTITY() 
end 
else 
begin  
    update [Shippers] 
set 
    [CompanyName]
=@CompanyName, 
    [Phone]
=@Phone 
    where [ShipperID]
=@ShipperID  
end
2.方法定义:
[SWCommand(SWCommandType.InsertUpdate, 
"Order Details")]
public abstract void OrderDetailsInsertUpdate
    (
    [SWParameter(SWParameterType.Key)]
int OrderID,
    [SWParameter(SWParameterType.Key)]
int ProductID,
    Decimal UnitPrice,
    Int16 Quantity,
    
float Discount
    );

SQL语句:

update [Order Details] 
set 
[OrderID]
=@OrderID, 
[ProductID]
=@ProductID, 
[UnitPrice]
=@UnitPrice, 
[Quantity]
=@Quantity, 
[Discount]
=@Discount 
where [OrderID]
=@OrderID and [ProductID]=@ProductID  

if (@@rowcount = 0)  
    insert into [Order Details]([OrderID], [ProductID], [UnitPrice], 
    [Quantity], [Discount]) 
    values(@OrderID, @ProductID, @UnitPrice, @Quantity, @Discount)

同你看到的一样,第一个例子中@ShipperID和NULL比较,默认的,这个值可能合适的是参数等于或者小于0.如果你要你能设置其他的值,你可以设置SWParameter.TreatAsNull属性,那么你设置的值将被转化为null.

在你的应用程序中创建数据访问层

SqlWrapper苦包含了基本的类:DataAccessLayerBase,它能够被利用很少的代码定制你自己的自定义数据访问层(DAL).所有要做的工作就是继承DataAccessLayerBase类,然后定义你要包装的类的属性如下:
public YourWrapperClass YourPropertyName
{
    get
    {
      return (YourWrapperClass)GetWrapped();
    }
}

这就是全部了.你可以添加任何其他你需要的成员.这个DAL的例子能创建上边的类Orders2,UserClass1.
public class MyDAL : DataAccessLayerBase
{
    public UserClass1 UserClass1{get{return (UserClass1)GetWrapped();}}
    public Orders2 Orders2{get{return (Orders2)GetWrapped();}}
}

这是一个DAL类的图:
 

这有一个如何使用你的数据访问层的例子:
MyDAL dal = new MyDAL();
dal.Init(cnn, true, true);
int c = dal.Orders2.CountByEmployee(6);
DataTable dt = dal.UserClass1.Method1();

在你使用一个DAL内的一个对象类,你要调用一个重载方法Init(),它继承于DataAccessLayerBase类.

public void Init(SqlConnection connection, bool autoCloseConnection,
          bool ownsConnection);
public void Init(string connectionString, bool autoCloseConnection);

这些方法非常重要.因为在你连接设置属性外,他们调用一个私有方法,GenerateAllWrapper(),它列举了你DAL层中类的所有方法.创建包装对象和保存它们到一个私有的哈希表,m_swTypes:
private void GenerateAllWrapped()
{
    MethodInfo[] mis = this.GetType().GetMethods();
    for(int i = 0; i < mis.Length; ++i)
    {
        Type type = mis[i].ReturnType;
        if(type.GetInterface(typeof(ISqlWrapperBase).FullName) ==
                 typeof(ISqlWrapperBase))
        {
            if(mis[i].Name.StartsWith("get_"))
            {
                if(!m_swTypes.ContainsKey(mis[i].Name))
                {
                    ISqlWrapperBase sw = WrapFactory.Create(type);
                    m_swTypes[mis[i].Name] = sw;
                }
            }
        }
    }
}

你曾经定义的属性,调用包含方法GetWrapped(),将查找一个调用方法的名字,从m_swTypes返回一个正确的对象
protected ISqlWrapperBase GetWrapped()
{
    MethodInfo mi = (MethodInfo)(new StackTrace().GetFrame(1).GetMethod());
    ISqlWrapperBase res = (ISqlWrapperBase)m_swTypes[mi.Name];
    if(res == null)
    {
        throw new SqlWrapperException("The object is not initialized.");
    }
    return res;
}

DataAccessLayerBase 使用下面三个方法,来简单第支持事务.
BeginTransaction() 和BeginTransaction(IsolationLevel iso) 打开一个新的事务.
RollbackTransaction() 回滚一个打开的事务.
CommitTransaction() 提交一个打开的事务.
包装类的Connection, Transaction 或者 AutoCloseTransaction属性将自动更新,当它们改变的时候.

另外,DataAccessLayerBase类还有几个方法(ExecuteDataSet(), ExecuteDataTable(), ExecuteScalar()和ExecuteNonQuery()),帮助你在特殊查询时使用sql语句.

在CodeProject上看了数据库的文章,感觉很好.性能也可以(感觉和访问数据库比,性能可以不计).就翻译了,水平有限,请指正. (翻译的不好的例子)
关于性能:
 1.利用Emit减少性能损失http://yok.cnblogs.com/archive/2005/11/03/267952.html
2.反射性能分析: http://www.chinaitclub.org/forums/350/ShowPost.aspx

注: 在.NET2.0中,插入和更新不能使用,发生错误.

posted @ 2005-11-16 08:21 葵花宝典 阅读(385) 评论(1) 编辑

2005年11月12日 #

为了体现良好的用户体现,你可能要使用多线程.

单线程在完成一个冗长任务时会让界面停止绘画.异步调用则产生多个线程来完成任务,却有机会更新界面.

在.NET1.1中,使用多线程是很麻烦的事情,你要声明委托,实现函数,进行异步调用,在回调函数中了解工作状态.代码下载:

这很麻烦,实际上我们使用多线程,就是为了1.完成一个异步的任务,2.在界面上显示进度的任务,3.用户可以取消任务.既然如此,为什么要这么麻烦呢?在.NET2.0中就有一个组件,可以完成类似的任务了!而且,有人实现了1.1的组件,它的名字就是BackgroundWorker.它使用起来非常简单,组件公开了三个事件,可以完成执行任务,显示进度,取消任务.拥有两个属性.
   this.m_BackgroundWorker.WorkerReportsProgress = false;
   this.m_BackgroundWorker.WorkerSupportsCancellation = true;
   this.m_BackgroundWorker.DoWork += new ThreadHelper.DoWorkEventHandler(this.OnDoWork);
   this.m_BackgroundWorker.RunWorkerCompleted += new ThreadHelper.RunWorkerCompletedEventHandler(this.OnCompleted);
这个组件和.NET2.0的一样!用法都相同.这就方便执行行后台线程了!

1.1的组件包含在 IssueVision中.下载
2.0的在System.ComponentModel.命名空间(也是BackgroundWorker名称都一样). 2.0例子请下载



链接:

Creating a better BackgroundWorker: CancelImmediately and other goodies

posted @ 2005-11-12 10:04 葵花宝典 阅读(436) 评论(0) 编辑

2005年11月11日 #

摘要: 数据库设计经验谈(转载) 出处:google搜索一个成功的管理系统,是由:[50% 的业务 + 50% 的软件] 所组成,而 50% 的成功软件又有 [25% 的数据库 + 25% 的程序] 所组成,数据库设计的好坏是一个关键。如果把企业的数据比做生命所必需的血液,那么数据库的设计就是应用中最重要的一部分。有关数据库设计的材料汗牛充栋,大学学位课程里也有专门的讲述。不过,就如我们反复强调的那样,再...阅读全文
posted @ 2005-11-11 11:07 葵花宝典 阅读(144) 评论(0) 编辑

摘要: 背景: 我开发MIS应用程序.使用DOT.NET.喜欢对象编程.性能很重要.由于以上要求,我考察了很多程序访问数据的方法.一种是ORM,一种是直接使用ADO.NET.当然数据访问的问题要从数据表一直研究到用户的界面才算完成任务.首先,因为使用对象,所以ORM吸引了我.研究了很多ORM.NET,.ibatis.net的DataMap很好用.用它调用存储过程很好.还有速马的XORMS,是最简单好用的....阅读全文
posted @ 2005-11-11 10:59 葵花宝典 阅读(135) 评论(0) 编辑

仅列出标题