• NET框架设计


    .NET框架设计(常被忽视的C#设计技巧)

     

    阅读目录:

    • 1.开篇介绍
    • 2.尽量使用Lambda匿名函数调用代替反射调用(走进声明式设计)
    • 3.被忽视的特性(Attribute)设计方式
    • 4.扩展方法让你的对象如虎添翼(要学会使用扩展方法的设计思想)
    • 5.别怕Static属性(很多人都怕Static在Service模式下的设计,其实要学会使用线程本地存储(ThreadStatic))
    • 6.泛型的协变与逆变(设计架构接口(Interface)时要时刻注意对象的协变、逆变)
    • 7.使用泛型的类型推断(还在为参数类型烦恼吗)
    • 8.链式编程(设计符合大脑思维习惯的处理流程)
      • 8.1.链式编程(多条件(方法碎片化)调用
    • 9.部分类、部分方法的使用(扩大设计范围)

    1.】开篇介绍

    本文中的内容都是我无意中发现觉得有必要分享一下的设计经验,没有什么高深的技术,只是平时我们可能会忽视的一些设计技巧;为什么有这种想法是因为之前跟一些同事交流技术的时候会发现很多设计思维被固化了,比如之前我在做客户端框架开发的时候会去设计一些关于Validator、DTO Transfer等常用的Common function,但是发现在讨论某一些技术实现的时候会被弄的云里雾里的,会自我郁闷半天,不会及时的明白对方在说的问题;

    后来发现他们一是没有把概念分清楚,比如.NETFrameworkC#VisualStudio,这三者之间的关系;二是没有理解.NET中各个对象的本质含义,比如这里的特性(Attribute),大部分人都认为它是被用来作为代码说明、标识使用的,而没有突破这个思维限制,所以在设计一些东西的时候会绕很多弯路;还有一点是很多人对C#中的语法特性分不清版本,当然我们要大概的了解一下哪些特性或者语法是C#2的哪些是C#3的,这样在我们设计东西的时候不会由于项目的版本问题而导致你无法使用设计技巧,比如扩展方法就无法使用在低于.NET3.0版本中,LINQ也无法在低于.NET3.O的版本中使用;

    .NETFramework的版本不断的在升级,目前差不多5.0都快面世了;.NETFramework的升级跟C#的升级没有必然的关系,这个要搞清楚;C#是为了更好的与.NET平台交互,它提供给我们的都是语法糖,最后都是.NETCTS中的类型;就比如大家都在写着LINQ,其实到最后LINQ也就被自动解析成对方法的直接调用;

    2.】尽量使用委托调用代替反射调用

    委托相信大家都玩的很熟,委托的发展到目前为止是相当不错的,从原本很繁琐的每次使用委托的时候都需要定义一个相应的方法用来实例化委托,这点在后来的C#2中得到了改进,支持匿名委托delegate{…}的方式使用,再到现在的C#3那就更方便了,直接使用面向函数式的Lambda表达式;那么这样还需要反射调用对象的方法吗?(当然特殊场合我们这里不考虑,只考虑常用的场景;)当然反射不是不好,只是反射需要考虑很多性能优化方面的东西,增加了代码的复杂性,也让框架变的很重(现在都是在追求轻量级,只有在DomainModel中需要将平面化的数据抽象;),所以何不使用简单方便的委托调用呢;

    注:如果你是初学者,这里的委托可以理解成是我们平时常用的Lambda表达式,也可以将它与Expression<T>结合起来使用,Expression<T>是委托在运行时的数据结构,而非代码执行路径;(兴趣的朋友可以查看本人的:LINQ系列文章

    下面我们来看一下演示代码:

     1 /*==============================================================================
     2  * Author:深度训练
     3  * Create time: 2013-07-28
     4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
     5  * Author Description:特定领域软件工程实践;
     6  *==============================================================================*/
     7 using System;
     8 using System.Collections.Generic;
     9 using System.Linq.Expressions;
    10 using System.Linq;
    11 using Infrastructure.Common.Cache;
    12 using Infrastructure.Common.Validator;
    13 
    14 namespace ConsoleApplication1.DomainModel
    15 {
    16     /// <summary>
    17     /// Order.
    18     /// </summary>
    19     [EntityCache(10, true)]
    20     [EntityValidator(ValidatorOperationType.All)]
    21     public class Order
    22     {
    23         /// <summary>
    24         /// Order code.
    25         /// </summary>
    26         public string OrderCode { get; set; }
    27 
    28         /// <summary>
    29         /// Items filed.
    30         /// </summary>
    31         private List<Item> items = new List<Item>();
    32         /// <summary>
    33         /// Gets items .
    34         /// </summary>
    35         public IEnumerable<Item> Items { get { return items; } }
    36 
    37         /// <summary>
    38         /// Submit order date.
    39         /// </summary>
    40         public DateTime SubmitDate { get; set; }
    41 
    42         /// <summary>
    43         /// Mark <see cref="DomainModel.Order"/> Instance.
    44         /// </summary>
    45         /// <param name="orderCode">Order code. </param>
    46         public Order(string orderCode)
    47         {
    48             this.OrderCode = orderCode;
    49         }
    50 
    51         /// <summary>
    52         /// Sum items prices.
    53         /// </summary>
    54         /// <param name="itemUsingType">item type.</param>
    55         /// <returns>prices .</returns>
    56         public double SumPrices(int itemUsingType)
    57         {
    58             double resultPrices = 0.00;
    59             var currentItems = items.GroupBy(item => item.ItemUsingType).Single(group => group.Key == itemUsingType);
    60             if (currentItems.Count() > 0)
    61             {
    62                 foreach (var item in currentItems)
    63                 {
    64                     resultPrices += item.Price;
    65                 }
    66             }
    67             return resultPrices;
    68         }
    69 
    70         /// <summary>
    71         /// Add item to order.
    72         /// </summary>
    73         /// <param name="item">Item.</param>
    74         /// <returns>bool.</returns>
    75         public bool AddItem(Item item)
    76         {
    77             if (!item.ItemCode.Equals(string.Empty))
    78             {
    79                 this.items.Add(item);
    80                 return true;
    81             }
    82             return false;
    83         }
    84     }
    85 }
    View Code

    这是一个订单领域实体,它里面引用了一个Item的商品类型;

     1 /*==============================================================================
     2  * Author:深度训练
     3  * Create time: 2013-07-28
     4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
     5  * Author Description:特定领域软件工程实践;
     6  *==============================================================================*/
     7 using System;
     8 
     9 namespace ConsoleApplication1.DomainModel
    10 {
    11     /// <summary>
    12     /// Order item.
    13     /// </summary>
    14     public class Item
    15     {
    16         /// <summary>
    17         /// Item code.
    18         /// </summary>
    19         public Guid ItemCode { get; set; }
    20 
    21         /// <summary>
    22         /// Item price.
    23         /// </summary>
    24         public float Price { get; set; }
    25 
    26         /// <summary>
    27         /// Item using type.
    28         /// </summary>
    29         public int ItemUsingType { get; set; }
    30     }
    31 }
    View Code

    上面代码应该没有问题,基本的订单领域模型大家都太熟了;为了保证上面的代码是绝对的正确,以免程序错误造成阅读者的不爽,所以都会有100%的单元测试覆盖率;这里我们主要使用的是Order类中的SumPrices方法,所以它的UnitTest是100%覆盖;

    图1:

    Order中的SumPrices方法的UnitTest代码:

     1 using System;
     2 using Microsoft.VisualStudio.TestTools.UnitTesting;
     3 using NSubstitute;
     4 
     5 namespace ConsoleApplication.UnitTest
     6 {
     7     using ConsoleApplication1.DomainModel;
     8 
     9     /// <summary>
    10     /// Order unit test.
    11     /// </summary>
    12     [TestClass]
    13     public class DomainModelOrderUnitTest
    14     {
    15         /// <summary>
    16         /// Order sumprices using type 1 test.
    17         /// </summary>
    18         [TestMethod]
    19         public void DomainModelOrderUnitTest_SumPrices_ItemUsingTypeIs1_UnitTest()
    20         {
    21             Order testOrder = new Order(Guid.NewGuid().ToString());
    22 
    23             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
    24             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
    25 
    26             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
    27             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
    28 
    29             double result = testOrder.SumPrices(1);
    30 
    31             Assert.AreEqual(result, 25.0F);
    32         }
    33 
    34         /// <summary>
    35         /// Order sumprices using type is 2 test.
    36         /// </summary>
    37         [TestMethod]
    38         public void DomainModelOrderUnitTest_SumPrices_ItemUsingTypeIs2_UnitTest()
    39         {
    40             Order testOrder = new Order(Guid.NewGuid().ToString());
    41 
    42             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
    43             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
    44 
    45             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
    46             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
    47 
    48             double result = testOrder.SumPrices(2);
    49 
    50             Assert.AreEqual(result, 50.0F);
    51         }
    52     }
    53 }
    View Code

    在以往我基本上不写单元测试的,但是最近工作上基本上都需要写每个方法的单元测试,而且要求是100%覆盖,只有这样才能保证代码的正确性;也建议大家以后多写单元测试,确实很有好处,我们应该把单元测试做起来;下面我们言归正传;

    由于我们的Order是在DomainModel Layer中,现在有一个需求就是在Infrastructure Layer 加入一个动态计算Order中指定Item.ItemUsingType的所有Prices的功能,其实也就是说需要将我们的一些关键数据通过这个功能发送给远程的Service之类的;这个功能是属于Infrastructure中的Common部分也就是说它是完全独立与项目的,在任何地方都可以通过它将DomainModel中的某些领域数据发送出去,那么这样的需求也算是合情合理,这里我是为了演示所以只在Order中加了一个SumPrices的方法,可能还会存在其他一些DomainModel对象,然后这些对象都有一些关键的业务数据需要在通过Infrastructure的时候将它们发送出去,比如发送给配送部门的Service Interface;

    那么常规设计可能需要将扩展点配置出来放在指定的配置文件里面,然后当对象经过Infrastructure Layer中的指定Component时触发事件路由,然后从缓存中读取出配置的信息执行,那么配置文件可能大概是这样的一个结构:DomainEntity名称、触发动作、方法名称、参数,DomainEntity名称是确定聚合根,触发动作是对应Infrastructure中的组件,当然你也可以放在DomainModel中;这里只关心方法名称、参数;

    当然这里只演示跟方法调用相关的代码,其他的不在代码中考虑;我们来看一下相关代码:

    1 using System;
    2 namespace Infrastructure.Common
    3 {
    4     public interface IBusinessService
    5     {
    6         void SendBusinessData(object callAuthor, string methodName, object parameterInstance);
    7         void SendBusinessData<P>(Func<P, object> callFun, P parameter);
    8     }
    9 }
    View Code

    这是业务调用接口;

     1 using System;
     2 using System.Reflection;
     3 using System.Linq;
     4 using System.Linq.Expressions;
     5 
     6 namespace Infrastructure.Common
     7 {
     8     /// <summary>
     9     /// Business service .
    10     /// </summary>
    11     public class BusinessService : IBusinessService
    12     {
    13         /// <summary>
    14         /// Send service data interface .
    15         /// </summary>
    16         private ISendServiceData sendService;
    17 
    18         /// <summary>
    19         /// Mark <see cref="Infrastructure.Common.ISendServiceData"/> instance.
    20         /// </summary>
    21         /// <param name="sendService"></param>
    22         public BusinessService(ISendServiceData sendService)
    23         {
    24             this.sendService = sendService;
    25         }
    26 
    27         /// <summary>
    28         /// Send business data to service interface.
    29         /// </summary>
    30         /// <param name="callAuthor">Object author.</param>
    31         /// <param name="methodName">Method name.</param>
    32         /// <param name="parameterInstance">Method call parameter.</param>
    33         public void SendBusinessData(object callAuthor, string methodName, object parameterInstance)
    34         {
    35             object result =
    36                 callAuthor.GetType().GetMethod(methodName).Invoke(callAuthor, new object[] { parameterInstance });
    37             if (result != null)
    38             {
    39                 sendService.Send(result);
    40             }
    41         }
    42 
    43         /// <summary>
    44         /// Send business data to service interface.
    45         /// </summary>
    46         /// <typeparam name="P"></typeparam>
    47         /// <param name="callFun"></param>
    48         /// <param name="parameter"></param>
    49         public void SendBusinessData<P>(Func<P, object> callFun, P parameter)
    50         {
    51             object result = callFun(parameter);
    52             if (result != null)
    53             {
    54                 sendService.Send(result);
    55             }
    56         }
    57     }
    58 }
    View Code

    这里简单实现IBusinessService接口,其实代码很简单,第一个方法使用反射的方式调用代码,而第二个方法则使用委托调用;在实现类里面还包含了一个简单的接口;

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace Infrastructure.Common
     8 {
     9     public interface ISendServiceData
    10     {
    11         void Send(object sendObject);
    12     }
    13 }
    View Code

    目的是为了方便单元测试,我们来看一下单元测试代码;

     1 using System;
     2 using Microsoft.VisualStudio.TestTools.UnitTesting;
     3 using Infrastructure.Common;
     4 using ConsoleApplication1.DomainModel;
     5 using NSubstitute;
     6 
     7 namespace Infrastructure.Common.UnitTest
     8 {
     9     [TestClass]
    10     public class InfrastructureCommonBusinsessServiceUnitTest
    11     {
    12         [TestMethod]
    13         public void InfrastructureCommonBusinsessServiceUnitTest_BusinessService_SendBusinessData()
    14         {
    15             Order order = new Order(Guid.NewGuid().ToString());
    16 
    17             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
    18             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
    19 
    20             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
    21             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
    22 
    23             ISendServiceData mockISendServiceData = Substitute.For<ISendServiceData>();
    24             object sendresult = null;
    25             mockISendServiceData.When(isend => isend.Send(Arg.Any<object>())).Do(callinfo => sendresult = callinfo.Arg<object>());
    26 
    27             BusinessService testService = new BusinessService(mockISendServiceData);
    28             testService.SendBusinessData(order, "SumPrices", 1);
    29 
    30             Assert.AreEqual((double)sendresult, 25);
    31         }
    32 
    33         [TestMethod]
    34         public void InfrastructureCommonBusinsessServiceUnitTest_BusinessService_SendBusinessDataGen()
    35         {
    36             Order order = new Order(Guid.NewGuid().ToString());
    37 
    38             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
    39             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
    40 
    41             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
    42             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
    43 
    44             ISendServiceData mockISendServiceData = Substitute.For<ISendServiceData>();
    45             object sendresult = null;
    46             mockISendServiceData.When(isend => isend.Send(Arg.Any<object>())).Do(callinfo => sendresult = callinfo.Arg<object>());
    47 
    48             BusinessService testService = new BusinessService(mockISendServiceData);
    49             testService.SendBusinessData<Order>(ord => { return ord.SumPrices(1); }, order);
    50 
    51             Assert.AreEqual((double)sendresult, 25);
    52         }
    53     }
    54 }
    View Code

    在第二个单元测试方法里面我们将使用Lambda方式将逻辑直接注入进BusinessService中,好就好这里;可以将Lambda封进Expression<T>然后直接存储在Cache中或者配置中间,彻底告别反射调用吧,就好比委托一样没有人会在使用委托在定义个没用的方法;(所以函数式编程越来越讨人喜欢了,可以关注一下F#;)总之使用泛型解决类型不确定问题,使用Lambda解决代码逻辑注入;大胆的尝试吧,将声明与实现彻底分离;

    (对.NET单元测试有兴趣的朋友后面一篇文章会详细的讲解一下如何做单元测试,包括Mock框架的使用;)

    3】被忽视的特性(Attribute)设计方式

    大部分人对特性的定义是代码的“数据注释”,就是可以在运行时读取这个特性用来做类型的附加属性用的;通常在一些框架中对DomainModel中的Entity进行逻辑上的关联用的,比如我们比较熟悉的ORM,都会在Entity的上面加上一个类似 [Table(TableName=”Order”)] 这样的特性声明,然后再在自己的框架中通过反射的方式去在运行时差找元数据找到这个特性,然后就可以对附加了这个特性的类型进行相关的处理;

    这其实没有问题,很正常的设计思路,也是比较通用的设计方法;但是我们的思维被前人固化了,难道特性就只能作为代码的声明吗?问过自己这个问题吗?

    我们继续使用上面2】小结中的代码作为本节演示代码,现在我们假设需要在DomainModel中的Entity上面加上两个特性第一个用来断定它是否需要做Cache,第二个用来确定关于Entity操作验证的特性;

    看代码:

    1 /// <summary>
    2     /// Order.
    3     /// </summary>
    4     [EntityCache(10, true)]
    5     [EntityValidator(ValidatorOperationType.All)]
    6     public class Order
    7     {}
    View Code

    代码应该很明了,第一EntityCache用来设计实体的缓存,参数是缓存的过期时间;第二个特性EntityValidator用来设置当实体进行相关处理的时候需要的验证类型,这里选择是所有操作;

    现在的问题是关于特性的优先级,对于Order类的处理到底是先Cache然后验证,还是先验证然后Cache或者说内部没有进行任何的逻辑处理;如果我们将特性的视为代码的标识而不是真正的逻辑,那么对于优先级的处理会比较棘手,你需要设计如何将不同的特性处理逻辑关联起来;比较合理的设计方法是特性的处理链表;本人之前设计过AOP的简单框架,就遇到过对于特性的优先级的处理经验,也是用的链表的方式将所有的特性按照顺序串联起来然后将对象穿过特性内部逻辑,这也符合DDD的中心思想;

    下面我们来看代码:

     1 Codeusing System;
     2 
     3 namespace Infrastructure.Common
     4 {
     5     [AttributeUsage(AttributeTargets.Class)]
     6     public abstract class EntityOperation : Attribute
     7     {
     8         protected EntityOperation NextOperation { get; set; }
     9     }
    10 }
    View Code

    我们抽象出所有的处理,然后在内部包含下一个处理逻辑的特性实例;然后让各自的Attribute继承自它;

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace Infrastructure.Common.Cache
     8 {
     9     [AttributeUsage(AttributeTargets.Class)]
    10     public class EntityCache : EntityOperation
    11     {
    12         public EntityCache(int cacheTime, bool IsEnable)
    13         {
    14             this.ExpireTime = cacheTime;
    15         }
    16 
    17         public int ExpireTime { get; set; }
    18 
    19     }
    20 }
    View Code
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace Infrastructure.Common.Validator
     8 {
     9     public enum ValidatorOperationType
    10     {
    11         Insert,
    12         Delete,
    13         Update,
    14         Select,
    15         All
    16     }
    17 
    18     [AttributeUsage(AttributeTargets.Class)]
    19     public class EntityValidator : EntityOperation
    20     {
    21         public EntityValidator(ValidatorOperationType validatorType)
    22         {
    23 
    24         }
    25     }
    26 }
    View Code

    根据特性在类的先后顺序就可以控制他们的优先级;

    图2:

    上图很直观的表现了链表设计思想,再通过仔细的加工应该会很不错的;

    4】扩展方法让你的对象如虎添翼(要学会使用扩展方法的设计思想)

    扩展方法我们用的应该不算少的了,在一些新的框架中到处都能看见扩展方法的优势,比如:ASP.NETMVC、EntityFramework等等特别是开源的框架用的很多;

    那么我们是不是还停留在原始社会,应该尝试接受新的设计思想,尽管一开始可能不太适应,但是当你适应了之后会让你的设计思想提升一个境界;

    下面我们还是使用上面的演示代码来进行本节的代码演示,现在假如有一个这样的需求,为了保证DomainModel的完全干净,我们在应用层需要对领域模型加入一些非业务性的行为,这些行为跟DomainModel本身没有直接关系,换句话说我们这里的Order聚合实体可能需要一个获取Order在Cache中存活了多长时间的方法;那么在以往我们可能提供一个方法然后把Order实例作为参数这样来使用,但是这里我们的需求是该方法是Order对象的方法而不是其他地方的方法;

    所以这里使用扩展方法就可以在不改变对象本身业务逻辑的情况下扩展对象行为;最关键的是扩展方法为后面的链式编程提供了基石;从长远来看DomainModel将会被独立到ThreadProcess中,当系统初始化时部分的DomainModel将直接驻留在内存中,然后通过系统本地化将扩展方法加入,这样就可以在不改变对象的情况下添加行为,这也为行为驱动设计提供了好的技术实现;

    用纯技术性的假设没有说服力,上面说给领域本身加上获取Cache的方法,肯定会有朋友说这完全没有必要,提供一个简单的方法就OK了,恩 我觉得也有道理,那么下面的需求你将不得不说妙;

    【需求简述】:对象本身的行为不是固定不变的,尤其我们现在设计对象的时候会将对象在全局情况下的所有行为都定义在对象内部,比如我们正常人,在不同的角色中才具有不同的行为,我们只有在公司才具有“打开服务器”的行为,只有在家里才可以“亲吻”自己的老婆的行为;难道我们在设计User类的时候都将这些定义在对象内部吗?显然不符合逻辑,更不符合面向对象设计思想,当然我们目前基本上都是这么做的;

    (有兴趣的朋友可以参考:BDD(行为驱动设计)、DCI(数据、上下文、交互)设计思想;)

    现在我们来为Order添加一组行为,但是这组 行为只有在某些场景下才能使用,这里只是为了演示而用,真要在项目中设计还需要考虑很多其他因素;

     1 namespace DomainModelBehavior.Order.ShippingBehavior
     2 {
     3     using ConsoleApplication1.DomainModel;
     4     public static class OrderBehavior
     5     {
     6         public static Order TaxRate(this Order order)
     7         {
     8             return order;
     9         }
    10     }
    11 }
    12 
    13 namespace DomainModelBehavior.Order.ShoppingCart
    14 {
    15     using ConsoleApplication1.DomainModel;
    16     public static class OrderBehavior
    17     {
    18         public static Order Inventory(this Order order)
    19         {
    20             return order;
    21         }
    22     }
    23 }
    View Code

    这里有两个位于不同namespace中的行为,他们对应不同的场景;第一个TaxRate用来计算税率的行为,只有在Order对象已经处于提交状态时用的;那么第二个行为Inventory用来计算库存的,用户在Shoppingcart的时候用来确定是否有足够的库存;当然这里我只是假设;

    然后我们就可以在不同的场景下进行命名空间的引用,比如我们现在Shoppingcart阶段将不会使用到TaxRate行为;

     1 using System;
     2 
     3 namespace ConsoleApplication1
     4 {
     5     using DomainModelBehavior.Order.ShoppingCart;
     6     class Program
     7     {
     8         static void Main(string[] args)
     9         {
    10             DomainModel.Order order = new DomainModel.Order(Guid.NewGuid().ToString());
    11             order.Inventory();
    12         }
    13     }
    14 }
    View Code

    例子虽然有点简单,但是应该能说明扩展方法的基本使用方式,对于DCI架构的实现会复杂很多,需要好好设计才行;

    5】别怕Static属性(很多人都怕Static在Service模式下的设计,其实要学会使用线程本地存储(ThreadStatic))

    很多时候我们在设计对象的时候,尤其是面向Context类型的,很希望能通过某个静态属性直接能拿到Context,所以会定义一个静态属性用来保存对象的某个实例;但是会有很多人都会排斥静态属性,动不动就说性能问题,动不动就收多线程不安全等等借口,难道静态属性就没有存在必要了嘛;

    不用静态属性你哪来的ASP.NET中的CurrentContext直接,如果怕因为多线程问题导致数据不完整,建议使用线程本地存储;没有什么好怕的,多用就熟悉了;用也很简单,直接在静态属性上面加上这个特性就OK了,前提是你已经考虑了这个属性是线程内部共享的不是应用程序级别的共享;

    1 /// <summary>
    2         /// 数据源的操作
    3         /// </summary>
    4         [ThreadStatic]
    5         private static IDataSourceOperation datasourceoperation = IDataSourceOperationFactory.Create();
    View Code

    6】泛型的协变与逆变(设计架构接口(Interface)时要注意对象的协变、逆变)

    越来越多的人喜欢自己捣鼓点东西出来用用,这很不错,时间长了设计能力自然会得到提升的;但是最近发现我们很多泛型在设计上缺乏转换的控制,也就是这里的协变和逆变;我们有一个Item类型,现在我们需要对它进行更加具体化,我们派生出一个Apple类型的Item;

    1 List<Apple> apples = new List<Apple>();
    2  List<Item> items = apples;
    View Code

     这段代码是编译不通过的,因为List<T> 在定义的时候就不支持逆变、但是如果换成下面这样的代码是完全可以的;

    1 List<Apple> apples = new List<Apple>();
    2  IEnumerable<Item> items = apples;
    View Code

    很容易的就可以得到集合的转换,虽然很简单的功能但是在设计上如果运用好的话能大大改变接口的灵活性;你可能会有一个疑问,为什么具体实现List<T>不支持协变而IEnumerable<out T>反而支持协变;这就是面向对象设计的思想,接口本质是抽象的,抽象的不会有具体的实现所以它作为协变不会存在问题,但是逆变就会有问题;

    7】使用泛型的类型推断(还在为参数类型烦恼吗)

    在设计泛型方法的时候要学会使用类型推断技巧,这样会很方便的在调用的时候减少你显示调用<>的代码,也会显得很优美;大家应该都比较熟悉Func泛型委托,它是C#3中的主角,也是函数式编程的基础,我们在设计某个方法的时候会将逻辑暴露在外部,然后通过Lambda的方式注入进来;现在的LINQ都是这么实现的,比较熟悉的Where方法、Select方法,都需要我们提供一个作为它内部逻辑的函数段;

    1 List<Item> items = new List<Item>();
    2             items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
    3             items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
    4             items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
    5             items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
    6 
    7             items.Where<Item>(item => item.ItemUsingType == 1);
    8             items.Where(item=>item.ItemUsingType==1);
    View Code

    这里有两种调用Where的代码,哪一种看上去舒服一点有没一点,不用我说了;那我们看一下它的定义:

    1 public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
    View Code

    我们看到TSource类型占位符,很容易理解,这是一个扩展IEnumerable<TSource>类型的方法,系统会自动的匹配TSource;我们在设计的时候也要借鉴这种好的设计思想;

    (有兴趣的朋友可以参见本人的:.NET深入解析LINQ框架(一:LINQ优雅的前奏)

    8】链式编程(设计符合大脑思维习惯的处理流程)

    其实那么多的C#新特性都是为了能让我们编写代码能更方便,总之一句话是为了更符合大脑思维习惯的编程模式;

    C#从纯面向对象渐渐的加入了函数式模式,从静态类型逐渐加人动态类型特性;C#现在变成多范式编程语言,其实已经很大程度满足我们的日常需求;以往我们都会为了动态行为编写复杂的Emit代码,用很多CodeDom的技术;现在可以使用Dymanic解决了;

    这节我们来看一下关于如何设计线性的链式方法,这不是技术问题,这是对需求的理解能力;可以将链式思想用在很多地方,只要有逻辑有流程的地方都可以进行相关设计,首先你要保证你是一个正常思考问题的人,别设计出来的方法是反的,那么用的人会很不爽的;这里我举一个我最近遇到的问题;

    8.1】链式编程(多条件(方法碎片化)调用

    我们都熟悉DTO对象,它是从UI传过来的数据集合,简单的业务逻辑Application Layer将它转换成DomainModel中的Entity,如果复杂的业务逻辑是不能直接将DTO进行转换的;但是在转换过程中我们总是少不了对它的属性判断,如果UserName不为空并且Password不为空我才能去验证它的合法性,等等;类似这样的判断;这里我们将运行扩展方法将这些逻辑判断链起来,并且最后输出一个完整的Entity对象;

     1 using System.Collections.Generic;
     2 
     3 namespace ConsoleApplication1
     4 {
     5     public static class DtoValidator
     6     {
     7         public static IEnumerable<TSource> IsNull<TSource>(this IEnumerable<TSource> tList)
     8         {
     9             return tList;
    10         }
    11         public static IEnumerable<TSource> IsLength<TSource>(this IEnumerable<TSource> tList, int max)
    12         {
    13             return tList;
    14         }
    15         public static IEnumerable<TResult> End<TResult>(this IEnumerable<object> tList, IEnumerable<TResult> result)
    16         {
    17             result = new List<TResult>();
    18             return result;
    19         }
    20     }
    21 }
    View Code

    有一组扩展方法,用来做验证用的;

    1 List<Order> orderList = null;
    2 
    3 List<Dto.OrderInfoDto> dtoList = new List<Dto.OrderInfoDto>();
    4 
    5 dtoList.IsNull().IsLength(3).End(orderList);
    View Code

    由于时间关系我这里只是演示一下,完全可以做的很好的,在判断的最后拿到返回的列表引用最后把数据送出来;

    (有一个开源验证框架应该还不错,目前工作中在用:FluentValidator)

    9】部分类、部分方法的使用(扩大设计范围)

    部分类不是新的特性,而部分方法是新特性;我们通过灵活运用部分类可以将发挥很大作用,比如我们完全可以将类的部分实现完全隔离在外部,起到低耦合的作用,甚至可以将声明式设计元编程运用在C#中,比较经典就是ASP.NET后台代码和前台的模板代码,在运行时然后再通过动态编译合起来,我们不要忘记可以使用部分类、部分方法来达到在运行时链接编译时代码和运行时代码,类似动态调用的效果;由于这部分内容比较简单,是设计思想的东西,所以没有什么要演示的,只是一个总结;

    总结:内容虽然简单,但是要想运用的好不简单,这里我只是总结一下,希望对大家有用,谢谢;

    示例DEMO地址https://files.cnblogs.com/wangiqngpei557/ConsoleApplication1.zip

  • 相关阅读:
    面条代码 vs. 馄沌代码
    GraphQL 到底怎么用?看看这个例子就知道了
    程序员难逃二八法则,如何晋升为头部 20% 玩家?
    正则匹配负正数和负小数
    js、Jquery处理自动计算的输入框事件
    mobile easyui兼容实体数据(tree插件为例)
    framework7中一行的字如果过多就省略号显示的CSS写法
    PHP获取系统时间不对的解决办法(转载)
    BZOJ 3156: 防御准备
    P4098 [HEOI2013]ALO
  • 原文地址:https://www.cnblogs.com/shangguanjunzhi/p/3414581.html
Copyright © 2020-2023  润新知