首先声明以下是转载的,感谢原作者
通用分页实现及其OO设计探讨
本来是打算拿到it168投稿的!!那边的速度太慢了!先贴出来先!!
分页是一种常用的页面数据显示技术,分页能够通过减少页面数据处理量从而提高了系统的性能。分页应该是做WEB开发必须掌握的一个小技术。而分页却是复杂的,倒不是它的技术有多复杂;而是有太多的重复代码,这些代码都难以重用。能不能实现一个通用的分页框架?每次只需要去覆写一两个方法,通过少量的代码就能实现分页的功能?
一般分页应该要具有的功能有
1. 灵活的设置分页大小。可以动态的设置分页大小,而不是写死到代码中。
2. 自动计算总页数。根据分页大小和总记录数自动计算总页数。
3. 获得当前页的页号。
4. 获得当前页的总记录数。一般是最后一页的时候可能会小于分页大小。
5. 判断当前页是否为第一页。
6. 判断当前页是否为最后一页。
7. 判断当前页是否有上一页。
8. 判断当前页是否有下一页。
9. 获得当前页的数据列表。
10. 获得当前页的第一条记录的索引号
11. 获得当前页的最后一条记录的索引号。
二、常用的分页技术
目前常用的分页技术有两种:
1. 第一次访问是读取所有记录,放入session中,然后每次从session对象中读取当前页的数据
2. 每次都访问数据库,从数据库中读取当前页的记录。
这两种方法都各有优缺点,当数据量比较少时,第一种方法无疑是要快一些,因为减少与数据库的连接访问。而当数据量比较大时,比如查询结果可能会是上万条,那么内存的开销是十分大的,放到session中还有一个问题是能不能及时的清除无用的对象。而且这么大数据量在网络中传输也会使系统变得很慢。
第二种方法就是专门解决这个问题的,它每次访问数据库,只读取当前页所需的记录,大大的减少网络传输量;它不会把页数据放到session中,大大提高服务器的性能。
所以第二种方式要优于第一种方法。Session不要乱用,要用也仅仅是存放一些公共变量,相对于占用空间比较少的对象。不适合存放大量的数据,否则在很多个用户同时访问时那么系统会很慢,因为服务器内存被销耗的很厉害
三、通用分页框架需要解决的问题
作为一个通用分页框架,
1. 应该不依赖于任何其它框架
2. 应该支持多种数据库
3. 应该可以应用于任何web框架中,如:struts,spring等。
4. 应该把数据访问的具体实现留给用户去实现。
5. 应该实现关键的算法和过程,如:计算总页数,所需的实始化动作。
6. 应该减化Contrller控制器的代码,以往的分页技术在Contrller中存在太多的if…else代码。十分难懂,应该由一个辅助类来实现。
7. 应该减化jsp页面的代码,页面应该没有任何与分页相关的计算。应该由分页对象来实现。
8. 应该支持两种分页方式,采用session或不采用session由用户控制。
四、具体实现
1.通用分页接口。定义接口可以有更多不同的实现,接口只声明了分页应该具有的公共行为。
ViewPage.java
/**
* 分页接口
* @author ex_yuanguangdong
*
*/
public interface ViewPage {
/**
* 获取总页数
* @return 总页数
*/
public int getPageCount();
/**
* 获得页面大小
* @return 页面大小
*/
public int getPageSize();
/**
* 设置页面大小
* @param size
*/
public void setPageSize(int size);
/**
* 获得当前页数据
* @return 数据列表
*/
public List getPageData();
/**
* 获得当前页索引号
* @return 当前页索引号
*/
public int getPageIndex();
/**
* 获得当前页记录总数
* @return 当前页记录总数
*/
public int getPageRows();
/**
* 是否有下一页
* @return
*/
public boolean getHashNextPage();
/**
* 是否有上一页
* @return
*/
public boolean getHashPreviousPage();
/**
* 转到尾页
*
*/
public void gotoLastPage();
/**
* 转到首页
*
*/
public void gotoFirstPage();
/**
* 是否首页
* @return
*/
public boolean isFirstPage();
/**
* 是否尾页
* @return
*/
public boolean isLastPage();
/**
* 转到上一页
*
*/
public void gotoPreviousPage();
/**
* 转到下一页
*
*/
public void gotoNextPage();
/**
* 转到指定页面,pageIndex小于1时,转到第一页;pageIndex大于总页数时,转到最尾页
* @param pageIndex 指定的页号
*/
public void gotoPage(int pageIndex);
/**
* 获取当前页第一条记录的记录号
* @return int 当前页第一条记录的记录号
*/
public int getPageFirstRecord();
/**
* 获取当前页最后一条记录的记录号
* @return int 当前页最后一条记录的记录号
*/
public int getPageLastRecord();
}
2. 分页抽像实现类,实现关键的算法
AbstractViewPage.java
/**
* 分页默认抽象实现
* 初始时,分页类有下列默认值:
* 分页大小为-1,为不分页;
* 总页数为1页
* 当前页为第一页
* 总记录数为0条
* 当前页数据列表为没有任何记录的列表
* @author ex_yuanguangdong
*
*/
public abstract class AbstractViewPage implements ViewPage {
//-----------------------------------------
//私有静态常量
//-----------------------------------------
private static final int DEFAULT_PAGE_INDEX = 1;
private static final int DEFALT_PAGE_COUNT = 1;
private static final int DEFAULT_PAGE_SIZE = -1;
private static final int DEFAULT_ROWS = 0;
//-----------------------------------------
//私有成员变量
//-----------------------------------------
/**当前页索引号**/
private int pageIndex = DEFAULT_PAGE_INDEX;
/**总页数**/
private int pageCount =DEFALT_PAGE_COUNT;
/**分页大小**/
private int pageSize = DEFAULT_PAGE_SIZE ;
/**数据总记录数**/
private int rows = DEFAULT_ROWS;
//------------------------------------------
//本地成员变量getter,setter方法
//------------------------------------------
/**
* 设置新页号,只有大于等于1而且小于等于总页数并且不为当前页时,才允许设置
* @param pageIndex The pageIndex to set.
*/
private void setPageIndex(int newPageIndex) {
if( newPageIndex >= this.DEFAULT_PAGE_INDEX && newPageIndex <= this.getPageCount() && newPageIndex != this.pageIndex) {
this.pageIndex = newPageIndex;
}
}
/**
* @return Returns the rows.
*/
private int getRows() {
return rows;
}
/**
* @param rows The rows to set.
*/
private void setRows(int rows) {
this.rows = rows;
}
/**
* @param pageCount The pageCount to set.
*/
private void setPageCount(int pageCount) {
this.pageCount = pageCount;
}
//--------------------------------------
//实现Page接口方法
//--------------------------------------
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageData()
*/
public List getPageData() {
List pageList =null;
//获得当前页数据
pageList = this.pageList(this.getPageFirstRecord(), this.getPageRows());
//保证不返回null
if(pageList == null) {
pageList =new ArrayList();
}
return pageList;
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageIndex()
*/
public int getPageIndex() {
return this.pageIndex;
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#isFirstPage()
*/
public boolean isFirstPage() {
return this.DEFAULT_PAGE_INDEX ==this.pageIndex;
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#isLastPage()
*/
public boolean isLastPage() {
//当前页索引为总页数时为最后一页
return this.pageIndex == this.pageCount;
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getHashNextPage()
*/
public boolean getHashNextPage() {
//当前页索引号小于总页数
return this.pageIndex < this.pageCount ;
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getHashPreviousPage()
*/
public boolean getHashPreviousPage() {
//当前页索引号大于默认的初始页号,这里为1
return this.pageIndex > this.DEFAULT_PAGE_INDEX ;
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageCount()
*/
public int getPageCount() {
return this.pageCount;
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageSize()
*/
public int getPageSize() {
return this.pageSize;
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageRows()
*/
public int getPageRows() {
//当页面大小为-1 时,返回总记录数
if(this.DEFAULT_PAGE_SIZE == this.pageSize ) {
return this.rows;
}
//不为最后一页时,返回pageSize
if(!this.isLastPage()) {
return this.pageSize;
}
//最后一页时
return this.rows - (this.pageSize * (this.pageCount -1));
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageFirstRecord()
*/
public int getPageFirstRecord() {
//页大小为-1 时
if(this.DEFAULT_PAGE_SIZE== this.pageSize ) {
return 0;
}
return (this.pageIndex -1)* this.pageSize;
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageLastRecord()
*/
public int getPageLastRecord() {
//页大小为-1时,返回总记录数
if(this.DEFAULT_PAGE_SIZE == this.pageSize) {
return this.rows;
}
return this.getPageFirstRecord() + this.getPageRows() ;
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#gotoFirstPage()
*/
public void gotoFirstPage() {
this.gotoPage(this.DEFAULT_PAGE_INDEX);
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#gotoLastPage()
*/
public void gotoLastPage() {
this.gotoPage(this.getPageCount());
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#gotoPreviousPage()
*/
public void gotoPreviousPage() {
this.gotoPage(this.getPageIndex() -1);
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#gotoNextPage()
*/
public void gotoNextPage() {
this.gotoPage(this.getPageIndex() + 1);
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#gotoPage(int)
*/
public void gotoPage(int newPageIndex) {
if( newPageIndex >= this.DEFAULT_PAGE_INDEX && newPageIndex <= this.getPageCount() ) {
this.setPageIndex(newPageIndex);
}
}
/**
* @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#setPageSize(int)
*/
public void setPageSize(int size) {
if(size < 1) {
size = 1;
}
this.pageSize = size;
//进行初始化
this.doInit();
}
//-----------------------------------
//辅助方法
//-----------------------------------
/**
* 分页初始化方法,为了保证总是能正确的初始化,所以声明为final ,为了让子类可以调用声明为protected
*
*/
protected final void doInit() {
int rows = 0;
//获得总记录数
rows= totalRows();
//设置总记录数
this.setRows(rows);
//设置新的总页数
//计算并设置总页数
int pages = calculatePageCount();
this.setPageCount(pages);
//转到第一页
this.gotoPage(this.DEFAULT_PAGE_INDEX);
onInit();
}
/**
* 记算总页数
* @return 总页数
*/
private int calculatePageCount() {
//总记录数为0条,则返回的总页数为1
if(this.getRows() == 0) {
return this.DEFALT_PAGE_COUNT;
}
//如果页面大小为-1,则返回的总页数为1
if(this.DEFAULT_PAGE_SIZE == this.getPageSize() ) {
return this.DEFALT_PAGE_COUNT;
}
return this.getRows() / this.getPageSize() + ( this.getRows() % this.getPageSize() ==0 ? 0 :1);
}
/**
* 获得总记录数,调用queryTotalRows(),将异常封装为non-checked 异常
* @return 总记录数
* @throws ApplicationRuntimeException
*/
private int totalRows() throws ApplicationRuntimeException{
try{
return queryTotalRows();
}
catch(Exception ex){
throw new ApplicationRuntimeException(ex);
}
}
/**
* 获得当前页数据,调用queryPageList()方法,将异常封装为non-checked异常
* @param startRow 开始记录号
* @param rowCount 记录总数
* @return 当前页数据
* @throws ApplicationRuntimeException
*/
private List pageList(int startRow, int rowCount) throws ApplicationRuntimeException{
try{
return queryPageList(startRow, rowCount);
}catch(Exception ex){
throw new ApplicationRuntimeException(ex);
}
}
//-----------------------------------------
//子类实现的方法
//-----------------------------------------
/**
* 初始化附加方法,由子类扩展
*/
protected void onInit() {
}
/**
* 查询获得总记录数,由子类具体实现
* @return 总记录数
* @throws Exception
*/
protected abstract int queryTotalRows() throws Exception;
/**
* 查询当前页数据,从startRow 开始的rowCount条记录
* @param startRow 开始记录号
* @param rowCount 记录总数
* @return 当前页数据
* @throws Exception
*/
protected abstract List queryPageList(int startRow, int rowCount) throws Exception;
}
3. 分页辅助类
ViewPageHelper.java/**
* 分页辅助类,用于减化Controller中的代码
* @author yuanguangdong
* date: Oct 22, 2006
*/
public class ViewPageHelper {
private static final int FIRST_PAGE_VALUE = 1;
private static final int PREVIOUS_PAGE_VALUE = 2;
private static final int NEXT_PAGE_VALUE = 3;
private static final int LAST_PAGE_VALUE = 4;
private static final int SPECIAL_PAGE_VALUE = 5;
public static final String FIRST_PAGE = "FIRST_PAGE";
public static final String PREVIOUS_PAGE = "PREVIOUS_PAGE";
public static final String NEXT_PAGE = "NEXT_PAGE";
public static final String LAST_PAGE = "LAST_PAGE";
public static final String SPECIAL_PAGE = "SPECIAL_PAGE";
/**分页动作参数名**/
public static final String PAGE_ACTION = "page_action";
/**分页对象属性名**/
public static final String SESSION_PAGE = "session_page";
/**页号参数名**/
public static final String PAGE_NO = "page_no";
private static Map actionMap = new HashMap();
static {
actionMap.put(FIRST_PAGE, new Integer(FIRST_PAGE_VALUE));
actionMap.put(PREVIOUS_PAGE, new Integer(PREVIOUS_PAGE_VALUE));
actionMap.put(NEXT_PAGE, new Integer(NEXT_PAGE_VALUE));
actionMap.put(LAST_PAGE, new Integer(LAST_PAGE_VALUE));
actionMap.put(SPECIAL_PAGE, new Integer(SPECIAL_PAGE_VALUE));
}
/**
* 执行分页动作
* @param page 分页对象
* @param action 分页动作参数
* @param pageIndex 页号
*/
public static void doAction(ViewPage page, String action, int pageIndex) {
int actionIndex = 0;
if (page == null) {
throw new NullPointerException("Page对象null");
}
if (action == null || "".equals(action)) {
throw new IllegalArgumentException("无效的分页动作参数null");
}
action = action.toUpperCase();
if (!actionMap.containsKey(action)) {
throw new UnsupportedOperationException("不支持的分页动作参数:" + action);
}
Integer index = (Integer) actionMap.get(action);
actionIndex = index.intValue();
switch (actionIndex) {
case FIRST_PAGE_VALUE:
page.gotoFirstPage();
break;
case PREVIOUS_PAGE_VALUE:
page.gotoPreviousPage();
break;
case NEXT_PAGE_VALUE:
page.gotoNextPage();
break;
case LAST_PAGE_VALUE:
page.gotoLastPage();
break;
case SPECIAL_PAGE_VALUE:
page.gotoPage(pageIndex);
}
}
public static void doAction(ViewPage page, String action){
doAction(page, action, 1);
}
}
五、应用通用分页框架
1.继承AbstractViewPage类,实现queryPageList(int startRow, int endRow)和
queryTotalRows()方法。
protected int queryTotalRows() throws Exception
获得查询条件的总记录数
protected List queryPageList(int startRow, int rowCount)
用于查询指定范围的数据。startRow为开始记录号, rowCount为查询的记录数
queryPageList(0,20)为查询从第一条开始的20条记录。
使用Ibatis可以由queryPageList调用queryForList()方法。
/**
* 用户信息分页内部类
* @author yuanguangdong
* date: Oct 22, 2006
*/
class UserInfoPage extends AbstractViewPage{
//------------------------------------------------
//实现AbstractViewPage抽象类的抽象方法
//------------------------------------------------
/**
* @see com.prs.application.ehld.web.mvc.AbstractViewPage#getPageDate(int, int)
*/
protected List queryPageList(int startRow, int endRow) throws Exception {
return sampleAction.getUserInfoList(startRow, endRow);
}
/**
* @see com.prs.application.ehld.web.mvc.AbstractViewPage#getRows()
*/
protected int queryTotalRows() throws Exception {
return sampleAction.getUserCount();
}
}
3. 在Contrller中的实现
public ModelAndView listUser(HttpServletRequest request,
HttpServletResponse response) throws Exception {
String pageAction =
RequestUtils.getStringParameter(request,ViewPageHelper.PAGE_ACTION);
Integer pageIndex =
RequestUtils.getIntParameter(request,ViewPageHelper.PAGE_NO);
//声明分页对象
ViewPage userPage =
(ViewPage) request.getSession().getAttribute(ViewPageHelper.SESSION_PAGE);
//第一次请求
if(pageAction == null || userPage == null){
//构建一个新的分页对象
userPage = new UserInfoPage();
//设置分页大小
userPage.setPageSize(2);
}else{
if(ViewPageHelper.SPECIAL_PAGE.equals(pageAction)){
//如果页数为空,则默认为1
if (pageIndex == null)
pageIndex = new Integer(1);
ViewPageHelper.doAction(userPage,pageAction,pageIndex.intValue());
}else{
ViewPageHelper.doAction(userPage,pageAction);
}
}
//从分页对象中获得当前页数据
List userInfoList = userPage.getPageData();
ModelAndView mav = new ModelAndView(userInfoListView);
mav.addObject(this.userInfoListKey,userInfoList);
request.getSession().setAttribute(ViewPageHelper.SESSION_PAGE,userPage);
return mav;
}
4. jsp页面实现
<%@ page contentType="text/html;charset=utf-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>
<%@ taglib prefix="tiles" uri="http://jakarta.apache.org/struts/tags-tiles" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<title>显示所有员工</title>
<SCRIPT language="javaScript">
function pageNoChange(pageNo){
location.href= "ehld.sample.getuserinfolist.do?page_action=SPECIAL_PAGE&page_no="+pageNo.value;
}
</SCRIPT>
</head>
<body>
<table width="80%" border="0">
<tr>
<td bgcolor="#F0FEFF"><div align="left"> 用户列表</div></td>
</tr>
</table>
<br>
<input name="adduser" type="submit" id="adduser" value="新增用户" onclick="location.href='ehld.sample.edituserinfo.do'">
<table width="80%" border="0">
<tr bgcolor="#58ED64">
<th width="25%">id</th>
<th width="34%">姓名</th>
<th colspan="2">操作</th>
</tr>
<c:forEach items="${userInfoList}" var="userInfoDTO">
<tr bgcolor="#D6EBF8">
<td><c:out value="${userInfoDTO.userID}"/></td>
<td><c:out value="${userInfoDTO.userName}"/></td>
<td width="21%"><a href="ehld.sample.edituserinfo.do?id=<c:out value='${userInfoDTO.userID}'/>">编辑</a></td>
<td width="20%"><a href="#">删除</a></td>
</tr>
</c:forEach>
</table>
<c:if test="${session_page.firstPage}">
首页
</c:if>
<c:if test="${! session_page.firstPage}">
<a href="ehld.sample.getuserinfolist.do?page_action=FIRST_PAGE">首页</a>
</c:if>
<c:if test="${! session_page.hashPreviousPage}">
上一页
</c:if>
<c:if test="${session_page.hashPreviousPage}">
<a href="ehld.sample.getuserinfolist.do?page_action=PREVIOUS_PAGE">上一页</a>
</c:if>
<c:if test="${!session_page.hashNextPage}">
下一页
</c:if>
<c:if test="${session_page.hashNextPage}">
<a href="ehld.sample.getuserinfolist.do?page_action=NEXT_PAGE">下一页</a>
</c:if>
<c:if test="${session_page.lastPage}">
尾页
</c:if>
<c:if test="${!session_page.lastPage}">
<a href="ehld.sample.getuserinfolist.do?page_action=LAST_PAGE">尾页</a>
</c:if>
共有<c:out value="${session_page.pageCount}" />页,第
<select name = "pageNo" onChange = "java script:pageNoChange(this);">
<c:forEach begin="1" end = "${session_page.pageCount}" var = "pageIndex">
<option value="<c:out value='${pageIndex}'/>" <c:if test = "${pageIndex ==session_page.pageIndex }">selected</c:if>>
<c:out value="${pageIndex}"/>
</option>
</c:forEach>
</select>
页
</body>
</html>
六、设计探讨
1.通过提供queryTotalRows() 和queryPageList(int startRow, int rowCount)方法,交由用户具体的去实现,所以能够支持任何数据库。
对于Ibatis用户可以使用queryForList()方法,对于用jdbc实现也可以有多种方法来支持各种数据库。
Ms sql 可以使用top 关键字,来获得指定范围的数据
ORACEL可以使用rowid 伪列来获得指定范围的数据
具体怎么去读取数据,完全交由用户控制
2.分页对象与具体的业务对象分离。分页对象如果不能与具体的业务对象分离那么就不可能实现分页对象的重用,不可以实现代码的最大的重用。这不符合oo的按职责来设计对象的原则。
3. ViewPageHelper帮助类的使用有两个好处,统一为分页代码所需的字符参数进行定义,便于contrller和jsp页面代码的维护。第二便于代码重用,减少在contrller中的if分支句语。如果不使用帮助类,则在每个controller中都会产生大量相同的代码。
4. final关键字的使用,protected final void doInit()用于分页对象的实始化,它读取并设置总记录数,计算总页数,默认为第一页等。为什么不在构造函数中来做它呢?如果在构造函数来做它,子类就不可以扩展了。像这样的初始化方法的位置应该由扩展类来灵活控制。声明为protected是不让它由外部对象来进行访问,但是子类又可以进行调用。声明为final是为了子类不能重写它,如果子类重写不当就会造成分页对象的执行逻辑错误。但是如果子类又想扩展它怎么办?子类重写protected void onInit()方法就可以了。这样就能保证父类的逻辑,又能够让子类进行扩展。
5.异常处理的思考,queryTotalRows()和queryPageList方法都是要求由子类实现的抽象类,这两个类的特点都是可能会调用业务对象去实现相应的功能,业务对象可能会访问业务数据库等,可能会抛出任何Exception,但是分页对象类去调用queryTotalRows()和queryPageList的方法是不应该对这些Exception进行任何处理的,如果进行try…catch那么就会隐藏了异常的细节,这是十分可怕的。如果这些方法抛出异常,分页对象应该是不能处理的,不能处理的异常应该封装为运行时异常,所以就有了下面的实现
private List pageList(int startRow, int rowCount) throws ApplicationRuntimeException{
try{
return queryPageList(startRow, rowCount);
}catch(Exception ex){
throw new ApplicationRuntimeException(ex);
}
}
private int totalRows() throws ApplicationRuntimeException{
try{
return queryTotalRows();
}
catch(Exception ex){
throw new ApplicationRuntimeException(ex);
}
}
分页对象内部调用pageList和totalRows方法,这样就很好的解决了异常的问题,把异常交由外部调用者去决定是否处理,而不是强制调用者去处理。
5. 模板方法模式的使用,这是一个典型的模板方法模式的运用。在父类实现关键的算法代码,实现分页对象的处理逻辑,而把某些会发生改变的方法交由子类去实现,使得子类完全不用去关心父类的实现细节,子类只需要重写两个简单的方法就可以实现父类的功能。这就是模板方法带来的最大好处。模板方法模式在各种开源框架中有着广泛的运用,看看spring的源码就知道。子类只需要去实现自己最关心的细节,而父类实现那些不变的逻辑或算法。
6. 针对接口编程,而不是针对类编程。接口可以实现多重继承,而类却不能。接口有比类获得更多的好处,更利于扩展。比如说分页接口,它可以让用户有更多不同的实现,完全不依赖于任何类。只需要为它定制了共同的行为就可以了。在使用委托的时候接口比抽像类更好用。比如在装饰模式的使用中,可能需要实现一个接口,而其中还要有一个本接口的引用。 如果是抽象类,则不可以实现。
7. 通用框架应该具有灵活性,不应该依懒于任何具体的框架。如果通用框架依懒于某一技术细节,某一框架,那么它就有一定的局限性。所以通用分页不应该依懒于ibatis或hibernate 或spring的某一特点。更不应该依懒于sql或oralce某种数据库。