在应用中剖释接口所担当的角色
一提到接口的用处相信很多人都说用来实现多继承,但为什么需要多继承?有一点可以肯定的是多继承用于实现对象更多的行为特性,从而更灵活地和其他对象进行沟通完成相关的功能.但在单继承的语言中缺少了这样重要的特性,从而引伸出接口来担负这个重任.但在很多描述接口的文章中确很少提到接口在功能组合起到的重要作用.在这里向大家分享一个在实际应用中通过接口作为功能组合的桥接例子,从而使不同模块之间的偶合度更低组合更灵活.
在使用NClay编写应用案例时碰到一个需求,这个需求相信很多朋友在实际开发中都要面对,那就是数据分页;数据分页贯穿系统的所有逻辑层包括VIEW,BLL和DAL,如何实现数据分页的逻辑描述信息在这三个层之间传递的同时保证模块的偶合度成了关键.在这里通过代码来详细讲述这个设计过程,通过这个过程相信大家能够体会接口在功能组合担当着非常重要的角色.
制定分页描述接口
public interface IDataPage
{
int PageIndex
{
get;
set;
}
int PageSize
{
get;
set;
}
int RecordCount
{
get;
set;
}
int PageCount
{
get;
}
string OrderField
{
get;
set;
}
}
public interface IDataPageProperty
{
IDataPage DataPage
{
get;
set;
}
}
以上代码制定了两种分页描述,一是直接由实现者来描述分页信息,二就是通过实现的对象成员来描述分页信息.但后者也是基于IDataPage的实现扩展.
DAL层的接口扩展
public static IList<T> List<T>(IDataSession session, Expressions.Expression exp, IDataPageProperty datapage)
{
return List<T>(session, exp, datapage.DataPage);
}
public static IList<T> List<T>(IDataSession session, Expressions.Expression exp, IDataPage datapage)
{
Region region = new Region(datapage.PageIndex*datapage.PageSize, datapage.PageSize);
Mappings.FieldAdapter[] orders = null;
if (!Common.IsEmpty(datapage.OrderField))
{
orders = new NClay.Data.Mappings.FieldAdapter[] {
new Mappings.FieldAdapter(datapage.OrderField,null)
};
}
datapage.RecordCount = CountOf<T>(session,exp);
return List<T>(session, exp, region, orders);
}
这里只是NClay的数据持久的扩展,具体的实现和相关DAL层代码有关,如果是使用SQLHELPER所需要编写的代码相对会多些和复杂些.通过接口首先保证了对象类型的无关性,只要实现相关接口就能进行数据查询和分页;我们所说的用接口实现多继承在这里体现出来了,更重要的一点是这个继承的目的是为了和DAL组合完成数据分页的功能.
业务逻辑层的实现
当DAL提供这个功能组合时,业务逻辑实现分页功能就得到更进一步的简化;逻辑对象并不需要关心数据分页的细节,它只要实现相关分页接口即可。
/// <summary>
/// 产品查询逻辑
/// </summary>
public interface IProductList : IDataPageProperty
{
/// <summary>
/// 产品名称
/// 模糊匹配
/// </summary>
string ProductName
{
get;
set;
}
/// <summary>
/// 所属类别
/// 等于匹配
/// </summary>
string CategoryId
{
get;
set;
}
/// <summary>
/// 起始单价
/// 大于等于匹配
/// </summary>
decimal PriceForm
{
get;
set;
}
/// <summary>
/// 结果单价
/// 小于等于匹配
/// </summary>
decimal PriceTo
{
get;
set;
}
/// <summary>
/// 产品列表
/// </summary>
IList<ProductView> Items
{
get;
set;
}
}
逻辑处理代码
/// <summary>
/// 查询
/// </summary>
/// <param name="view">逻辑接口</param>
public void List(IProductList view)
{
Expression exp = new Expression();
if (!Common.IsEmpty(view.ProductName))
{
exp &= DB.Product.ProductName.Match(view.ProductName);
}
if (!Common.IsEmpty(view.CategoryId))
{
exp &= DB.Product.CategoryID == view.CategoryId;
}
if (view.PriceForm > 0)
{
exp &= DB.Product.Price >= view.PriceForm;
}
if (view.PriceTo > 0)
{
exp &= DB.Product.Price <= view.PriceTo;
}
view.Items = exp.List<ProductView>(view);
}
在逻辑处理里同样也使用接口来桥接,原因很简单c#不支持通过多继承来实现对象不同的行为特性,所以采用接口来肩负着这个责任;但也是出于同一个目的组合完成功能,逻辑和逻辑处理并没有一个明确的对象依赖关系,只是通过一个接口来约定。
分页控件的实现
既然已经有了一个分页描述接口,在编写控件时就不需要考虑控件依赖于某个Page对象;直接根据接口进行视图表现,只要控件所在的Page实现了相关接口分页控件就能够良好的工作。
<%@ Control Language="C#" AutoEventWireup="true" Inherits="NClay.Web.BaseControl" %>
<%@ Assembly Name="NClay" %>
<%@ Assembly Name="NClay.Web" %>
<%
NClay.IDataPage mDataPage = null;
int spitindex = 0;
NClay.Web.IFormContext context = ((NClay.Web.BaseControl)this).FormContext;
if (context.View is NClay.IDataPage)
mDataPage = (NClay.IDataPage)context.View;
if (context.View is NClay.IDataPageProperty)
mDataPage = ((NClay.IDataPageProperty)context.View).DataPage;
if (mDataPage == null)
mDataPage = new NClay.DataPage();
object view = context.View;
%>
<table class="datapage" >
<tr>
<%
if(mDataPage.PageIndex>0)
{
%>
<td valign="middle">
<a href="<%context.WriterDataPageInfo(mDataPage,null,mDataPage.PageIndex-1,view as NClay.Web.IDataPageParamUrl); %>" class="datapage">
Previous</a>
</td>
<% }
spitindex =mDataPage.PageIndex - 2;
if (spitindex > 4)
{
%>
<td valign="middle">
<a href="<%context.WriterDataPageInfo(mDataPage,null,0,view as NClay.Web.IDataPageParamUrl); %>" class="datapage">1</a>
</td>
<td valign="middle">
<a href="<%context.WriterDataPageInfo(mDataPage,null,spitindex-2,view as NClay.Web.IDataPageParamUrl); %>" class="datapage_" >...</a>
</td>
<% }
控件的代码太多就不完全贴出来了,在控件代码中看到IDataPageParamUrl接口,这个接口是用于描述获取相关URL参数的,如果相关对象实现了参接口控件就会在URL中注入参数信息,如果没有就不作处理并不影响控件的处理。
以上每一个功能的桥接都使用接口的主要原因是C#并不提供多继承,从而导致了对象之间的整合受到一定程度上限制。通过接口可以良好地解决这个问题,但在这几个功能中接口都是为了把几个不同的功能组合在一起最终完成一个完整的功能。到这里大家可以看到使用接口来实现多继承只是一种手段,其真正的目的是使对象之间更良好的搭配工作。
补个控件的效果图