`

解除具体依赖的技术(转)

阅读更多

作者: 张逸  来源: 博客园  发布时间: 2010-07-20 11:55 原文链接  

一个外部具体对象的引入,必然会给一个模块带来与外部模块之间的依赖。而具体对象的创建始终是我们无法规避的。即使我们可以利用设计模式的工厂方法模式或抽象工厂封装具体对象创建的逻辑,但却又再次引入了具体工厂对象的创建依赖。虽然在设计上有所改进,但没有彻底解除具体依赖,仍让我心有戚戚焉。

以一个电子商务网站的设计为例。在该项目中要求对客户的订单进行管理,例如插入订单。考虑到访问量的关系,系统为订单管理提供了同步和异步的方式。显然,在实际应用中,我们需要根据具体的应用环境,决定使用这两种方式的其中一种。由于变化非常频繁,因而我们采取了“封装变化”的设计思想。譬如,考虑应用Strategy模式,因为插入订单的行为,实则就是一种插入订单的策略。我们可以为此策略建立抽象对象,如IOrderStrategy接口。

1 public interface IOrderStrategy 2 { 3 void Insert(OrderInfo order); 4 }

然后分别定义两个类OrderSynchronousOrderAsynchronous实现IOrderStrategy接口。类结构如图1所示。

image 订单策略的设计

当领域对象Order类需要插入订单时,将根据IOrderStrategy接口的运行期类型,执行相关的订单插入策略,如下代码所示。

1 public class Order 2 { 3 private IOrderStrategy m_orderStrategy; 4 5 public Order(IOrderStrategy orderStrategy) 6 { 7 m_orderStrategy = orderStrategy; 8 } 9 10 public void Insert(OrderInfo order) 11 { 12 m_orderStrategy.Insert(order); 13 } 14 }

由于用户随时都可能会改变插入订单的策略,因此对于业务层的订单领域对象而言,绝不能与具体的订单策略对象产生耦合关系。也就是说,在领域对象Order类中,不能new一个具体的订单策略对象,如下面的代码:

IOrderStrategy orderStrategy = new OrderSynchronous();

  虽然在前面的实现中,我们通过领域对象的构造函数传递了IOrderStrategy接口对象。但这样的实现仅仅是将具体订单策略对象的创建推迟到了领域对象的调用者那里而已,调用者无法避免具体订单策略对象的创建。显然,这是一种“治标不治本”的做法。我们当然也期望能够有一种理想的状态,就是具体对象的创建永远都不要在代码中出现。事实上,模块与模块间之所以产生依赖关系,正是因为有具体对象的存在。一旦在一个模块中创建了另一个模块中的具体对象,依赖就产生了。现在,我们的目的就是要将这些依赖消除。

  1、配置文件与反射技术

使用硬编码方式创建一个对象,必然会带来对象之间的具体依赖。一种最简单的方式是将反射技术与配置文件相结合,在具体对象拥有共同抽象的前提下,通过配置文件获得具体对象的类型信息,然后利用反射创建相应的对象。例如,在领域对象Order类中,可以如此实现: 

<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --> 1 public class Order 2 { 3 private static readonly IOrderStrategy orderInsertStrategy = 4 LoadInsertStrategy(); 5 6 private static IOrderStrategy LoadInsertStrategy() 7 { 8 //通过配置文件找到具体的订单策略对象 9   string path = ConfigurationManager.AppSettings["OrderStrategyAssembly"]; 10 string className = ConfigurationManager.AppSettings["OrderStrategyClass"]; 11 //通过反射创建对象实例 12   return (IOrderStrategy)Assembly.Load(path).CreateInstance(className); 13 } 14 }

在配置文件web.config中,配置如下的节:

 

<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --><add key="OrderStrategyAssembly" value="AgileDon.BLL"/> <add key="OrderStrategyClass" value="BLL.OrderSynchronous"/>

 

 

通过引入泛型,我们可以对前面的逻辑进行有效的封装,例如 <script src="http://cms.cnblogs.com/editor/tiny_mce/themes/advanced/langs/zh.js" type="text/javascript"></script><script src="http://cms.cnblogs.com/editor/tiny_mce/plugins/insertCode/langs/zh.js" type="text/javascript"></script><script src="http://cms.cnblogs.com/editor/tiny_mce/plugins/syntaxHighlighter/langs/zh.js" type="text/javascript"></script> 定义如下的工厂辅助类。

1 public static class FactoryHelper<T> where T:class 3 { 4 private static T instance = null; 5 6 public static T Create(string typeNameKey, string nameSpace, string assemblyPath) 9 { 10 if (instance == null) 11 { 12 string typeName = ConfigurationManager.AppSettings[typeNameKey]; 13 string className = nameSpace + "." + typeName; 14 instance = (T)Assembly.Load(assemblyPath). 15 CreateInstance(className); 16 } 17 return instance; 18 } 19 }

注意, Create()辅助方法中的typeNameKey,是指向具体对象类型的键值。通常建议将其键值赋值为具体对象类型的抽象接口类型名,而对应的值则是目标创建对象的类型名。例如:

 

<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --><add key="IOrderStrategy" value="OrderSynchronous"/>

然后,我们可以为属于相同命名空间的类统一定义工厂类,并在其中调用工厂辅助类FactoryHelperCreate()辅助方法。例如,为业务逻辑层的对象定义工厂类BLLFactory

1 public static class BLLFactory<T> where T:class 3 { 4 public static T Create(string typeNameKey) 5 { 6 string nameSpace = ConfigurationManager.AppSettings["BLLAssembly"]; 7 string assemblyPath = ConfigurationManager.AppSettings["BLLPath"]; 8 return BaseFactory<T>.CreateT( 9 typeNameKey, nameSpace, assemblyPath); 10 } 11 }

针对订单策略对象,对应的配置文件为:

 

<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --><add key="BLLAssembly" value="AgileDon.BLL"/> <add key="BLLPath" value="AgileDon.BLL"/> <add key="IOrderStrategy" value="OrderSynchronous"/>

 

 

现在,我们就可以调用BLLFactory类的Create ()方法,传入类型名以获得具体的对象。例如:

IOrderStrategy orderInsertStrategy = BLLFactory<IOrderStrategy>.Create("IOrderStrategy");

  如果需要将订单插入策略从同步修改为异步方式,只需将配置文件中IOrderStrategy键对应的值修改为"OrderAsynchronous"即可。

2、表驱动法

借鉴表驱动法【注:参见Steve McConnell著作《代码大全》第18章】的思想,我们可以利用一个Dictionary集合来维护目标对象与键值之间的映射关系。当我们需要获得对象时,可以利用键值对表进行查询,这样就可以有效地消除if语句。例如,可以在Strategy模式中使用表驱动法,将其作为模式的上下文对象,而不必执行对策略对象类型的逻辑判断。利用表驱动法,我们也可以解除对象之间的具体依赖。

仍然以订单的管理为例。我为订单的管理专门定义了一个OrderManager类,它负责初始化并维持对象表。

1 public static class OrderManager 2 { 3 private static IDictionary<string,IOrderStrategy> m_strategyTable; 4 static OrderManager() 5 { 6 Init(); 7 } 8 9 private static void Init() 10 { 11 m_strategyTable = new Dictionary<string,IOrderStrategy>(); 12 m_strategyTable.Add("sync",new OrderSynchronous()); 13 m_strategyTable.Add("async",new OrderAsynchronous()); 14 } 15 16 public static IOrderStrategy GetOrderStrategy(string strategyKey) 17 { 18 IOrderStrategy strategy; 19 20 if (m_strategyTable.TryGetValue(strategyKey, out strategy)) 21 { 22 return strategy; 23 } 24 else 25 { 26 throw new Exception("无法找到正确的订单策略对象"); 27 } 28 } 29 }

  在调用OrderManagerGetOrderStrategy()方法时,为提供更好的灵活性,寻找策略对象的键值应该放在配置文件中,以避免修改源代码。

string strategyKey = ConfigurationManager.AppSettings["StrategyKey"];

IOrderStrategy strategy = OrderManager.GetOrderStrategy(strategyKey);
我们甚至可以提供一个注册方法
RegisterStrategy(),用以应对未来可能的扩展。

 

<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --> 1 public static class OrderManager 2 { 3 //其余实现略 4   public static void RegisterStrategy( 5 string strategyKey, 6 IOrderStrategy strategy) 7 { 8 if (String.IsNullOrEmpty(strategyKey)) 9 { 10 throw new ArgumentNullException(strategyKey); 11 } 12 13 if (strategy == null) 14 { 15 throw new ArgumentNullException("策略对象不能为null"); 16 } 17 18 if (m_strategyTable.ContainsKey(strategyKey)) 19 { 20 throw new ArgumentException("已经存在键值" + strategyKey); 21 } 22 23 m_strategyTable.Add(strategyKey,strategy); 24 } 25 }

3、依赖注入

 依赖注入(Dependency Injection)是一个漂亮的隐喻。依赖关系就像是被注入的液体,我们可以在任何时候将依赖关系注入到模块中,而不只限于在编译时绑定。既然这种依赖关系是通过注入的方式完成,就意味着我们可以随时更新,因为注入的液体与模块并无直接关联。实现依赖注入的前提是面向接口编程,而辅助的技术则是利用反射技术。

依赖注入是目前大多数轻量级IoC(控制反转,Inversion of Control)容器用于解除外部服务与容器服务之间依赖关系的一把利刃。首先,容器服务包含了外部服务接口的定义。然后,依赖注入通过使用一个专门的装配器对象,提供外部服务的具体实现,并其赋值给对应的容器服务对象。Martin Fowler将依赖注入的形式分为三种:构造函数注入(Constructor Injection)、设置方法注入(Setter Injection)和接口注入(Interface Injection)。其中,接口注入通过定义接口约束的方式实现依赖注入,会给容器带来设计的限制。而构造函数注入与设置方式注入则表现了产生依赖的两个连接点:构造函数与属性。如果构造函数参数或属性对象的类型为抽象的接口类型,则产生具体依赖的源头在于具体对象的创建。将创建具体对象的职责转移到IoC容器,就可以在运行时为构造函数参数或属性对象传递依赖。

目前,实现了依赖注入的轻量级容器已经应用在许多框架产品中,如Java平台下的SpringPicoContainer等。在.NET平台下,常见的依赖注入框架包括AutoFacNinjectSpring.NETStructureMapWindsor等。

Ninject框架为例,我们可以定义这样的Order类:

1 public class Order 2 { 3 private IOrderStrategy m_strategy; 4 5 public Order(IOrderStrategy strategy) 6 { 7 m_strategy = strategy; 8 } 9 10 public void Insert(OrderInfo order) 11 { 12 m_strategy.Insert(order); 13 14 } 15 }

然后,我们需要自定义一个OrderModule类,它派生自Ninject.Core.StandardModule类。这是Ninject实现依赖注入的一个特色,它抛弃了传统的xml映射文件,而是利用类型绑定的方式,并根据要创建的具体对象分组建立对应的Module类。注意,它不同于之前的解耦方法,因为它对业务逻辑代码没有造成任何侵略与污染。如上定义的Order类,保留了领域对象的本来面貌。使得领域层的开发人员可以专心致志着力于业务逻辑的实现。OrderModule类的定义如下所示:

1 using Ninject.Core; 2 3  public class OrderModule:StandardModule 4 { 5 public override void Load() 6 { 7 Bind<IOrderStrategy>().To<OrderSynchronous>(); 8 } 9 }

客户端调用的代码可以通过Ninject提供的IKernel对象获得Order对象:

1 OrderModule module = new OrderModule(); 2 IKernel kernal = new StandardKernel(module); 3 Order order = kernal.Get<Order>(); 4 order.Insert(new OrderInfo());

  4、惯例优于配置

 使用配置文件固然可以解除与具体对象之间的依赖,然而,它带来的良好可扩展性,却是以牺牲系统的可维护性乃至于可靠性为代价的。配置文件很难管理,尤其是在配置信息相对较多的情况下。不管是集中管理还是分散管理,都存在一些与生俱来的缺陷。如果采用集中管理,则配置文件过大,既影响性能,也不能很好地展现配置信息的分类与层次。在.NET中,虽然可以利用<section></section>对配置文件进行分节,但终究不够直观。采用分散管理,则不同大小的配置文件千头万绪,既会给维护者带来管理的障碍,也不利于部署与使用。使用配置文件尤其不便于调试。开发环境提供的编译期检查,对于配置文件只能是“望洋兴叹”。所谓“差之毫厘,谬以千里”,小小的一个配置项错误,可能会造成难以弥补的巨大损失。为了弥补这些缺陷,许多产品或框架都提供了专门的配置或管理工具,使用直观的UI界面对配置文件进行操作,但繁杂的配置项仍然有可能让使用者望而却步。

惯例优于配置(Convention over Configuration)来源于Ruby On Rails框架的设计理念,也被认为是Rails大获成功的关键因素之一。这里所谓的惯例,可以理解为框架对编程的一些约束,我们可以根据实现制订的默认规则,通过反射技术完成对象的创建,对象的协作,甚至是应用程序的组装。例如在Rails中对MVC模式的实现中,就事先确立了ModelViewController的目录结构与命名规范。在这种情况下,我们不需要对元数据进行任何配置。ASP.NET MVC框架同样采纳了惯例优于配置的思想。采用惯例,虽然在一定程度上损失了系统的灵活性,带来的却是良好的可维护性。同时,它仍然可以解除系统与具体对象之间的强耦合关系。

惯例优于配置的技术并不是非常适合于本文中的订单策略示例。不过,在.NET框架中,有关WebRequest对象的创建,却可以改用惯例优于配置的思想来实现。图2WebRequest对象的继承体系:

image

2 WebRequest的类结构

.NET框架中,创建一个WebRequest实例的方法是调用WebRequest的静态方法Create()

WebRequest myRequest = WebRequest.Create("http://www.agiledon.com");

  由于,传入的Uri地址其前缀为"http",因此创建的myRequest对象应该为HttpWebRequest具体对象。如果需要根据不同的Request协议,扩展不同的WebRequest对象,就需要引入一些设计技巧,来解除与具体对象创建的依赖。.NET框架的实现能够达到这样的目的,但非常复杂,这里不提。我想要介绍的是如何利用惯例优于配置来实现WebRequest对象的扩展。利用“惯例优于配置”的思想有一个前提,就是我们要对WebRequest对象的命名规范进行惯例约束。例如,我们规定所有的WebRequest子类对象均由协议名加上“WebRequest”后缀构成。通过解析传入的Uri,可以获得传输协议的名称,之后将它与“WebRequest”连接起来,获得WebRequest子类对象的类名,再利用反射技术创建该对象。在WebRequest类中定义如下的Create()静态方法:

1 public static WebRequest Create(Uri requestUri) 2 { 3 if (requestUri == null) 4 { 5 throw new ArgumentNullException("requestUri"); 6 } 7 8 string prefix = requestUri.Scheme.ToLower(); 9 10 if (prefix == null) 11 { 12 throw new ArgumentNullException("requestUri"); 13 } 14 15 if (prefix.Contains("")) 16 { 17 prefix = prefix.Replace(".",""); 18 } 19 20 StringBuilder typeName = new StringBuilder(); 21 typeName.Append("System.Net."); 22 typeName.Append(prefix.Substring(0,1).ToUpper()); 23 typeName.Append(prefix.ToLower().Substring(1,prefix.Length - 1)); 24 typeName.Append("WebRequest"); 25 26 return (WebRequest)Activitor.CreateInstance( 27 System.Type.GetType(typeName)); 28 }

只要WebRequest的子类对象能够遵循我们的惯例,即该类的类型名符合事先制订的规范,改进后的Create()方法就能够运行良好。以新增Tcp协议的WebRequest对象为例。该协议的Schema为“net.tcp”,因此其类名必须为“NettcpWebRequest”,并放在“System.Net”命名空间下。如果客户端调用WebRequest.Create()方法,并传入“net.tcp://www.agiledon.com”值,则Create()方法就会对该Uri地址进行解析,获得完整的类型名为“System.Net.NettcpWebRequest”,然后,利用反射技术创建该对象。采用“惯例优于配置”的方式,可以极大地简化工厂方法的实现代码,抛弃了繁琐的设计理念,具有非常灵活的扩展性以及良好的代码可读性。或许,唯一的遗憾是由于反射技术带来的性能损耗。

利用抽象的方式封装变化,固然是应对需求变化的王道,但它也仅仅能解除调用者与被调用者之间的耦合关系。只要还涉及具体对象的创建,即使引入了创建型模式,例如Factory Method模式,具体工厂对象的创建依然是必不可少的。不要小看这一点点麻烦,需知“千里之堤,溃于蚁穴”,牵一发而动全身,小麻烦可能会酿成大灾难。对于那些业已被封装变化的对象,我们还应该学会利用诸如“依赖注入”、“表驱动法”等技术,彻底解除两者之间的耦合;至于选择何种技术,则需要根据具体的应用场景做出判断。当然,模块或对象解耦的重要前提,则源于封装变化,要求我们针对接口编程,而不是实现。这也是GOF提出的面向对象设计原则。

分享到:
评论

相关推荐

    证件挂靠解除协议书.pdf

    IT行业是一个日新月异、充满挑战的行业,它不仅仅是技术和创新的竞技场,也是一个高度依赖职业资格和个人信用的领域。证件挂靠的行为在一定程度上与行业诚信挂钩,因此,了解相关的法律法规,遵守行业规范,是每个IT...

    锐捷交换机去堆叠技术详解

    去堆叠技术是指在不牺牲高可用性和高性能的前提下,解除原本堆叠在一起的多台交换机之间的堆叠关系,使其成为独立运行的设备,同时保持原有的网络连接和服务质量。通过采用先进的路由、负载均衡等技术手段,确保在...

    技术秘密转让合同格式

    1. **非书面形式**:技术秘密不同于专利,它不依赖于公开,而是依赖于保密性来维持其价值,因此合同中的保密条款尤为重要。 2. **员工培训**:企业应确保员工了解并遵守保密协议,防止内部泄密。 3. **监控和更新*...

    ncm 格式转 mp3 工具

    关于 ncm 转 mp3 的具体过程,可能涉及以下知识点: 1. **音频编码与解码**:ncm 文件需要通过解码器解码成原始音频数据,然后使用 mp3 编码器重新编码成 mp3 格式。这个过程中可能需要逆向工程网易云音乐的 DRM ...

    解除SQL挂起

    2. **错误排查**:检查并修复安装日志中的错误信息,识别出导致挂起的具体原因,如文件缺失、依赖项未满足等,并提供解决方案。 3. **进程管理**:结束与SQL Server安装冲突的进程,避免安装过程中出现的阻塞情况。...

    基于PLC的轮毂检测翻转装置设计.pdf

    当前大多数轮毂的翻转操作依赖于人工进行,这种方式不仅耗时、费力,而且效率低下,难以实现劳动的解放。因此,需要设计一种自动化的轮毂翻转装置。 2. 自动化轮毂翻转装置设计: 设计的轮毂翻转装置主要由三部分...

    直接将PDF在线转换成Excel.pdf

    描述部分未提供具体内容,但我们可以推测其可能涉及如何简便快捷地进行这种转换,避免传统方法可能导致的排版问题。 标签“cs”可能是指计算机科学或相关技术领域,因为PDF到Excel的转换涉及到计算机软件和在线服务...

    技术入股合作协议书范本【推荐】.docx

    这种出资方式常见于高科技企业或创新型企业,特别是那些依赖核心技术发展的公司。 2. **无形资产估值**:协议书中提到的技术、专利、商标等属于无形资产,需要经过协商作价来确定其价值。这通常需要评估技术的先进...

    张燕飞:基于ACPI的内存热插拔技术

    在内存热插拔技术中,内存的动态映射和解除映射是核心任务之一。直接内存映射处理物理内存到内存管理单元(MMU)的直接映射关系,而虚拟内存映射则涉及到虚拟内存地址到物理内存地址的映射。页面在线和离线操作涉及...

    技术专家聘用合同范本4篇.docx

    在当今快速发展的经济背景下,企业对技术专家的依赖日益加重,为了在法律层面确保双方权益和规范雇佣关系,技术专家聘用合同应运而生。本文将详细解读技术专家聘用合同范本的主要内容,旨在为企业和求职者提供明确的...

    【合同协议范本】技术进口合同模板范本.pdf

    这种设定意味着合同的有效执行并不依赖于单一的沟通渠道,而是由一个团队共同负责。此外,卖方成员需承担连带责任,这确保了在任何一个成员未能履行合同时,其他成员亦需共同承担违约责任。这样的安排既体现了团队...

    行业分类-设备装置-串行非易失性存储器及解除存储器写保护的方法.zip

    4. **应用实例**:通过具体的设备或系统案例,解释如何在实际操作中使用和管理SNVM的写保护功能,例如在微控制器、物联网设备或嵌入式系统的固件升级过程中。 5. **错误恢复策略**:如果在解除写保护或写入过程中...

    语音识别技术在智能家居中的应用现状浅析.pdf

    本文将探讨智能家居的兴起背景,语音识别技术的发展历程及其在智能家居领域中的具体应用。 智能家居最早源于美国,在1984年美国便建成了第一座智能家居建筑,标志着智能家居时代的来临。这一概念的初衷是通过网络将...

    技术咨询合同(二).doc

    【技术咨询合同详解】 技术咨询合同是企业在进行科技创新、技术研发、技术改造等活动时,与专业顾问机构...同时,合同的签订也反映了企业对专业知识和外部智力资源的尊重和依赖,是推动科技进步和企业发展的关键步骤。

    05克莱门特中央空调技术资料.zip

    3. **风冷式冷却器**:这种设备利用空气作为冷却介质,无需依赖水源,适合于水资源匮乏或限制用水的地区。技术资料可能涉及风冷式冷却器的散热效率、运行条件和适应环境的讨论。 4. **系统设计**:资料中可能包含...

    参考资料-技术服务合同范本 (1).zip

    2. **服务内容**:详细描述乙方需提供的具体技术服务,如系统集成、软件开发、技术支持等,确保双方对服务范围有清晰认识。 3. **服务质量与标准**:定义服务应达到的质量标准,可能包括响应时间、故障解决率、系统...

    煤矿机电控制中PLC技术的有效应用.pdf

    针对煤矿生产中可能遇到的具体问题,例如文中提到的剪叉式升降平台在制动解除后出现的问题,通过在PLC系统中增加逻辑阀和节流阀等控制元件,可以有效解决因惯性和无背压导致的平台倾斜和马达异响问题。这些措施通过...

    基于物联网技术的某悬索桥吊杆索力振动法测试研究.pdf

    首先,索力的振动法检测依赖于理想状态下索力与索的振动频率间的严格数学关系。通过检测吊杆的自振频率,可以方便快捷地进行索力检测。在具体实施时,需确保测量的采样频率和采样时间设置能够满足测试结果频率分辨率...

    计算机系统技术服务外包协议---1.docx

    - **背景**:随着信息技术的发展,企业对计算机系统的依赖日益增强。为了更好地支持业务发展,许多企业选择将部分信息技术服务外包给专业的服务商。 - **目的**:通过签订此协议,明确双方的权利与义务,确保服务的...

    基于网络指纹识别的电力系统应急调度动态控制技术.pdf

    【摘要】中提到的技术主要关注的是电力系统的应急调度动态控制,传统的应急调度方法依赖于用户密码识别,这种方法存在时间消耗长、成本高以及效果不佳的问题。为了改进这一情况,研究者引入了网络指纹识别技术来开发...

Global site tag (gtag.js) - Google Analytics