笨笨的熊窝
度尽劫波兄弟在,相逢一笑泯恩仇
posts - 13,  comments - 162,  trackbacks - 5
      引言:本文不是从理论的角度来探讨三层架构,而是用一个示例来介绍如何建设一个三层架构的项目,并说明项目中各个文件所处的层次与作用。写本文的目的,不是为了说明自己的这个方法有多对,别人的肯定不对,而是希望给那些初学三层架构却不知从何入手的朋友提供一点帮助。因为网上的文章,大多是注重理论的介绍,而忽略了具体的实践应用,或者有示例但讲得不透彻。导致看了之后,理论上又学习了一遍,但还是不知道代码怎么写。所以想从这个方面入手写一下,让从来没做过三层架构的初学者也能照猫画虎,写出代码来。文章表述的是笔者个人对三层架构的认识,肯定有许多不足的地方,欢迎大家指正,小弟也会根据反馈来修改这篇文章。文中的代码是伪代码,仅用来阐明思路。
    正文:
    一提三层架构,大家都知道是表现层(UI),业务逻辑层(BLL)和数据访问层(DAL),而且每层如何细分也都有很多的方法。但具体代码怎么写,到底那些文件算在哪一层,却是模模糊糊的。下面用一个简单的例子来带领大家实战三层架构的项目,这个例子只有一个功能,就是用户的简单管理。
    首先建立一个空白解决方案,添加如下项目及文件
    1、添加ASP.NET Web Application项目,命名为UI,新建Web Form类型文件User.aspx(含User.aspx.cs
    2、添加ClassLibrary项目,命名为BLL,新建Class类型文件UserBLL.cs
   
3、添加ClassLibrary项目,命名为DAL,新建Class类型文件UserDAL.cs。添加SQLHelper引用。(这个是微软的数据访问类,也可以不用,直接编写所有的数据访问代码。我一般用自己写的数据访问类DataAccessHelper )。
    4、添加ClassLibrary项目,命名为Model,新建Class类型文件UserModel.cs
    5、添加ClassLibrary项目,命名为IDAL,新建Interface类型文件IUserDAL.cs
    6、添加ClassLibrary项目,命名为ClassFactory
    相信大家已经看出来了,这个和Petshop的示例没什么区别,而且更简单,因为在下也是通过Petshop学习三层架构的。但一些朋友对于这几个项目所处的层次,以及它们之间的关系,可能比较模糊,这里逐个说明一下:
    1User.aspxUser.aspx.cs
    这两个文件(以及文件所属的项目,下面也是如此,不再重复强调了)都属于表现层部分。User.aspx比较好理解,因为它就是显示页面了。User.aspx.cs有些人觉得不应该算,而是要划到业务逻辑层中去。如果不做分层的话,那么让User.aspx.cs来处理业务逻辑,甚至操作数据库都没什么问题,但是做分层的话,这样就不应该了。在分层结构中,User.aspx.cs仅应该处理与显示有关的内容,其它部分都不应该涉及。
    举例:我们实现用列表方式显示用户的功能,那么提取信息的工作是由BLL来做的,UI(本例中是User.aspx.cs)调用BLL得到UserInfo后,通过代码绑定到User.aspx的数据控件上,就实现了列表的显示。在此过程中User.aspx.csUI没有起到什么作用,仅是用来传递数据,而且因为实际编码中大部分情况都是如此的实现,所以使有些人觉得User.aspx.cs不应该算UI,而应该并入BLL负责逻辑处理。继续往下看,这时提出了一个新需求,要求在每个用户的前面加一个图标,生动地表现出用户的性别,而且不满18岁的用儿童图标表示。这个需求的实现,就轮到User.aspx.cs来做了,这种情况下User.aspx.cs才算有了真正的用途。
    2NewBLL.cs
    添加如下方法:
    public IList<UserInfo> GetUsers():返回所有的用户信息列表
    public UserInfo GetUser(int UserId):返回指定用户的详细信息
    public bool AddUser(UserInfo User):新增用户信息
    public bool ChangeUser(UserInfo User):更新用户信息
    public void RemoveUser(int UserId):移除用户信息
    此文件就属于业务逻辑层了,专门用来处理与业务逻辑有关的操作。可能有很多人觉得这一层唯一的用途,就是把表现层传过来的数据转发给数据层。这种情况确实很多,但这只能说明项目比较简单,或者项目本身与业务的关系结合的不紧密(比如当前比较流行的MIS),所以造成业务层无事可做,只起到了一个转发的作用。但这不代表业务层可有可无,随着项目的增大,或者业务关系比较多,业务层就会体现出它的作用来了。
    此处最可能造成错误的,就是把数据操作代码划在了业务逻辑层,而把数据库作为了数据访问层。
    举例:有些朋友感觉BLL层意义不大,只是将DAL的数据提上来就转发给了UI,而未作任何处理。看一下这个例子
    BLL
    SelectUserUserInfo userInfo)根据传入的usernameemail得到用户详细信息。
    IsExistUserInfo userInfo)判断指定的usernameemail是否存在。
    然后DAL也相应提供方法共BLL调用
    SelectUserUserInfo userInfo
    IsExistUserInfo userInfo
    这样BLL确实只起到了一个传递的作用。
   
但如果这样做:
    BLL.IsExistUserinfo userinfo
    {
          UerInfo user = DAL.SelectUserUser);
       
return (userInfo.Id != null); 
    }
    那么DAL就无需实现IsExist()方法了,BLL中也就有了逻辑处理的代码。
    3UserModel.cs
    实体类,这个东西,大家可能觉得不好分层。包括我以前在内,是这样理解的:UIßàModelßàBLLßàModelßàDAL,如此则认为Model在各层之间起到了一个数据传输的桥梁作用。不过在这里,我们不是把事情想简单,而是想复杂了。
    Model是什么?它什么也不是!它在三层架构中是可有可无的。它其实就是面向对象编程中最基本的东西:类。一个桌子是一个类,一条新闻也是一个类,intstringdoublie等也是类,它仅仅是一个类而已。
    这样,Model在三层架构中的位置,和intstring等变量的地位就一样了,没有其它的目的,仅用于数据的存储而已,只不过它存储的是复杂的数据。所以如果你的项目中对象都非常简单,那么不用Model而直接传递多个参数也能做成三层架构。
    那为什么还要有Model呢,它的好处是什么呢。下面是思考一个问题时想到的,插在这里:    
    Model
在各层参数传递时到底能起到做大的作用?
    在各层间传递参数时,可以这样:
    AddUseruserIduserNameuserPassword,)
    也可以这样:
    AddUseruserInfo
    这两种方法那个好呢。一目了然,肯定是第二种要好很多。
    什么时候用普通变量类型(int,string,guid,double)在各层之间传递参数,什么使用Model传递?下面几个方法:
   
SelectUserint UserId
    SelectUserByNamestring username
    SelectUserByNamestring usernamestring password
    SelectUserByEmailstring email
   
SelectUserByEmailstring emailstring password
    可以概括为:
    SelectUseruserId
    SelectUseruser
    这里用user这个Model对象囊括了usernamepasswordemail这三个参数的四种组合模式。UserId其实也可以合并到user中,但项目中其它BLL都实现了带有id参数的接口,所以这里也保留这一项。 
    传入了userInfo,那如何处理呢,这个就需要按照先后的顺序了,有具体代码决定。
    这里按这个顺序处理
    首先看是否同时具有usernamepassword,然后看是否同时具有emailpassword,然后看是否有username,然后看是否有email。依次处理。
    这样,如果以后增加一个新内容,会员卡(number),则无需更改接口,只要在DAL的代码中增加对number的支持就行,然后前台增加会员卡一项内容的表现与处理即可。 
    4UserDAL.cs
    public IList<UserInfo> SelectUsers():返回所有的用户信息列表
    public UserInfo SelectUser(int UserId):返回指定用户的相信信息
    public bool InsertUser(UserInfo User):新增用户信息
    public bool UpdateUser(UserInfo User):更新用户信息
    public void DeleteUser(int UserId):移除用户信息
    很多人最闹不清的就是数据访问层,到底那部分才算数据访问层呢?有些认为数据库就是数据访问层,这是对定义没有搞清楚,DAL是数据访问层而不是数据存储层,因此数据库不可能是这一层的。也有的把SQLHelper(或其同类作用的组件)作为数据访问层,它又是一个可有可无的东西,SQLHelper的作用是减少重复性编码,提高编码效率,因此如果我习惯在乎效率或使用一个非数据库的数据源时,可以丢弃SQLHelper,一个可以随意弃置的部分,又怎么能成为三层架构中的一层呢。
    可以这样定义:与数据源操作有关的代码,就应该放在数据访问层中,属于数据访问层
    5IUserDAL
    数据访问层接口,这又是一个可有可无的东西,因为Petshop中带了它和ClassFactory类工厂,所以有些项目不论需不需要支持多数据源,都把这两个东西做了进来,有的甚至不建ClassFactory而只建了IDAL,然后“IUserDAL iUserDal = new UserDAL();”,不知意义何在。这就完全是画虎不成反类犬了。
   
许多人在这里有一个误解,那就是以为存在这样的关系:BLLßàIDALßàDAL,认为IDAL起到了BLLDAL之间的桥梁作用,BLL是通过IDAL来调用DAL的。但实际是即使你如此编码:“IUserDAL iUserDal = ClassFacotry.CreateUserDAL();”,那么在执行“iUserDal.SelectUsers()”时,其实还是执行的UserDAL实例,而不是IUserDAL实例,所以IDAL在三层中的位置是与DAL平级的关系。
    通过上面的介绍,基本上将三层架构的层次结构说明了。其实,本人有一个判断三层架构是否标准的方法,那就是将三层中的任意一层完全替换,都不会对其它两层造成影响,这样的构造基本就符合三层标准了(虽然实现起来比较难^_^)。例如如果将项目从B/S改为C/S(或相反),那么除了UI以外,BLLDAL都不用改动;或者将SQLServer改为Oracle,只需替换SQLServerDALOracleDAL,无需其它操作等等。本来想在文中加入一些具体的代码的,但感觉不是很必要,如果大家觉得需要的话,我再补充吧。
    总结:不要因为某个层对你来说没用,或者实现起来特别简单,就认为它没有必要,或者摒弃它,或者挪作它用。只要进行了分层,不管是几层,每一层都要有明确的目的和功能实现,而不要被实际过程所左右,造成同一类文件位于不同层的情况发生。也不要出现同一层实现了不同的功能的情况发生。
posted on 2008-05-29 09:01 笨笨的考拉熊 阅读(4942) 评论(60)  编辑 收藏 所属分类: C#

FeedBack:
2008-05-29 09:03 | 鹰击长空      
学习了
  回复  引用  查看    
2008-05-29 09:11 | 路过秋天      
放到新手区就差不多了
  回复  引用  查看    
2008-05-29 09:20 | 陳龑      
不错,受益匪浅啊!
  回复  引用  查看    
2008-05-29 09:21 | 墙外行人      
学习啦,很有帮助,让我对三层的理解更深了一步
  回复  引用  查看    
2008-05-29 09:29 | 小生      
分層不是目的﹐只是手段
  回复  引用  查看    
2008-05-29 09:39 | datasky      
我觉得写得不错。
最反对那些自认为水平很高,到处泼冷水,不说实话的家伙。
文章好不好不是说你写的内容是否一定是研究高深技术的,我个人觉得只要很实用,又能客观的讲出自己的理由的,就是好文章。
楼主加油!
  回复  引用  查看    
2008-05-29 09:42 | 岁月无声是 [未注册用户]
这里有我的技术文章,欢迎大家来交流,并多多提出意见http://***/
  回复  引用    
2008-05-29 09:47 | zitsing      
非常的好!放这方对了。。希望继续。。
  回复  引用  查看    
2008-05-29 09:49 | 徐影 [未注册用户]
找这种资料很不好找

感谢楼主贴出自己的所想!!!
  回复  引用    
2008-05-29 09:50 | 一个农民 [未注册用户]
请楼主注意:
1,web 2.0
三层架构毫无用处
2,企业Web应用
三层架构正在被更灵活,更有利于整和的SOA取代
也许你会说SOA也是三层架构,SOA又分为重型的实现WS-XXX协议栈,和REST Jason 等轻型协议
3, 所有的ERP系统都是基于Data-Centric的, 就是可以根据数据来决定表现的, 说白了,就是以数据字典为中心的。改动数据字典自然就会映射到界面。
哎,建议大家去学一学SAP,以及最新AXAPTA ERP系统就会明白怎么回事了。


  回复  引用    
2008-05-29 09:51 | JiangKunJian [未注册用户]
真是温故而知新啊,又多学到一点,呵呵~~~
  回复  引用    
2008-05-29 09:55 | 大柳树 [未注册用户]
写的很好,老板放个代码吧
  回复  引用    
2008-05-29 10:18 | Eric zhou      
支持
  回复  引用  查看    
#14楼 [楼主]
2008-05-29 10:22 | 笨笨的考拉熊      
@大柳树
这几天如果有时间,会继续写一下RBAC的文章,到时会有代码的。
这样配合着实际项目的代码,看起来应该更有效果。

  回复  引用  查看    
2008-05-29 10:23 | GuoYong.Che      
个人观点:
Petshop可以学习它的思想,但照搬的意义不大,对于.NET 2.0来说,个人觉得还是使用类型化DataSet方便,一个DataRow实质就是一个Model,一个DataTable就是一个IList<T>。扩展性、灵活性虽差点,但开发效率不知强到哪去了。
  回复  引用  查看    
2008-05-29 10:34 | jerry.zheng [未注册用户]
学习...
  回复  引用    
2008-05-29 10:36 | 飞无痕落无声      
--引用--------------------------------------------------
GuoYong.Che: 个人观点:
Petshop可以学习它的思想,但照搬的意义不大,对于.NET 2.0来说,个人觉得还是使用类型化DataSet方便,一个DataRow实质就是一个Model,一个DataTable就是一个IList&lt;T&gt;。扩展性、灵活性虽差点,但开发效率不知强到哪去了。
-----------------------------------------------------
嗯,有道理!
  回复  引用  查看    
2008-05-29 10:38 | 退尽浮华,尽显本色      
楼上说的有道理。在2.0中我也是这样用的,以为很少人用这个方法,感到很困惑。今天找到知音了。
希望大家对此提出自己的看法。
  回复  引用  查看    
2008-05-29 10:40 | 退尽浮华,尽显本色      
--引用--------------------------------------------------
GuoYong.Che: 个人观点:
Petshop可以学习它的思想,但照搬的意义不大,对于.NET 2.0来说,个人觉得还是使用类型化DataSet方便,一个DataRow实质就是一个Model,一个DataTable就是一个IList&lt;T&gt;。扩展性、灵活性虽差点,但开发效率不知强到哪去了。
-----------------------------------------------------
实际上是方便多了,但是三层也照样可以方便,利用代码生成器就OK了,比生成每个DATASET更方便
  回复  引用  查看    
2008-05-29 11:23 | 大柳树 [未注册用户]
很喜欢linq的东西,用他搞分层应该简单很多了吧
  回复  引用    
2008-05-29 11:51 | Spring.Cheung      
Model改成Entity更合适吧
  回复  引用  查看    
2008-05-29 11:55 | 金色海洋(jyk)      
写得很好,对于那些对三层比较迷糊的人来说,能够让他们对三层的理解更清晰一些。

但是这种简单的三层方式(或者说是简单的应用),好像优势越来越不明显了。

比三层好的方式还有很多。


  回复  引用  查看    
2008-05-29 12:56 | 水言木      
我比较疑惑的是,当数据库表增加一个字段时,DAL要改,Model也要改,UI要改,只有BLL“可能”不要改,不知有何良策?(当然,虽然要改,但是比不分层时好改多了)
  回复  引用  查看    
2008-05-29 13:38 | 徐影      
受教了!!
  回复  引用  查看    
2008-05-29 13:45 | scotoma      
--引用--------------------------------------------------
GuoYong.Che: 个人观点:
Petshop可以学习它的思想,但照搬的意义不大,对于.NET 2.0来说,个人觉得还是使用类型化DataSet方便,一个DataRow实质就是一个Model,一个DataTable就是一个IList&lt;T&gt;。扩展性、灵活性虽差点,但开发效率不知强到哪去了。
--------------------------------------------------------
这个还是很方便的,其实都是以实现为目标的,但是要综合的太多的了.

感谢楼主的文章

  回复  引用  查看    
2008-05-29 14:26 | 私家侦探      
--引用--------------------------------------------------
水言木: 我比较疑惑的是,当数据库表增加一个字段时,DAL要改,Model也要改,UI要改,只有BLL“可能”不要改,不知有何良策?(当然,虽然要改,但是比不分层时好改多了)
--------------------------------------------------------

那是封装得不好,依赖太强了

比如在dal层用反射来获取model对象属性(当然还有其它办法),这样你要改字段增加字段的话只改model类就好了,其它地方有用到model的因为并不依赖model的具体属性名,自然能动态处理了,好好想想吧,我的实际办法是让model有索引,还有一个"数据库字段名"的字段,并且可以从索引获取字段名或者属性值
  回复  引用  查看    
2008-05-29 14:28 | haceknliu [未注册用户]
但实际是即使你如此编码:“IUserDAL iUserDal = ClassFacotry.CreateUserDAL();”,那么在执行“iUserDal.SelectUsers()”时,其实还是执行的UserDAL实例,而不是IUserDAL实例,所以IDAL在三层中的位置是与DAL平级的关系。
你的这段话,我不认同,虽然我不对三层结构还不太清楚,但从面向对象角度上来说,应该针对抽象编程,而不是细节.代码的可复用性,才较好
  回复  引用    
2008-05-29 14:55 | 金色海洋(jyk)      
@水言木
如果是添加、修改数据,可以参考我的那个表单控件。
对于增加字段绝对是一件很容易的事情。只下修改一个地方就可以了。
http://www.cnblogs.com/jyk/category/135295.html

如果是显示数据的话,其实直接使用DataTable 是很方便的。

@私家侦探
这个,反射是不是有一个效率的问题呢?如果是高访问量的网站的话,性能损失是不是太大了呢?
  回复  引用  查看    
2008-05-29 15:05 | Windie Chai(笑煞天)      
楼主对三层的理解让我受益匪浅,特别是最后关于替换任何一层而不需要修改其它层才是完美三层的分析。
  回复  引用  查看    
2008-05-29 15:15 | xiao_p(匿名) [未注册用户]
如果可以,打死我也不选择这种三层架构,虽然也是从这样的三层架构走过来的。

现在有那么多的好的方式可以去尝试,orm,linq,为什么还要选择三层这种最最古老的方式!

ps:我说的三层是楼主的三层,就是那些dao,daofactory之类的!
  回复  引用    
2008-05-29 15:39 | 私家侦探      
@金色海洋(jyk)
看过有些人用反射,我不是,我的替代方法在后面有说了啊,反射也不一定就是要一概反对,比如那种增删改操作,就可以用反射把界面的控件和model的各属性绑定在一起,不然如果有三十个属性那不是死了吗,这种情况的反射几乎是没有影响的,有时候写代码也不一定要太钻牛角尖,要灵活

我的model类不单单有对应表字段的属性还有表字段名常量还有一个索引,用索引获取属性名或设置,也可以获取model中各个表字段名,用工具生成的

不过这种做法我即将要淘汰掉掉了,因为不是主流做法,只有跟着主流走才不会吃亏
  回复  引用  查看    
2008-05-29 15:44 | 私家侦探      
@xiao_p(匿名)
确实啊,要用先进的主流的东西,毕竟是在公司里面混的
petshop是打理论基础的,
  回复  引用  查看    
2008-05-29 15:46 | 随风逝去(叶进)      
看了文章,也看了评论!~
赞美的也有,鄙视这种三层的也有
感觉,既然都是“手段”,而不是“目的”,那么谁的生命力强,谁就好。这个生命力包括性能、开发速度、维护、易用性等n多方面
  回复  引用  查看    
2008-05-29 15:58 | 炭炭      
凑个热闹。分层的目的正如LZ所说,是为了OCP,需求的变化不至于让我们到处修改我们的程序。用不用3层,就看你是否有更好的办法保证OCP就行了。先考虑维护而不是考虑效率就是正确的方式
  回复  引用  查看    
2008-05-29 16:33 | 金色海洋(jyk)      
@ 私家侦探
那你的方法和我的表单控件的思路不就是差不多了吗?
  回复  引用  查看    
2008-05-29 16:44 | Train-i [未注册用户]
不错
  回复  引用    
#37楼 [楼主]
2008-05-29 16:46 | 笨笨的考拉熊      
@haceknliu

可能是我的表述不清楚,这里只是想说明IDA,DAL的关系而已,而不是说不要接口编程,举例的代码就是面向接口而不是面向实现的。
我并不是说实际编程中IDAL没用,而是在理论上分析时,抛开IDAL可以让人看得更清晰而已。
  回复  引用  查看    
2008-05-29 20:51 | yellowTiger [未注册用户]
因该以数据字典为中心的。改动数据字典自然就会映射到界面。这样改动就很少了。界面元素根据XML配置文件动态生成,增删改也由配置文件控制,这样的话,某些需求改了,只要改改配置文件就OK了。
  回复  引用    
2008-05-29 23:13 | 私家侦探      
@金色海洋(jyk)
差太多了,我的实体可以for循环它的属性,嘿嘿,不依赖属性名
  回复  引用  查看    
2008-06-02 10:48 | 赵俊      
@水言木
对于数据图添加新字段基本上没有很好的办法,因为这种改动对程序来说属于完全陌生的行为,不过可以在数据库中添加参数表解决。
  回复  引用  查看    
2008-06-02 10:51 | 赵俊      
支持原创,反对那些只会起哄,不做实事的人!
  回复  引用  查看    
2008-06-06 12:02 | 静静 [未注册用户]
挺好的,希望尽快给代码啊。
  回复  引用    
2008-06-27 13:03 | scotoma      
认真详细的看了一遍发现理解了点了,还是得在项目的具体开发中来看.
  回复  引用  查看    
2008-07-05 18:00 | leeoo [未注册用户]
认真的看完了,学习了不少,不知道有代码没?慢慢学习。 如果有的话请发到我的邮箱里,谢谢啊!
  回复  引用    
2008-07-24 09:53 | 秋风落叶 [未注册用户]
其实我认为写程序用什么方法无所谓的,关键就是你程序的最后实现效果和效率

个人意见,请问各位现在用三层的牛人有多少,有多少人能写的把类都封装的完美的
  回复  引用    
2008-07-30 16:23 | 袁 [未注册用户]
也看文章,也看评论,也看自己的想法。
  回复  引用    
2008-07-30 17:01 | 袁 [未注册用户]
偶遇,照你的说法去做三层,顺手关了页面,做到半道又回来找,只记得笨笨的熊窝,幸亏只记得,博客园里,瞟见楼主的不吐不快,顺道看了看,很真实,很难得。值得一赞,只有真实的人才知道。不问辩得标不标准,至少我很钦佩。英雄惜英雄的感觉。
  回复  引用    
2008-07-30 17:03 | 袁 [未注册用户]
补充一句,“可惜咱都不是”,我怕埃板砖……
  回复  引用    
2008-07-31 11:04 | 哎哎,,路过 [未注册用户]
写的确实不错,我都忘记什么叫三层了,是你给我了回忆,谢谢!受教!~
  回复  引用    
2008-08-05 15:12 | aa523 [未注册用户]
@金色海洋(jyk)
简单是简单,但那个系统不是从简单才演化到复杂的.有时候那些SOA过于庞大了,,不是所有的人都是开发大型系统的,现在中小型的系统还是主流,SOA中的很功能就显得很多余了.关键还是合适就好,就看你在效率和可开发性,可维护性中找平衡点了.
我想应该更多的是吸收其中分层的思想,如果你能亲手搭建一个简单的三层的系统出来,相信会更深的体会到分层的意义.

  回复  引用    
2008-08-21 11:03 | 顶 [未注册用户]
好东西学习
  回复  引用    
2008-08-22 10:37 | 琳琳 [未注册用户]
讲得非常好,能感觉到作者的用心,非常感谢
  回复  引用    
2008-08-22 13:34 | 簡簡單單..      
温故而知新喔..
  回复  引用  查看    
#54楼 [楼主]
2008-08-23 10:04 | 笨笨的考拉熊      
让大家久等了,终于给出了实例代码。
欢迎大家访问,呵呵。
  回复  引用  查看    
2008-08-25 22:57 | 傲宇 [未注册用户]
好!
  回复  引用    
2008-08-25 22:58 | 傲宇 [未注册用户]
从来不说废话,好!
  回复  引用    
2008-09-17 14:56 | NoText      
例如如果将项目从B/S改为C/S(或相反),那么除了UI以外,BLL与DAL都不用改动;或者将SQLServer改为Oracle,只需替换SQLServerDAL到OracleDAL,无需其它操作等等。

说得太好了,学习了。
  回复  引用  查看    
2008-09-18 17:58 | 李训 [未注册用户]
很好,学习了
  回复  引用    
2008-09-23 09:43 | Known      
@水言木
有同感
  回复  引用  查看    
2008-10-11 15:06 | 小硕 [未注册用户]
好东西啊..~谢谢分享~~
  回复  引用    

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]