【开源】QuickPager 分页控件的内部结构,和OO原则与设计模式

时间:2022-04-28
本文章向大家介绍【开源】QuickPager 分页控件的内部结构,和OO原则与设计模式,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

关键字:提出需求、需求分析、原则、设计模式、索引

     先说一下讨论的范围:使用数据库保存信息的项目,b/s结构,asp.net编写。请不要讨论这个范围之外的事情哦,谢谢!

     这里想说的并不仅限于一个控件,而是一个关于分页的解决方案。信息都是放在数据库里的,在b/s结构里面一次提取所有的数据显示并不是一个好的方法,所以就需要一个把数据分成多个页的形式来显示。关于分页的解决方案有多种,一种实现方式可以用一个“分页控件”(我的解决方案),也可以用其他的方式来体现(比如LinQ、ORM等)。这里想说的是我的这个解决方案,我想从提出需求、需求分析到解决方案的步骤来说明。

一、提出需求

     客户的信息(产品信息、员工信息、合同信息、金额相关等)都会放在数据库里面,由于是采用b/s结构,信息多了如果不分页的话,显示起来就会比较慢。数据库有多种类型,asp.net常用的是SQL Server ,小一点的网站会采用Access,数据量多了,或者比较重要的会采用Orcale。避免版权问题的会采用mySQL。还有特殊的会采用其他的数据库。

     分页方式也会有多种,网站为了照顾SEO,一般要采用URL的方式来分页(包括URL重写)。而OA、CRM这一类的(包括网站的后台管理)就不必考虑SEO了,采用Postback的分页方式会更方便,可以很容易的保存状态,比如查询条件等。

     总结一下就是:

          1、多种数据库(SQL Server、Access、Orcale等)。 2、多种分页方式(URL、Postback等)。 3、提高提取数据的速度。 4、对SEO要友好。 5、使用要方便。 6、便于阅读、便于扩展。 7、支持多种显示数据的控件(比如GridView、repeater等)。

二、需求分析

     要求基本就是这么多了,那么怎么来实现呢?我们先开看看分页的基本步骤:           1、绘制UI,生成分页事件或者URL地址。 2、选择分页算法。 3、提交给数据库。 4、得到记录集,绑定到控件。

第一步是比较独立的,每一个方案就差不多会把它独立出来。而下面的就不太一致了。基本就是这样。

     然后再来看看常用的几种解决方案。

          1、GridView

               一般简单的可以使用GridView自带的分页功能来实现,优点就是使用起来非常的方便,但是他有一个明显的缺点,就是记录多了会比较慢,原因是他是把所有的数据都提取出来放在内存里面,然后在计算显示哪些数据。

2、LinQ

     利用LinQ来分页的话,那么他就会把生成分页算法(SQL语句)、提交到数据库、得到记录集、填充到实体类都包含进去了。ORM好像也是这样(没用过ORM,只好用“好像”这个词了)。

3、AspNetPager      就是吴旗娃的分页控件,他只实现了第一部分,其他的都要自己另想办法了。最近又推出了Socut.Data.dll,可以用它来提取数据,但是这样又把原来的存储过程给“扔”掉了。另外配合起来也不是很“默契”,或者说不是“无缝连接”吧。

4、QuickPager      就是我的分页控件了,我要把这几个都包含进去,目的就是要简化操作,让使用的时候达到最简单。

5、其他的方案      这个我就不太了解了,应该还有很多的分页方案的,只是我还不太了解。

三、如何解决

     分页控件的基本结构已经完成了,又看了王涛的《你必须知道的.net》和两本设计模式的书(都还没有看完),不能白看呀,理论联系实际,实际配合理论,看看分页控件的内部代码的设计方式有哪些优缺点,符合了哪些原则,违反了哪些原则,还有和哪些设计模式有点像。

     多种数据库,一般是SQL Server2000、SQL Server2005、Orcale、mySQL这几种数据库,而这几种数据库对于“分页算法”又各有不同,SQL Server2000只能用表变量、颠倒Top、Max等,而SQL Server2005可以使用Row_Number,Orcale可以使用number、mySQL可以使用limit。各个数据库有各自的特点。虽然一个项目一般只用一种数据库,只考虑一种数据库的情况也是可以的,但是给自己增加一点难度,我的分页方案可以适合多种数据库,而且还可以扩展。

OO原则:

     1、单一职责。           从表面上看,QuickPager是严重违反了这个原则,不仅负责绘制UI,还负责拼接SQL语句,还要处理回发事件,还要提交给数据库,还要绑定显示数据的控件。一个分页控件要实现这么多的事情,是不是会很复杂、耦合度也高了,严重的违反了单一职责呢?

简单的看确实是这样,但是仔细看一下分页控件内部结构,就会发现不是这样的。

class QuickPager:有一点“实体类”的感觉,他本身基本什么功能都不能实现,只是负责记录一些分页需要的信息。

class PageSQL:负责分页算法,说白了就是拼接SQL语句的。当然不是随意的拼接,而是按照分页算法来拼接。分页算法又有好多种。

class PageUI:绘制UI,包括总记录数、总页数、第一页、上一页、下一页、页号导航、分页事件等。

class PageGetData:负责把PageSQL生成SQL语句交给“数据访问函数库”,然后返回记录集。

          这么看的话,每一个类的职责都是比较单一的,一个类负责一件事情,实现一个功能,和在一起配合工作,才实现了最终的分页效果。

如果有变化的话只需要修改一个类就可以,不影响其他的类。比如要加一个分页算法的话,只需要添加一个子类继承PageSQL就可以了。对其他的类,和其他的分页算法都没有关系。

这样是不是说可以符合了单一职责呢?

2、开放封闭原则。           上面说了,要增加一个分页算法的话,只需要加一个子类就可以了,不用修改其他的代码,这个就是开放封闭原则吧。同样,URL分页、Postback分页也是这样的思路。

3、依赖倒置           就是依赖抽象,分页控件就是根据分页的需求进行的一个“抽象”,内部的各个类也是依据抽象才能够协同工作的。

设计模式

1、策略模式      引用:http://www.cnblogs.com/justinw/archive/2007/02/06/641414.html

     定义:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。

这个图和上面的是不是很像?

     Context(应用场景): 是QuickPager      Strategy(抽象策略类):是PageSQL、PageUI      ConcreteStrategy(具体策略类):是SQL_Max、SQL_Row_Number等和UI_PostBack、UI_URL。

     一个分页控件要提供多种分页算法,一个分页算法就相当于一个策略。同理,URL分页、Postback分页都可以看做是一种策略。看深入浅出设计模式,里面的例子采用的是接口,而分页控件采用的是基类。

     在QuickPager里面定义下面几个成员,这时并没有实例化。

/**//// <summary>
        /// 访问数据库用的实例
        /// </summary>
        private  DataAccessHelp dal = null;

        /**//// <summary>
        /// 生成SQL语句的部分
        /// </summary>
        private PageManage.PageSQL MgrPageSQL = null;

        /**//// <summary>
        /// 提取数据的部分
        /// </summary>
        private PageManage.PageGetData MgrGetData = null;

        /**//// <summary>
        /// 提取数据的部分
        /// </summary>
        private PageManage.PageUI MgrPageUI = null;

然后再设计几个属性,来处理如何实例化这几个成员。

数据访问实例的设置#region 数据访问实例的设置
        /**//// <summary>
        /// 设置数据访问层的实例
        /// </summary>
        public DataAccessHelp DAL
        {
            set { dal = value; }
            get
            {
                if (dal == null)
                    dal = new DataAccessHelp();

                return dal;
            }
        }
        #endregion

        分页算法的实例#region 分页算法的实例
        /**//// <summary>
        /// 分页算法的实例
        /// </summary>
        public PageManage.PageSQL ManagerPageSQL
        {
            set { MgrPageSQL = value; }
            get
            {
                if (this.SetRunKind == myPageRunKind.Customer)
                    return null;        //自定义方式不提供分页算法

                PageManage.PageSQL tmp = MgrPageSQL;
                if (tmp == null)
                {
                    switch (SetSQLKind)
                    {
                        case myPageSQLKind.Row_Number:
                            tmp = new JYK.Controls.PageManage.SQL_Row_Number();
                            break;

                        case myPageSQLKind.Max_TopTop :
                            if (this.TableOrderColumns.Contains(","))
                            {
                                //多字段排序
                                //CommandClass.MsgBox("Max_TopTop2", false);
                                tmp = new JYK.Controls.PageManage.SQL_TopTop();
                            }
                            else
                            {
                                //一个排序字段
                                //CommandClass.MsgBox("Max_TopTop1", false);
                                tmp = new JYK.Controls.PageManage.SQL_Max();
                            }
                            break;

                        //其他的算法的判断就省略了。
                    }

                    MgrPageSQL = tmp;
                }

                if (tmp.myPage == null)
                    tmp.myPage = this;

                return tmp;
            }
        }
        #endregion

        分页方式的实例#region 分页方式的实例
        /**//// <summary>
        /// 分页方式的实例
        /// </summary>
        public PageManage.PageUI ManagerPageUI
        {
            set { MgrPageUI = value; }
            get
            {
                PageManage.PageUI tmp = MgrPageUI;
                if (tmp == null)
                {
                    switch (this.SetUIKind)
                    {
                        case myPageUIKind.PostBack:
                            tmp = new JYK.Controls.PageManage.UI_PostBack();
                            break;
                       
                    }

                    MgrPageUI = tmp;
                }

                if (tmp.myPage == null)
                    tmp.myPage = this;

                return tmp;
            }
        }
        #endregion

     这样就非常的灵活了,不仅可以根据不同的属性设置来实例化不同的子类。比如根据不同的分页算法来实例化不同的子类。而且还可以在外部设置实例,就是说可以再分页控件的外部自己定义一个子类,然后实例化。 再有一个好处就是,用到的时候才实例化,不用就不实例化,比如如果采用自定义的运行方式的话,那么dal 、 MgrPageSQL、 MgrGetData都不会被实例化,这样就不会有浪费的感觉了。

自定义的运行方式,就是像吴旗娃的分页控件那样的使用方式。是需要自己处理数据的,所以dal 、 MgrPageSQL、 MgrGetData这三个类就用不到了,用不到也就不需要实例化了。

     2、模板模式

          策略模式只是规定了这几个类的关系,至于类的内部的实现方式,可以考虑使用模板模式。MgrPageSQL和子类的实现方式就是模板模式。

          模板模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 http://www.cnblogs.com/webabcd/archive/2007/03/13/673658.html

          在MgrPageSQL里面定义了分页算法必须做的几件事情,和几个步骤的实现代码,然后在子类里面完善。

总结:      这样来分析一下,会对QuickPager更加了解一点了吧。如果想看源码的话请到这里下载:http://www.cnblogs.com/jyk/archive/2008/07/29/1255891.html

     下一篇会说一下QuickPager里面已经实现的几种分页算法的SQL语句、适用范围、压力测试、几种算法之间的对比和与存储过程的对比。