目录:
第一讲
系统的复用性离不开系统的面向对象性
第二讲
什么应该提取出来,什么应该保留
第三讲
实现一种功能的代码只能出现在一处
第四讲
方法的重载真的用不到吗?
第五讲
复用离不开反射和IOC
第六讲
我的系统结构~将所有可以抽象的项目进行抽象
第一讲 系统的复用性离不开系统的面向对象性
今天主要说一下“系统的复用性离不开系统的面向对象性”,我们可能有一种感觉,那就是在开发一个项目时用到了一段代码块,在另一个项目中也用到了,我们通常的作法就是ctrl+C,然后ctrl+V,呵呵,这样做的好处就是省事,不好的地方也是“不省事”,为什么这样说呢?
省事:因为它不需要考虑什么,只是为了实现而去实现,而这肯定是不提倡这种方式的,因为使用这种方式编程的人,永远不会体会到其中的乐趣,可能只是为了工资而去工作。
不省事:在程序的测试阶段,工程师们突然发现了你复制的那块代码出现问题了,可能是性能问题,也可能是业务问题,也可能是。。。,反正是出问题了,那你作为一个负责的程序员,会怎么样,当然是一个一个的去改了,同样是ctrl+C,ctrl+V,但此时你的一定再后悔,不如把块代码,或者那个方法,再或者那个类,再或者那个项目给抽象了,呵呵。
今天我就来说一下系统要想得到复用,必须把系统先进行抽象,也就是你的系统代码要符合面向对象的特性,这个系列我将会用最近开发的“通用后台系统”做为实例,讲给大家
这个系统中,用到了4个解决方案文件夹,我下面来分别说一下它们
一 Project.Common文件夹:它为所有项目提供一个公用的,不依赖于其它项目的项目集合,如图:
OnlinePayment:支付功能模块相关
Standard:服务端和端户端持久化相关
VCommons:公用功能类库相关
VConfig:全局公用配置信息相关
二 Project.Core文件夹:它是对N层模型的抽象,将Web(UI),Entity(Model),Data(DAL)等各层的核心公用代码抽象出来,形成一个与领域无关的项目集合,如图:
Data.Commons:对数据层的抽象,本例中使用了Linq To SQL做为底层ORM,它同样适用于Entity
Frameworks
Entity.Commons:对实体层的抽象,本例中的实体全部是对linq
to sql原生实体的扩展,这也多谢微软的partial关键字,并对实体赋值进行了跟踪
Web.Commons:对WEB层的抽象,本例是标准的MVC模式的风格,对controller进行了抽象,以极对公用特性的抽象,如登陆验证等
三 Common.Background文件夹:它是对标准的后台管理系统的抽象,包括最基础的后台基础,有对用户,菜单,权限,部门等模块的管理,它适用于所有后台项目,如图:
Common.Background.Data:对后台数据层的实现,它继承自Data.Commons
Common.Background.Entity:对后台实体层的实现,它继承自Entity.Commons
Common.Background.Service:后台业务层的实现,它处理最基础的业务逻辑
Common.Background.Web.Controllers:后台UI层的实现,它继承自Web.Commons
四 个性化项目文件夹,这个就是和领域有关的真正的项目了,它有自己的架构标准,如图
我们可以看到,它也是标准的三层架构,前台和后台公用Data和Entity层,项目比较简单,没有使用Service层。
通过一个真正项目的解说,您是否对如何提高程序的复用性有一个比较清晰的认识了呢?呵呵!
第二讲 什么应该提取出来,什么应该保留
在进行项目整体架构设计时,我们应该明确知道哪些项目是可以被重复再利用的,而哪些项目是与领域模块关系密切的,对于后者我们是应该在解决方案中保留的,而前者则是应该提取出来的。
在一个完整的解决方案中,应该是由“公用的类库”,“核心的项目基础层”和“与业务领域关系密切个性项目组”组成的,对于我开会的那个项目来说,也是遵循这样一个原则:
将与领域和项目无关的项目进行抽象,形成一个最基础的层,称为Project.Common
将与架构模式有关,而与领域无关的项目,形成一个架构模式核心层,称为Project.Core
将与指定领域有关的,个性化业务组成的代码,叫做领域层,它的名称由项目含义确定
在这篇文章里,我们主要是找到一个项目中,可以被抽象和被重复再利用的点,它可以是个方法,也可能是个类,再可以是个接口,一个项目等,在架构领域时,在代码重复再利用方法,我认为应该是以一个项目为单位的,一个项目里,可能包括的是一些可以被再利用的类的集合。恩,就是这样的。
对于电子商务网站,它的解决方案架构可能是:
ABC公司的电子商备平台架构
ABC_WEB (MVC or WebForm)
ABC_Service
ABC_Data
ABC_Entity
使用所有电子商务类网站的核心代码:
EC_Web
EC_Entity
EC_Service
EC_Data(可以被多个数据库公用的一些dbml模块)
使用通用核心代码:
WEB.Commons
Entity.Commons
Data.Commons
最底层才是公用类库
VCommons
Standard
VLogs
等等
第三讲 实现一种功能的代码只能出现在一处
从标题中可以看到本篇文章将介绍代码随意性的缺点及由此引发的后果,首先,来说一下同一功能的代码在多个程序中被编写多次的后果:
1 它破坏了面向对象的“单一职责”的原则
2 当代码逻辑复杂时,或者进行二次开发时,程序员将对方法调用产生歧义,即不知道应该使用哪个方法,即代码可读性差
3 当这个不规范的方法逻辑需要修改时,你将会进行多次重复的调整,这是一个程序不希望做的事
解决方法:
当几个模块需要用到同一功能,或者功能相似的方法时,应该先将公用的功能抽象成一个新的方法,再把不同的地方抽象成其它方法,这也就是《重构》中的extract
method 。
下面看一下代码:
不规范的:
|
View Code
public bool RegisterUser(Userbase entity)
{
bool flag = false;
try
{
//注册用户逻辑
//添加日志逻辑
}
catch (Exception)
{
throw;
}
return flag;
} |
规范的,再利用率高的,面向对象的:
|
View Code
/// <summary>
/// 注册用户
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public bool RegisterUser(Userbase entity)
{
bool flag = false;
try
{
//注册用户逻辑
}
catch (Exception)
{
throw;
}
return flag;
}
/// <summary>
/// 添加日志
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public bool AddLog(Log entity)
{
bool flag = false;
try
{
//添加日志逻辑
}
catch (Exception)
{
throw;
}
return flag;
} |
通过代码我们可以看到,不规范的代码将多种功能方法合成了一种,属于逻辑混乱了,而规范的代码逻辑清晰,职责分明,代码重复利用率较高。
上面说的主要核心内容是将公用的部分从一个方法中提取出来,生成一个新的方法,这个重构中叫做“提取到方法”
,另外一个核心内容就是方法的”单一职责“,即一个方法干一件事,将出现复杂事件时,将多个方法进行组合调用即可
这回主要说一个重构中的提取,其实不仅方法可以被提取,类,及整个项目也可以被提取,只要他们有被提取的必要!
一个例子:对于一个数据实体操作的基类,它包括了其它所有实体类共有的属性(DB)和方法(SubmitChanges),这可以理解了”提取到类“,当然这也是类的继承及面向对象的一个例子。
|
/// <summary>
/// LINQ数据库操作基类
/// </summary>
public abstract class RepositoryBase
{
public RepositoryBase(DataContext db)
{
DB = db;
}
protected System.Data.Linq.DataContext DB { get; private set; }
#region DBContext SubmitChanges
/// <summary>
/// XXB默认提交【重写时候可能需要写入自定义的类似约束的逻辑】
/// </summary>
protected virtual void SubmitChanges()
{
ChangeSet cSet = DB.GetChangeSet();
if (cSet.Inserts.Count > 0
|| cSet.Updates.Count > 0
|| cSet.Deletes.Count > 0)
{
try
{
DB.SubmitChanges(System.Data.Linq.ConflictMode.ContinueOnConflict);
}
catch (System.Data.Linq.ChangeConflictException ex)
{
foreach (System.Data.Linq.ObjectChangeConflict occ in DB.ChangeConflicts)
{
// 使用当前数据库中的值,覆盖Linq缓存中实体对象的值
occ.Resolve(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
// 使用Linq缓存中实体对象的值,覆盖当前数据库中的值
occ.Resolve(System.Data.Linq.RefreshMode.KeepCurrentValues);
// 只更新实体对象中改变的字段的值,其他的保留不变
occ.Resolve(System.Data.Linq.RefreshMode.KeepChanges);
}
DB.SubmitChanges();
}
}
}
#endregion
} |
还有一种更大程序上的提取,即”提取到项目“,就是说,它的整个项目都是其它项目公用的部分,所有把整个项目抽象出来
Entity.Commons这个项目是对所有解决方案的所有实体层进行的抽象,它里面有对实体的分页,实体参数组织,实体消息返回及实体统一验证等功能,都在Entity.Commons里实现
EntityBase.cs代码如下:
|
View Code
/// <summary>
/// 实体基类,与linq to sql数据映射对应
/// </summary>
[Serializable]
public abstract class EntityBase /*: INotifyPropertyChanging, INotifyPropertyChanged*/
{
public EntityBase()
{
this.IsRealDeleted = true;
}
#region 实体相关
/// <summary>
/// 实体主键
/// 在子类中对它赋值,在其它类中可以访问到这个主键属性
/// </summary>
public abstract object[] PrimaryKey { get; }
/// <summary>
/// 是否执行真删除,默认为true,如果设为false,则更新实体的status字段
/// </summary>
public virtual bool IsRealDeleted { get; protected set; }
/// <summary>
/// 记录修改的列信息
/// 子类可以根据需要,去复写记录数据的方式
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void PropertyChangedEvent(object sender, PropertyChangedEventArgs e)
{
#region 添加修改字段记录
// VLog.IVLog log = new VLog.SqlVLog();
// log.Write(string.Format("被修改的字段{0}", e.PropertyName));
#endregion
#region 记录修改的字段和修改成的值
Type t = this.GetType();
PropertyInfo pi = t.GetProperty(e.PropertyName);
object value = pi.GetValue(this, null);
this.OnPropertyChanged(e.PropertyName, value);
#endregion
}
#endregion
#region 实体验证
/// <summary>
/// 验证的字段名集合,为NULL表示验证所有字段
/// </summary>
public string[] ValidFields { get; set; }
/// <summary>
/// 数据验证(是否成功)
/// 虚属性,子类可以根据自己的逻辑去复写
/// </summary>
public virtual bool IsValid { get { return this.GetRuleViolations().Count() == 0; } }
/// <summary>
/// 获取验证失败的信息枚举,默认提供了非空验证
/// 它使用了简单的迭代器,如果GetRuleViolations有错误则返回迭代列表
/// </summary>
/// <returns></returns>
public virtual IEnumerable<RuleViolation> GetRuleViolations()
{
PropertyInfo[] propertyInfo = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.
Instance);
if (ValidFields != null) propertyInfo = propertyInfo.Where(i => ValidFields.Contains(i.Name)).
ToArray();
foreach (var i in propertyInfo)
{
if (i.GetCustomAttributes(typeof(System.Data.Linq.Mapping.ColumnAttribute), false) != null
&& i.GetCustomAttributes(typeof(System.Data.Linq.Mapping.ColumnAttribute), false).
Count() > 0
&& !((System.Data.Linq.Mapping.ColumnAttribute)i.GetCustomAttributes(typeof(
System.Data.Linq.Mapping.ColumnAttribute), false)[0]).CanBeNull
&& !((System.Data.Linq.Mapping.ColumnAttribute)i.GetCustomAttributes(typeof(
System.Data.Linq.Mapping.ColumnAttribute), false)[0]).IsPrimaryKey)
if (i.GetValue(this, null) == null || string.IsNullOrEmpty(i.GetValue(this, null).
ToString()))
yield return new RuleViolation("*", i.Name);
}
}
#endregion
#region 重写linq to sql的一些东西
#region INotifyPropertyChanged and INotifyPropertyChanging Members
public event PropertyChangedEventHandler BasePropertyChanged;
public event PropertyChangingEventHandler BasePropertyChanging;
protected virtual void OnPropertyChanging(String propertyName)
{
if ((this.BasePropertyChanging != null))
{
this.BasePropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
protected virtual void OnPropertyChanged(String propertyName, object newValue)
{
if ((this.BasePropertyChanged != null))
{
this.BasePropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
if (_changeList == null)
return;
if (_changeList.ContainsKey(propertyName))
{
_changeList.Remove(propertyName);
}
_changeList.Add(propertyName, newValue);
}
protected bool IsPropertyChanged(string name)
{
return _changeList != null && _changeList.ContainsKey(name);
}
#endregion
#region Change tracking
private Dictionary<string, object> _changeList;
public Dictionary<string, object> GetChanges()
{
return _changeList;
}
private void StartTrackChanges()
{
if (_changeList != null)
{
throw new InvalidOperationException("This object is already tracking changes");
}
_changeList = new Dictionary<string, object>();
}
private bool _IsAlreadySaved = false;
public bool IsAlreadySaved()
{
return _IsAlreadySaved;
}
/// <summary>
/// 保存实体
/// </summary>
public void MarkEntitySaved()
{
_IsAlreadySaved = true;
}
/// <summary>
/// 实体初始化,开始跟踪实体的变化
/// </summary>
public virtual void Initialization()
{
this.StartTrackChanges();
}
#endregion
#endregion
}
#region 子类更新需要实现它的分部方法,内容如下
//partial void OnCreated()
// {
// base.IsRealDeleted = false;//假删除
// base.Initialization();//基类的某些属性初始化
// this.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler
(base.PropertyChangedEvent);//初始实体时,先订阅列修改的事件
// }
#endregion |
通过这篇文章,我们知道了,对于代码重构,不仅仅只对于方法而言,对于重构,也不仅仅只对一个项目而言,它可能是项目与项目之间的重构。
第四讲 方法的重载真的用不到吗?
在第三讲中我们主要关注了代码重构的思想,从方法重构到类重构再到项目重构,这是一个过程,一种思想上的升华,今天将继续我们“程序复用性设计”的旅程,说一下方法重载的重要性。
细心的朋友一定会非常关注net frameworks的源代码,即使只能看到它内部方法的定义,也足够了,在System.Web.Mvc这个命名空间下定义了很多关于MVC架构的东西,我们就以它为例来说一下方法重载吧!
重载的好处:
1 对方法调用的程序员来说,它是友好的(程序员只关心自己调用的方法签名即可,不用管参数为NULL怎么办这些逻辑)
2 对于代码维护量来说,它是容易的(核心代码只放在参数签名最多的方法中)
3 对于代码扩展来说,它是直接的(只要修改核心方法代码即可,而不用一个方法一个方法的去COPY)
以下是InputExtensions类下的几个方法:
|
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name)
{
return htmlHelper.CheckBox(name, null);
}
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, bool isChecked)
{
return htmlHelper.CheckBox(name, isChecked, null);
}
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, IDictionary
<string, object> htmlAttributes)
{
return CheckBoxHelper(htmlHelper, null, name, null, htmlAttributes);
}
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, object htmlAttributes)
{
return htmlHelper.CheckBox(name, ((IDictionary<string, object>) HtmlHelper.
AnonymousObjectToHtmlAttributes(htmlAttributes)));
}
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, bool isChecked,
IDictionary<string, object> htmlAttributes)
{
return CheckBoxHelper(htmlHelper, null, name, new bool?(isChecked), htmlAttributes);
}
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, bool isChecked,
object htmlAttributes)
{
return htmlHelper.CheckBox(name, isChecked, ((IDictionary<string, object>) HtmlHelper.
AnonymousObjectToHtmlAttributes(htmlAttributes)));
} |
我们看到这是一个在视图上建立复选框的方法,也是HtmlHelper类型的扩展方法,(即通过HtmlHelper的实例对象直接可以访问到这个方法),它提供了六个方法重载,程序员在使用时根据业务需求去调用,而在参数最少的方法中,直接为参数赋一个默认值即可。
我的项目中的方法重载:
以下是一个通过状态枚举类型,产生一个下拉列表框的扩展方法,它有两个重载方法,第一个是产生下拉列表
(public static MvcHtmlString StatusForSelectList(this
HtmlHelper html)),第二个是在产生下拉列表时,给它设一个默认值( public static
MvcHtmlString StatusForSelectList(this HtmlHelper html,
int selectedValue)),这时使用方法重载就更友好,而另一种作法就是直接用一个方法代替,这对于调用方的程序员来说是不友好的。
下面是两个方式的实现:
不友好的方式:
|
/// <summary>
/// 状态字段下列列表框,要求调用方的程序员为它传送一个默认值,如0,
即使写成int? selectValue,也会使程序员产生歧义
/// </summary>
/// <param name="html"></param>
/// <param name="selectedValue">选中的项</param>
/// <returns></returns>
public static MvcHtmlString StatusForSelectList(this HtmlHelper html, int selectedValue)
{
StringBuilder sb = new StringBuilder();
sb.Append("<select name='Status' id='Status'>");
//selectedValue等于0 ,然后去进行一个逻辑的实现
foreach (Status i in Enum.GetValues(typeof(Status)))
if ((int)i == selectedValue)
sb.AppendFormat("<option value='{0}' selected='selected'>{1}</option>", (int)i,
((Status)i).GetDescription());
else
{
if (i == Status.Normal) sb.AppendFormat("<option value='{0}' selected='selected'>{1}
</option>", (int)i, ((Status)i).GetDescription());
else
sb.AppendFormat("<option value='{0}'>{1}</option>", (int)i, ((Status)i).
GetDescription());
}
sb.Append("</select>");
return MvcHtmlString.Create(sb.ToString());
} |
下面这种方式是我们提倡的,也是微软主推的:
|
/// <summary>
/// 状态字段下列列表框
/// </summary>
/// <param name="html"></param>
/// <param name="selectedValue">选中的项</param>
/// <returns></returns>
public static MvcHtmlString StatusForSelectList(this HtmlHelper html, int selectedValue)
{
StringBuilder sb = new StringBuilder();
sb.Append("<select name='Status' id='Status'>");
foreach (Status i in Enum.GetValues(typeof(Status)))
if ((int)i == selectedValue)
sb.AppendFormat("<option value='{0}' selected='selected'>{1}</option>", (int)i,
((Status)i).GetDescription());
else
{
if (i == Status.Normal) sb.AppendFormat("<option value='{0}' selected='selected'>{1}
</option>", (int)i, ((Status)i).GetDescription());
else
sb.AppendFormat("<option value='{0}'>{1}</option>", (int)i,
((Status)i).GetDescription());
}
sb.Append("</select>");
return MvcHtmlString.Create(sb.ToString());
}
/// <summary>
/// 状态字段下列列表框
/// </summary>
/// <param name="html"></param>
/// <returns></returns>
public static MvcHtmlString StatusForSelectList(this HtmlHelper html)
{
return StatusForSelectList(html, 100);
} |
这使得代码很清晰,职责分明!
|
/// <summary>
/// LINQ数据库操作基类
/// </summary>
public abstract class RepositoryBase
{
#endregion
} |
第五讲 复用离不开反射和IOC
从本文标题中可以看出,主要说的是反射技术和控制反转(IOC)技术,本文主要先介绍一下我对这两种技术的理解及它们的优缺点,最后再用实例来说一下使用方法。
反射:可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。这里,它最重要的是“动态性”,即根据条件动态创建“指定类型”的“实例”。
|
1 // Using GetType to obtain type information:
2 int i = 42;
3 System.Type type = i.GetType();
4 System.Console.WriteLine(type); |
结果是:
System.Int32
本示例使用静态方法 GetType(Object 基类派生的所有类型都继承该方法)
获取变量类型的简单反射实例
|
1 // Using Reflection to get information from an Assembly:
2 System.Reflection.Assembly o = System.Reflection.Assembly.Load("mscorlib.dll");
3 System.Console.WriteLine(o.GetName()); |
结果是:
mscorlib, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
本示例使用反射获取已加载的程序集的完整名称
反射一般用在以下情况中:
- 需要访问程序元数据的属性。linq to sql 中使用很多
- 执行后期绑定,访问在运行时创建的类型的方法。与工厂模式一起使用,根据配置文件中的类型来动态建立实例
IOC:(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题。
控制反转还有一个名字叫做依赖注入(Dependency Injection)。简称DI。实现IOC的架构有很多如:Avalon
、Spring、JBoss及Unity等。
理解IOC:可以把IoC模式看做是工厂模式的升华,可以把IoC看作是一个大工厂,只不过这个大工厂里要生成的对象都是在XML文件中给出定义的,然后利用Java
的“反射”编程,根据XML中给出的类名生成相应的对象。
实现非常简单,根据容易名称去创建对象即可
|
/// <summary>
/// The static factory of container
/// </summary>
public sealed class ContainerManager
{
/// <summary>
/// Creates the specified container instance .
/// </summary>
/// <param name="containerName">Name of the container.</param>
/// <returns></returns>
public static IContainerContext GetContainer(string containerName)
{
return new UnityContainerContext(containerName);
}
} |
以下是在实际项目中的使用,IOC架构是用Unity,它的基础代码是:
|
/// <summary>
/// The specific container context for Unity
/// </summary>
public class UnityContainerContext : ContainerContextBase
{
#region Fields
/// <summary>
/// The lock used for synchronous
/// </summary>
private static readonly object _synlock = new object();
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="UnityContainerContext"/> class.
/// </summary>
/// <param name="name">The name.</param>
public UnityContainerContext(string name)
: base(name)
{
}
#endregion
#region Properties
/// <summary>
/// Gets the current context.
/// </summary>
/// <value>The current context.</value>
private HttpContext CurrentContext
{
get
{
HttpContext context = HttpContext.Current;
if (context == null)
{
throw new Exception("The current httpcontext is null");
}
return context;
}
}
#endregion
#region Override Methods
/// <summary>
/// Initializes container.
/// </summary>
public override void Initialize()
{
OnBeforeInitialized(new ContainerEventArgs(this, ContainerType.Unity));
if (CurrentContext.Application[Name] == null)
{
lock (_synlock)
{
if (CurrentContext.Application[Name] == null)
{
IUnityContainer currentContainer = new UnityContainer();
UnityConfigurationSection section = ConfigurationManager.GetSection("unity") as
UnityConfigurationSection;
section.Containers[Name].Configure(currentContainer);
CurrentContext.Application[Name] = currentContainer;
}
}
}
OnAfterInitialized(new ContainerEventArgs(this, ContainerType.Unity));
}
/// <summary>
/// Resolves this instance.
/// </summary>
/// <typeparam name="T">Parameter type.</typeparam>
/// <returns></returns>
public override T Resolve<T>()
{
try
{
Initialize();
IUnityContainer currentContainer = CurrentContext.Application[Name] as IUnityContainer;
return currentContainer.Resolve<T>();
}
catch(Exception ex)
{
OnResolveFailed(new ContainerFailedEventArgs(this, ContainerType.Unity, ex));
return default(T);
}
}
/// <summary>
/// Tears down.
/// </summary>
public override void TearDown()
{
OnBeforeTearDown(new ContainerEventArgs(this, ContainerType.Unity));
CurrentContext.Application[Name] = null;
OnAfterTearDown(new ContainerEventArgs(this, ContainerType.Unity));
}
#endregion
} |
在项目中通过unity来创建对象的代码是:
|
/// <summary>
/// 数据层实体的个性操作对象
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <returns></returns>
protected TEntity LoadRepositoryEntity<TEntity>()
{
IContainerContext container = ContainerManager.GetContainer("repositoryContainer");
return container.Resolve<TEntity>();
} |
这样,在BLL层调用DAL层对象时,可以通过LoadRepositoryEntity泛型方法来实现。
要研究的问题:IOC是什么?它解决了什么?我们的项目中为何要用IOC?反射对性能真的影响很大吗?不用反射,谁意见最大?
来吧,开始说问题
1,IOC是什么?不用说什么容器,框架,大白话其实IOC就是将一些接口与它的各种实现的类名配置在一个文件中,可以是XML,也可以是.config配置文件中,然后在程序“运行时”去根据配置的信息去动态得到(resolved)这个对象,然后程序中就可以使用这个对象了,所IOC就是用来建立对象的。
2,它解决了什么?一个接口,有三种实现,如sql实现,oracle实现,access实现等,那当前环境下用哪种方式来进行实现呢,这时可以由IOC来实现它。
|
// 统一规范
public interface IRepository<TEntity> where TEntity : class
{
void Insert(TEntity entity);
}
// sql 实现
public class SqlRepository<TEntity> : IRepository<TEntity>
{
public void Insert(TEntity entity)
{
...
}
}
// Access实现
public class AccessRepository<TEntity> : IRepository<TEntity>
{
public void Insert(TEntity entity)
{
...
}
} |
3,我们的项目中为何要用IOC?在项目中用IOC的目的很简单,就是让程序松耦合,说的有点抽象,看一个小代码
|
1 class UserService
2 {
3 var userRepository=new UserRepository();
4 } |
我们可以看到userRepository对象它依赖于UserRepository的实现,当我们需要改变userRepository的实现方法时,很困难,只能去代码中改变,这样导致程序的扩展性很低,这是项目中引入IOC的原因,也就是说:“当程序模块有多种实现方式时,可以引用IOC方式来创建对象的实例”。
4,反射对性能真的影响很大吗?这个问题有无数个人做了无数次实验,反射和不用反射的程序,差距不好确定,因为它与数据复杂度还有关系,一般数据量小时,可能几十倍,数据量时,可能上百倍(反射在底层实现时),我们可以从微软的原码中看到,它在一些时候使用了很多反射,是的,我本人不反对使用反射,因为我是一个面向对象的忠实信仰者。
5,不用反射,谁意见最大?这个问题很有意思,谁意见最大?我看因为是“面向对象”意见最大,因为我们无法去想像一个没有反射的程序是如何面向对象的,当一个需求被更改时,就要去修改你的程序代码,这对于程序员是一种痛苦,也违背了面向对象的原则,我想,即然C#是一个完全面向对象的语言,那我们在写程序时,是否应该面向对象更多一些,而不是那么一点点的性能,而且这种性能的浪费我认为是完成值得的
第六讲 我的系统结构~将所有可以抽象的项目进行抽象
N层架构及各层之间的通讯标准
一 总体结构图
二 各个模块的介绍
通用项目模块Project.Common:它是对所有项目都公开的项目组合,主要提供一个与领域无关的通用功能的代码库
核心项目模块Project.Core:它是针对某种构架方式(如LINQ
To SQL作为底层架构)抽象出来的项目组合,它与领域无关
领域项目模块,它是具体的项目,如XXB项目,它本身也是一个N层架构方式,一般地,它的UI层会继承我们的Product.Core下的Web.Commons项目,而对应的Entity对应Entity.Commons项目,由于BLL层是针对某种特殊业务领域的,所以在Project.Core里没有出现BLL层的抽象。
以下是一个Demo项目的结构
三 具体领域模块各层之间的通讯标准
由于 project.common和project.core是比较稳定的,而且对所有解决方案都是共用的,所以在这里它不是我们讨论的重点,我们主要说一下具体领域模块的各层间的通讯问题
领域模块总体图:
3.1 UI层与BLL层通讯
添加删除与更新操作统一使用实体类型,如果要返回信息,请使用统一的消息类
|
1 /// <summary>
2 /// 插入商品
3 /// </summary>
4 VMessage AddProduct(Product entity); |
而查询使用统一使用一个字典类型来存储条件,用一个结构体存储分页参数
|
/// <summary>
/// 根据条件得到分页结果集
/// </summary>
/// <param name="vp"></param>
/// <param name="pp"></param>
/// <returns></returns>
PagedList<Product> GetProduct(VPredication vp, PagingParam pp); |
3.2 BLL层与DAL层通讯
BLL层将数据进行组合后,调用数据层统一的方法即可,数据层只提供最基础的GURD操作,涉及到多表插入,查询的,统一在BLL层进行组合实现。
下面是业务层代码的实现:
|
/// <summary>
/// 商品模块实现
/// </summary>
public class ProductService : ServiceBase, IProductService
{
IProductRepository iProductRepository = null;
public ProductService()
{
iProductRepository = new ProductRepository();
}
#region IProductService 成¨|员?à
public Entity.VMessage AddProduct(Entity.Car_Rental.Product entity)
{
try
{
iProductRepository.Insert(entity);
VMessage.IsComplete = true;
}
catch (Exception)
{
// throw; //发布后注释它¨1
VMessage.IsComplete = false;
}
return VMessage;
}
public Entity.PagedList<Entity.Car_Rental.Product> GetProduct(Entity.VPredication vp, Entity.PagingParam pp)
{
var linq = iProductRepository.GetDetailModel();
return new Entity.PagedList<Entity.Car_Rental.Product>(linq, pp.PageIndex, pp.PageSize);
}
#endregion
} |
四 实体验证机制
应该是一种前台特殊效果验证和实体有效性验证相结合的一种验证方式,前台验证一般指JS验证,可能产生一些用户体验比较好的效果,实体验证多在实体层完成,在进行添加,修改等操作时,需要进行些验证。
4.1 实体统一基类:
在EntityBase类中已经完成了对实体非空的验证,GetRuleViolations方法将返回验证失败的迭代结果集,它是一个虚方法,子类可以根据自己的逻辑去复写它。IsValid是一个属性,它在判断实体验证时被赋值,为true表示成功,反之,验证失败。
对于复合实体,如在前台表单中可以出现多个实体组合的情况,这时,可以使用MVC的那种方法,页面元素写在一个ViewModel里,供这个页面视图使用,使用.net的attribute
很容易的可以实现属性的验证。
在controller层(可能根据业务需要,也会被抽象出来),我们在进行实体添加与修改操作时,需要判断实体的IsValid,然后再去调用BLL层的方法,一般代码是这样:
|
[HttpPost]
public ActionResult Create(FormCollection collection)
{
News entity = new News();
TryUpdateModel(entity);
entity.CreateDate = DateTime.Now;
entity.UpdateDate = DateTime.Now;
entity.Status = (int)Status.Normal;
if (entity.IsValid)
{
iNewsRepository.Insert(entity);
return AlertToUrl("Index");
}
else
{
entity.GetRuleViolations().ToList().ForEach(i =>
{
ModelState.AddModelError(i.PropertyName, i.ErrorMessage); //验证没有通过的信息
});
}
return View();
} |
五 接口的重要性
接口可能会成功我们的累赘,添加了代码量,(F12)转到定义时也会带来不方便,我们是否应该废弃接口
它的好处:
1 统一,稳定的操作规范
2 使用接口,配和IOC,实现一种操作的多种实现方式的切换
对DAL层接口与实现的抽象图示
|