代码改变世界

【C#|.NET】从细节出发(一) 通用接口 aop dto 相关

2013-03-25 16:14  熬夜的虫子  阅读(4486)  评论(2编辑  收藏  举报

系列文章完成后 源码发布在我的GIT上 https://github.com/dubing/

文章仅代表个人观点  旨在交流 欢迎讨论


背景

  随着信息化的普及,信息系统越来越多,通常不同系统是采用不同的技术基于不同平台开发的,缺乏统一规划、统一数据标准、统一调用接口,因此系统之间的交互变得很困难.通常大家在需要一个现有系统提供某方面功能的话就会让开发人员提供个接口,webservice接口也好,标准http接口也好。然后需求不停的变更,代码不停的迭代。随着应用端量的增多,对于类似业务逻辑提供的数据格式,内容的特殊处理,dto的设计等等都在变化。用.Net的同学会了解多一些,用webservice的话,应用端会生成代理类。服务端每次更新对应应用端都要同步更新。至此,我们引出第一个细节问题,解决服务端与应用端的强依赖,设计通用的接口规范。


技术点

  1. AOP(切面)

  2. JavaScriptSerializer (json序列化)

  3. DTO设计


实现

  先设计一个通用的接口类  

    /// <summary>
    /// 对象处理返回的结果接口
    /// </summary>
    /// <remarks>
    /// 建议在代码调用返回值中都采用此类实例为返回值<br />
    /// 一般ResultNo小于0表示异常,0表示成功,大于0表示其它一般提示信息
    /// </remarks>
    public interface IAOPResult 
    {
        /// <summary>
        /// 返回代码
        /// </summary>
        int ResultNo { get; }

        /// <summary>
        /// 对应的描述信息
        /// </summary>
        string ResultDescription { get; }

        /// <summary>
        /// 相应的附加信息
        /// </summary>
        object ResultAttachObject { get; }

        /// <summary>
        /// 内部AOPResult
        /// </summary>
        IAOPResult InnerAOPResult { get; }

        /// <summary>
        /// 处理结果是否成功(ResultNo == 0)
        /// </summary>
        bool IsSuccess { get; }

        /// <summary>
        /// 处理结果是否失败(ResultNo != 0 )
        /// </summary>
        bool IsNotSuccess { get; }

        /// <summary>
        /// 处理结果是否失败(ResultNo < 0 )
        /// </summary>
        bool IsFailed { get; }

        /// <summary>
        /// 已处理,但有不致命的错误(ResultNo > 0)
        /// </summary>
        bool IsPassedButFailed { get; }

        /// <summary>
        /// 如果处理失败,则抛出异常 
        /// </summary>
        /// <returns>返回本身</returns>
        IAOPResult ThrowErrorOnFailed();
    }

  设计一个里AOPResult实现这个接口,类中添加Serializable属性表明可序列化,应用到我们的具体的demo如下

        [WebMethod(Description = "检查购物车中的礼品")]
        [SoapHeader("header")]
        public AOPResult CheckGiftForCart(List<CartLineDTO> carlist, bool returnflag)
        {
            ValidateAuthentication();
            return (AOPResult)CartGiftCenter.GiftService.CheckGiftForCart(carlist, returnflag);
        }

        [WebMethod(Description = "获取所有的赠品信息")]
        [SoapHeader("header")]
        public AOPResult GetGiftList()
        {
            ValidateAuthentication();
            var result = (AOPResult)CartGiftCenter.GiftService.GetModelList();
            if(result.IsSuccess)
            {
                result.ResultAttachObject = result.ResultAttachObject.ObjectToJson();
            }
            return result;
        }

        [WebMethod(Description = "根据Id获得组信息")]
        [SoapHeader("header")]
        public AOPResult GetGroupInfoByID(int Id)
        {
             ValidateAuthentication();
             var result = (AOPResult)CartGiftCenter.GroupService.GetModelInfoById(Id);
             if (result.IsSuccess)
             {
                 var resultobj = result.ResultAttachObject as GiftGroup;
                 result.ResultAttachObject = new GroupLineDTO
                                                 {
                                                     GroupId = (int)resultobj.GroupId,                                                
                                                 }.ObjectToJson();

             }
             return result;
        }       

  从上面的例子我们可以看出所有的服务方法都采用了相同的返回类型。AOPResult中可以存对象,可以存数组,具体由自己定义。如果返回的是对象我们需要将他序列化成json方式。序列化方法参照下面

    public static class JsonExtensions
    {
        private static JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();

        [DebuggerStepThrough]
        public static string ObjectToJson(this object obj)
        {
            return javaScriptSerializer.Serialize(obj);
        }

        [DebuggerStepThrough]
        public static T JsonToObject<T>(this string json)
        {
            if (string.IsNullOrEmpty(json)) return default(T);
            return javaScriptSerializer.Deserialize<T>(json);
        }
    }

  这么做的优缺点是什么,优点是服务协议的双方有了共同的标准,应用端不再完全依赖于代理,也就是说服务端的变更不会直接影响到应用端。举个最简单的例子,应用a先向服务端索取一个对象A,A中包含c属性,后来b也想服务端索取对象A,但是需要包含d属性。服务器端更新重新build。按照普通设计a变也需要重新build引用新的服务代理。那么按照这个设计a便只需要从A中提取c就可以,不管服务端以后如何变更只要c属性还在,结构为改变就不需要重新build。

  应用端简单示例

            var b = CartGiftCenter.GiftService.GetModelList();
            string message =b.ResultDescription;
            var gift = b.ResultAttachObject as List<GiftDetail>;

  另一外的主要的有点就是可以统一的切面编程,aop的概念这里就不多说了。利用aop的特点我们可以对服务的反馈作统一的操作、检查。例如系统认证、内容过滤等等。这里推荐一篇简单务实的文章 http://blog.csdn.net/yanghua_kobe/article/details/6917228

  上文中所附的简单切面辅助类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Diagnostics;

/// <summary>
///AspectF 的摘要说明
/// </summary>
public class AspectF
{
    internal Action<Action> Chain = null;

    internal Delegate WorkDelegate;

    [DebuggerStepThrough]
    public void Do(Action work)
    {
        if (this.Chain==null)
        {
            work();
        }
        else
        {
            this.Chain(work);
        }
    }

    [DebuggerStepThrough]
    public TReturnType Return<TReturnType>(Func<TReturnType> work)
    {
        this.WorkDelegate = work;

        if (this.Chain==null)
        {
            return work();
        }
        else
        {
            TReturnType returnValue = default(TReturnType);
            this.Chain(() =>
            {
                Func<TReturnType> workDelegate = WorkDelegate as Func<TReturnType>;
                returnValue = workDelegate();
            });
            return returnValue;
        }
    }

    public static AspectF Define
    {
        [DebuggerStepThrough]
        get
        {
            return new AspectF();
        }
    }

    [DebuggerStepThrough]
    public AspectF Combine(Action<Action> newAspectDelegate)
    {
        if (this.Chain==null)
        {
            this.Chain = newAspectDelegate;
        }
        else
        {
            Action<Action> existingChain = this.Chain;
            Action<Action> callAnother = (work) =>
                existingChain(() => newAspectDelegate(work));
            this.Chain = callAnother;
        }

        return this;
    }

    public static void Retry(int retryDuration, int retryCount, Action<Exception> errorHandler, Action retryFaild, Action work)
    {
        do
        {
            try
            {
                work();
            }
            catch (Exception ex)
            {
                errorHandler(ex);
                System.Threading.Thread.Sleep(retryDuration);
                throw;
            }
        } while (retryCount-- > 0);

    }
}

public static class AspectExtensions
{
    [DebuggerStepThrough]
    public static void DoNothing()
    {

    }

    [DebuggerStepThrough]
    public static void DoNothing(params object[] whatever)
    {

    }

    [DebuggerStepThrough]
    public static AspectF Delay(this AspectF aspect, int milliseconds)
    {
        return aspect.Combine((work) =>
        {
            System.Threading.Thread.Sleep(milliseconds);
            work();
        });
    }

    [DebuggerStepThrough]
    public static AspectF MustBeNonNull(this AspectF aspect,params object[] args)
    {
        return aspect.Combine((work) =>
        {
            for (int i = 0; i < args.Length; i++)
            {
                object arg = args[i];
                if (arg==null)
                {
                    throw new ArgumentException(string.Format("Parameter at index {0} is null", i));
                }
            }
            work();
        });
    }

    public static AspectF MustBeNonDefault<T>(this AspectF aspect, params T[] args) where T : IComparable
    {
        return aspect.Combine((work) =>
        {
            T defaultvalue = default(T);
            for (int i = 0; i < args.Length; i++)
            {
                T arg = args[i];
                if (arg==null||arg.Equals(defaultvalue))
                {
                    throw new ArgumentException(string.Format("Parameter at index {0} is null", i));
                }
            }
            work();
        });
    }

    public static AspectF WhenTrue(this AspectF aspect, params Func<bool>[] conditions)
    {
        return aspect.Combine((work) =>
        {
            foreach (Func<bool> condition in conditions)
            {
                if (!condition())
                {
                    return;
                }
            }
            work();
        });
    }

    [DebuggerStepThrough]
    public static AspectF RunAsync(this AspectF aspect, Action completeCallback)
    {
        return aspect.Combine((work) => work.BeginInvoke(asyncresult =>
        {
            work.EndInvoke(asyncresult); completeCallback();
        }, null));
    }

    [DebuggerStepThrough]
    public static AspectF RunAsync(this AspectF aspect)
    {
        return aspect.Combine((work) => work.BeginInvoke(asyncresult =>
        {
            work.EndInvoke(asyncresult);
        }, null));
    }
}

  缺点:性能问题,首先是对象序列和反序列化的开销,其次是对象强制转换的开销,最后是传统aop反射的开销。不过对于一般项目利远远大于弊。

  再说DTO,这个概念和我们上述的场景完全是相反的论调。了解j2ee的同学对这些术语都很熟悉,Data Transfer Object数据传输对象,主要用于远程调用等需要大量传输对象的地方。比如我们一张表有20个字段,那么对应的持久对象(persistant object,简称po)就有20个属性。但是我们界面上只要显示10个字段,应用端用webservice来获取数据,没有必要把整个PO对象传递到应用端,这时我们就可以用只有这10个属性的DTO来传递结果到客户端。

  前文我们介绍AOPResult的时候,不考虑带宽也不考虑数据的安全策略。实际场景下,DTO的设计也需要考虑很多方面。我自己也不敢说自己设计的就是最合理的,我只分享下我的设计经验,在数据库通用(各个系统都能访问)的前提下,DTO只提供ID等表示字段,或者根据业务场景,如果应用端需要的内容不多可以设计成冗余类型甚至设计成PO。在带宽充足的场景下例如内网项目,放心的填充吧,没那么多性能元素需要考虑。一般场景下,则需要考虑的多些,看方法的使用频度,公网项目中dto中少存敏感字段等等。


题外

  本篇先到此,有疑问或者有其他想法的同学欢迎提出来一起讨论,下个章节一起再研究下如何利用泛型最简单的操作多种类型数据库。