技术为解决问题而生。

上面这个命题并非本文重点,我将来有空再谈这个。本文也并非什么了不起的技术创新,只是分享一下我对.net模块依赖关系及程序结构方面的一些看法。先看一个最最简单的hello world网站的模块结构如何:

就一个Website,没有任何层次划分,因为简单嘛。但很快,你就发现,还是把网站和业务逻辑处理层分开比较好,于是变成:

箭头从BLL指向Website,表明Website依赖于BLL。随着BLL的内容的不断增多,你发现需要再细分一下,于是把BLL划分为处理人事业务的HR和办公自动化的Office(假如你在做一个企业应用):

随着HR和Office的内容的增多,你发现HR和Office中有些公共的东西可以提取出来,比如部门信息及客户信息等,于是你创建了一个公共资料模块PubMaterial,HR及Office都会用到它:

接着你很快又发现还是有些公共代码得提取出来,否则会导致大量重复代码,比如统一的日志记录、异常类型、数据库连接、加解密……等,于是你又弄了一个Common模块,其它模块都要用到它:

你很满意,是的,到目前为止,都没有碰到太大的问题,但接下来,问题来了,Office要新加一个功能,这个功能需要获取员工的具体信息,而员工具体信息HR中有实现,让Office直接调用HR模块吗?这样的话本来HR和Office之间的并列关系就被破坏了,但程序还是可以运行,技术上来说还是没问题的:

上面只是破坏了原本企图保持的业务逻辑模块之间的平行关系,技术上问题不大,但接下来真的是问题了,有一天老板需要在HR中新增一个功能,需要能直接看到员工的工作简报,工作简报本来应该是Office的功能,现在需要在HR中进行一些额外的处理,这样HR需要依赖于Office,很明显,这是一个相互依赖,这是不可行的:

解决方法大致有以下三种:

  1. 将Office的一些“公共功能”抽出成为新的模块,供HR使用,且抽出的新模块不依赖于HR
  2. 直接在HR中实现一部分Office的功能,使之不依赖Office
  3. 对各个业务逻辑模块进行“接口抽象”,详情后面会说

很明显,方案2会导致重复代码,肯定不可取;而方案1也存在很大的问题,因为接下来老板要求新增计算和查看统计数据的Report模块和处理公司审批流程的Flow模块,它们与HR存在相互依赖的关系,这次看看你如何招架:

然后,再要加呢?“Oh my god!”你喊道:“快给我讲方案3吧。”OK,现在我来摆出方案3,这不一定是最好的办法,但却是我所要使用的办法:

图看起来有些眼花缭乱,但再仔细一看便发觉其实只是给原先的每个模块增加了一个“接口层”而已,比如Office模块,增加了IOffice,现在就变成Office是对IOffice的实现,而原先需要依赖于Office的模块,现在转去依赖IOffice,而“接口层”仅仅变成了一个描述,它只包括数据及方法的声明,不包括任何实现,所以它可以不依赖于任何其它模块,它只被别的模块所依赖,包括实现它的模块。相信到此大家也都理解了。那么上图那个虚框和“Config”模块是什么意思呢?

试着这样想:假如现在Website要调用Office模块,通过IOffice,但是实现IOffice的却是Office模块,那是不是得先实例化一个Office?谁来负责实例化?如果是Website负责这个实例化工作的话,那势必Website会直接依赖与Office模块,这样就是走老路了,会出问题,而且Office本身又依赖于IHR、IPubMaterial和ICommon,那对应的HR、PubMaterial和Common又谁来实例化?看来如果照着老思路下去的话,还是会掉进“依赖关系地狱”中去的。不行,我们得借助一些工具,当然,重复造轮子也是不可取的,所以我们这次要使用一个叫StructureMap的工具。

使用过Microsoft Enterprise Library的朋友也许都不会对Unity陌生,这是一个IoC(Inversion of Control)工具,后来IoC又有了一个更贴切的名称,叫DI(Dependency Injection),IoC和DI其实都是为了解决程序模块之间的依赖关系而提出来的设计模式,它们本身并不是一种具体的技术,只是一些设计套路,旨在给程序模块松耦(Loose Coupling)。StructureMap也是一个和Unity类似的DI工具,个人感觉很不错。

我目前从NuGet上获取到的StructureMap的版本是2.6.4.1,需要说明的是StructureMap官方网站上提供的文档有些落后,跟不上形势了。所幸的是我们并不需要用它的所有功能,大多数软件不都这样么?80%的功能是留给20%的人用的。

有了这个StructureMap,我们要使用ICommon,直接告诉StructureMap就是,具体用什么去实例化,怎么实例化,不用我们操心,StructureMap自动帮我们做,这就是解决的思路。现在剩下的问题就只是告诉StrutureMap “ICommon”和“Common”的关系即可,可以直接用代码来指明关系,也可以用配置文件,配置文件看起来更具“扩展性”,但这样也就没有了编译期的出错检查,反正都是得写,我这次就用代码直接指明它们之间的对应关系吧。上图中的Config,就是这么一个用来指明接口与实现之间的对应关系的配置。(除了硬编码和写配置文件之外,StructureMap还可以使用反射来自动建立接口和实现体之间的对应关系,具体可以参考它的文档,这里略过不表)

在开始一个真正的例子之前,我还是得说明一些东西,那就是前面所提到的“模块”其实在不同的场合就有着不同的意义,这是一个抽象的概念,有时候是一个代码文件,有时候是一个类,有时候后是一个类库,有时候是一个程序集,大家不要纠结于它究竟是什么,而是要重点看看在自己的使用当中如何给它们松耦。

OK,都了解完之后,我们就开始一个小小的例子。简单起见,我删掉了许多模块,程序结构图变成了:

IHR的内容有:

    public class Employee
{
public string EmpNo { get; set; }
public string ChineseName { get; set; }
} public class EmployeeDetail:Employee
{
public string Descriptions { get; set; }
public int TaskAmount { get; set; }
} public interface IHr
{
IEnumerable<Employee> GetAllEmployees();
EmployeeDetail GetEmployeeByNo(string strEmpNo);
int GetEmployeeAmount();
void AddEmployee(Employee emp);
string GetEmpNameByNo(string strEmpNo);
}

IOffice的功能有:

    public class Task
{
public int Id { get; set; }
public string EmpNo { get; set; }
public string Descriptions { get; set; }
} public class TaskDetail : Task
{
public string EmpName { get; set; }
} public interface IOffice
{
IEnumerable<Task> GetAllTasks();
TaskDetail GetTask(int id);
void AddTask(string strEmpNo, string strDesc);
void DeleteTask(int id);
int GetTaskAmountOfEmployee(string strEmpNo);
}

ICommon的功能有:

    public enum LogType
{
Information,
Warning,
Error
} public interface ICommon
{
void Log(LogType logType, string moduleName, string content, params object[] values);
}

很明显,这些都是“接口”,不包含任何的实现,它们只会被别的模块依赖,而不会依赖别的模块。

功能相信一看名字就知道怎么回事,不需要太多解释。值得说一下的是HR模块的“GetEmployeeByNo”方法返回的“EmployeeDetail”中包括了一个“TaskAmount”,也就是这个员工的“任务数”,这是Office模块的功能,这意味着HR模块需要调用Office模块的方法;接着看Office模块中的“GetTask”方法,会返回一个“TaskDetail”,“TaskDetail”中含有“EmpName”,即员工姓名,这个需要从HR模块中获取。这是一个“相互依赖”!但这次我向你保证没有问题,因为我们采用了新的设计模式工具——StructureMap,现在,我们来告诉StructureMap如何来帮助我们生成这些接口的实例,看代码:

    public static class ContainerBootstrapper
{
public static void BootstrapStructureMap()
{
ObjectFactory.Initialize(x =>
{
x.For<IHr>().Singleton().Use<HrManager>();
x.For<IOffice>().Singleton().Use<OfficeManager>();
x.For<ICommon>().Singleton().Use<CommonManager>().Ctor<string>("logPath").Is(AppDomain.CurrentDomain.BaseDirectory+"log");
});
}
}

一个静态配置类,一个静态方法,只需要在程序入口函数处调用一下即可。上面的“Singleton”方法是告诉StructureMap我们的HR、Office和Common模块都是“单实例”的,自始自终只有一个实例,如果把“.Singleton()”去掉,那么每次请求实例的时候都会创建一个新的实例出来,另外还有“HttpContextScoped”和“HybridHttpOrThreadLocalScoped”模式,前者表示每个HttpContext使用一个实例,这得在Web程序中才有效,否则按默认处理,而后者则会判断当前程序类型,如果是Web程序,那么和前者一样,如果不是Web程序,那么就每个线程使用一个实例, 究竟怎么选择,这个要看你自身的需要,在我的这个小小的demo中,Singleton即可。另外,对ICommon的实现的CommonManager的构造函数是带参数的,需要指定,否则就会出现运行时错误,上面的代码的意思是logPath这个参数是个string,把“AppDomain.CurrentDomain.BaseDirectory+"log"”作为它的值初始化。

接下来看看Main函数代码片段,简单起见,我写了个控制台程序来“冒充”Website:

        static void Main(string[] args)
{
//初始化StructureMap
ContainerBootstrapper.BootstrapStructureMap(); //获取HR实例
IHr hr = ObjectFactory.GetInstance<IHr>(); //用之
//... //获取Office实例
IOffice office = ObjectFactory.GetInstance<IOffice>(); //用之
//...
}

非常好!这样一来,模块项目依赖的关系就解决了。大家还可以看看“log”目录,你会发现一次运行中,尽管多次请求获取HR和Office的实例(HR中会请求Office,而Office也会请求HR),但它们都只会被初始化了一次,这是由于配置中指明了它们是Singleton。现在,我们来看看这个Solution的结构:

在这个Solution中,我是把IHr、IOffice和ICommon合并到了Interface这个Project中去,这样的话项目引用会少一点,但如果你要把它们分开的话也是完全没有问题的。

花了这么大的力气来处理模块相互依赖的问题其实还有一个目的,那就是可以做单元测试,如果时间允许,我将会另写一篇文章来讲述如何测试。

At Last,当然少不了完整代码(Visual Studio 2010):structuremap_demo.7z

谈谈.net模块依赖关系及程序结构的更多相关文章

  1. 【spring】jar包详解与模块依赖关系

    以spring3.X为例 jar包详解 1. spring-core.jar:包含Spring框架基本的核心工具类,Spring其它组件要都要使用到这个包里的类,是其它组件的基本核心: 2. spri ...

  2. seajs第二节,seajs各模块依赖关系

    index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> &l ...

  3. Java 9 揭秘(4. 模块依赖)

    文 by / 林本托 Tips 做一个终身学习的人. 在此章节中,主要学习以下内容: 如何声明模块依赖 模块的隐式可读性意味着什么以及如何声明它 限定导出(exports)与非限定导出之间的差异 声明 ...

  4. C/C++源代码的Include依赖关系图

    前一篇博文中我曾仔细介绍过如何查看C/C++代码的依赖项关系图,在这篇文章中我将会介绍如何使用Visualization and Modeling Feature Pack 工具包,查看C/C++源代 ...

  5. spring framework体系结构及内部各模块jar之间的maven依赖关系

    很多人都在用spring开发java项目,但是配置maven依赖的时候并不能明确要配置哪些spring的jar,经常是胡乱添加一堆,编译或运行报错就继续配置jar依赖,导致spring依赖混乱,甚至下 ...

  6. [转] spring framework体系结构及内部各模块jar之间的maven依赖关系

    很多人都在用spring开发java项目,但是配置maven依赖的时候并不能明确要配置哪些spring的jar,经常是胡乱添加一堆,编译或运行报错就继续配置jar依赖,导致spring依赖混乱,甚至下 ...

  7. spring framework体系结构及模块jar依赖关系

    本文对于Spring的JAR包使用和配置,结合网友材料以spring 4.3.6.RELEASE版本为例,介绍spring框架结构和各模块对应JAR包以及模块间JAR依赖关系. 注:不同版本JAR包依 ...

  8. 【转】spring framework 5以前体系结构及内部各模块jar之间的maven依赖关系

    作者:凌承一  出处:http://www.cnblogs.com/ywlaker/  很多人都在用spring开发java项目,但是配置maven依赖的时候并不能明确要配置哪些spring的jar, ...

  9. 玩转IDEA项目结构Project Structure,打Jar包、模块/依赖管理全搞定

    前言 你好,我是A哥(YourBatman). 如何给Module模块单独增加依赖? 如何知道哪些Module模块用了Spring框架,哪些是web工程? IDEA如何打Jar包?打War包? 熟练的 ...

随机推荐

  1. Daily Scrum 12.17

    今日完成任务: 解决匿名回答时候字数限制问题:完善了用户界面的UI设计: 明日任务: 孙思权 接口设计 晏旭瑞 资源索引问题 冯飘飘 解决上传资源时候不勾选或不添加选项内容可以上传的问题 黎柱金 进行 ...

  2. Linux_arm驱动之按键模拟脉冲实现定时器的精确计时

    /***************************************************************** 内核驱动部分button_ker.c ************** ...

  3. 项目jar包管理,使用 .userlibraries 文件增加jar包的可移植性,明确jar包依赖,多项目共用jar包里

    当一个普通的项目,在不适用maven 等jar包管理工具的时候,通常我都会直接把jar 包复制lib下,并且在build path 中直接添加额外jar包,或者使用user_libraries包所用的 ...

  4. CS0234: 命名空间“System.Web.Mvc”中不存在类型或命名空间名称“Html、Ajax”(是否缺少程序集引用?)

    从SVN上down下来的程序,编译报了一大堆的错,发现是缺少引用,但是明明引用了,后来打开引用,发现system.web.mvc这个引用打着叹号,如图: 后来重新引用了本机的system.web.mv ...

  5. UITextField使用详解

    转iOS中UITextField使用详解 (1) //初始化textfield并设置位置及大小   UITextField *text = [[UITextField alloc]initWithFr ...

  6. 日志——JSON的相关方法

    http://www.cnblogs.com/henryxu/archive/2013/03/10/2952738.html JSON  jar包: commons-lang.jar commons- ...

  7. n+1 < n , are you sure?

    密码终于找回了,原来是我邮箱把改密链接的邮件当垃圾邮件了-- 回到正题,这是道面试题,原话大致是这样的: n+1<n成立吗?请说明. 当时我听到这个题后直觉是成立的,但是想不到怎么回事,后来别人 ...

  8. WINCE 获取智能设备唯一编号

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  9. ANSI C 所有的转义字符

    \a 响铃符 \b 回退符 \f 换页符 \n 换行符 \r 回车符 \t 横向制表符 \v 纵向制表符 \\ 反斜杠 \? 问号 \' 单引号 \" 双引号 \000 八进制数 \xhh ...

  10. 二叉搜索树、B树

    二叉搜索树又叫二叉排序树. B树又可写为B-树,“B-树”种的“-”无区分意义. 此外,还有B+树,B*树.