解除具体依赖的技术
发布时间:2020-05-23 04:53:32 所属栏目:程序设计 来源:互联网
导读:一个外部具体对象的引入,必然会给一个模块带来与外部模块之间的依赖。而具体对象的创建始终是我们无法规避的。即使我们可以利用设计模式的工厂方法模式或抽象工厂封装具体对象创建的逻辑,但却又再次引入了具体工厂对象的创建依赖。虽然在设计上有所改进,但
|
图1
订单策略的设计
当领域对象Order
类需要插入订单时,将根据IOrderStrategy
接口的运行期类型,执行相关的订单插入策略,如下代码所示。
public
class
Order
{
private
IOrderStrategy m_orderStrategy;
public Order(
IOrderStrategy orderStrategy)
{
m_orderStrategy = orderStrategy;
}
public
void Insert(OrderInfo order)
{
m_orderStrategy.Insert(order);
}
}
由于用户随时都可能会改变插入订单的策略,因此对于业务层的订单领域对象而言,绝不能与具体的订单策略对象产生耦合关系。也就是说,在领域对象Order
类中,不能new
一个具体的订单策略对象,如下面的代码:
IOrderStrategy orderStrategy =
new OrderSynchronous();
虽然在前面的实现中,我们通过领域对象的构造函数传递了IOrderStrategy
接口对象。但这样的实现仅仅是将具体订单策略对象的创建推迟到了领域对象的调用者那里而已,调用者无法避免具体订单策略对象的创建。显然,这是一种“治标不治本”的做法。我们当然也期望能够有一种理想的状态,就是具体对象的创建永远都不要在代码中出现。事实上,模块与模块间之所以产生依赖关系,正是因为有具体对象的存在。一旦在一个模块中创建了另一个模块中的具体对象,依赖就产生了。现在,我们的目的就是要将这些依赖消除。
1
、配置文件与反射技术
使用硬编码方式创建一个对象,必然会带来对象之间的具体依赖。一种最简单的方式是将反射技术与配置文件相结合,在具体对象拥有共同抽象的前提下,通过配置文件获得具体对象的类型信息,然后利用反射创建相应的对象。例如,在领域对象Order
类中,可以如此实现:
public
class
Order
{
private
static
readonly
IOrderStrategy orderInsertStrategy =
LoadInsertStrategy(); private static IOrderStrategy LoadInsertStrategy() { //通过配置文件找到具体的订单策略对象 string path = ConfigurationManager. AppSettings[ "OrderStrategyAssembly"]; string className = ConfigurationManager. AppSettings[ "OrderStrategyClass"]; //通过反射创建对象实例 return ( IOrderStrategy)Assembly.Load(path). CreateInstance(className); } } 在配置文件web.config 中,配置如下的节: <add key="OrderStrategyAssembly" value="AgileDon.BLL"/> <add key="OrderStrategyClass" value="BLL.OrderSynchronous"/> 通过引入泛型,我们可以对前面的逻辑进行有效的封装,例如定义如下的工厂辅助类。 public static class FactoryHelper<T> where T : class { private static T instance = null; public static T Create( string typeNameKey, string nameSpace, string assemblyPath) { if (instance == null) { string typeName = ConfigurationManager.AppSettings[typeNameKey]; string className = nameSpace + "." + typeName; instance = (T) Assembly.Load(assemblyPath). CreateInstance(className); } return instance; } } 注意, Create() 辅助方法中的typeNameKey ,是指向具体对象类型的键值。通常建议将其键值赋值为具体对象类型的抽象接口类型名,而对应的值则是目标创建对象的类型名。例如: <add key=" IOrderStrategy" value="OrderSynchronous"/> 然后,我们可以为属于相同命名空间的类统一定义工厂类,并在其中调用工厂辅助类FactoryHelper 的Create() 辅助方法。例如,为业务逻辑层的对象定义工厂类BLLFactory 。 public static class BLLFactory<T> where T : class { public static T Create( string typeNameKey) { string nameSpace = ConfigurationManager.AppSettings[ "BLLAssembly"]; string assemblyPath = ConfigurationManager.AppSettings[ "BLLPath"]; return BaseFactory<T>.CreateT( typeNameKey,nameSpace,assemblyPath); } } 针对订单策略对象,对应的配置文件为: <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 类,它负责初始化并维持对象表。 public static class OrderManager { private static IDictionary< string,IOrderStrategy> m_strategyTable; static OrderManager() { Init(); } private static void Init() { m_strategyTable = new Dictionary< string,175)">IOrderStrategy>(); m_strategyTable.Add( "sync",new OrderSynchronous()); m_strategyTable.Add( "async",new OrderAsynchronous()); } public static IOrderStrategy GetOrderStrategy( string strategyKey) { IOrderStrategy strategy; if (m_strategyTable.TryGetValue(strategyKey,out strategy)) { return strategy; } else { throw new Exception( "无法找到正确的订单策略对象"); } } } 在调用OrderManager 的GetOrderStrategy() 方法时,为提供更好的灵活性,寻找策略对象的键值应该放在配置文件中,以避免修改源代码。 string strategyKey = ConfigurationManager.AppSettings[ "StrategyKey"]; IOrderStrategy strategy = OrderManager.GetOrderStrategy(strategyKey); 我们甚至可以提供一个注册方法RegisterStrategy() ,用以应对未来可能的扩展。 public static class OrderManager { //其余实现略 public static void RegisterStrategy( string strategyKey, IOrderStrategy strategy) { if ( String.IsNullOrEmpty(strategyKey)) { throw new ArgumentNullException(strategyKey); } if (strategy == null) { throw new ArgumentNullException( "策略对象不能为null"); } if (m_strategyTable.ContainsKey(strategyKey)) { throw new ArgumentException( "已经存在键值" + strategyKey); } m_strategyTable.Add(strategyKey,strategy); } } 3 、依赖注入 依赖注入(Dependency Injection )是一个漂亮的隐喻。依赖关系就像是被注入的液体,我们可以在任何时候将依赖关系注入到模块中,而不只限于在编译时绑定。既然这种依赖关系是通过注入的方式完成,就意味着我们可以随时更新,因为注入的液体与模块并无直接关联。实现依赖注入的前提是面向接口编程,而辅助的技术则是利用反射技术。 依赖注入是目前大多数轻量级IoC (控制反转,Inversion of Control )容器用于解除外部服务与容器服务之间依赖关系的一把利刃。首先,容器服务包含了外部服务接口的定义。然后,依赖注入通过使用一个专门的装配器对象,提供外部服务的具体实现,并其赋值给对应的容器服务对象。Martin Fowler 将依赖注入的形式分为三种:构造函数注入(Constructor Injection )、设置方法注入(Setter Injection )和接口注入(Interface Injection )。其中,接口注入通过定义接口约束的方式实现依赖注入,会给容器带来设计的限制。而构造函数注入与设置方式注入则表现了产生依赖的两个连接点:构造函数与属性。如果构造函数参数或属性对象的类型为抽象的接口类型,则产生具体依赖的源头在于具体对象的创建。将创建具体对象的职责转移到IoC 容器,就可以在运行时为构造函数参数或属性对象传递依赖。 目前,实现了依赖注入的轻量级容器已经应用在许多框架产品中,如Java 平台下的Spring 、PicoContainer 等。在.NET 平台下,常见的依赖注入框架包括AutoFac ,Ninject ,Spring.NET ,StructureMap 和Windsor 等。 以Ninject 框架为例,我们可以定义这样的Order 类: public class Order { private IOrderStrategy m_orderStrategy; public Order( IOrderStrategy orderStrategy) { m_orderStrategy = orderStrategy; } public void Insert(OrderInfo order) { m_orderStrategy.Insert(order); } } 然后,我们需要自定义一个OrderModule 类,它派生自Ninject.Core.StandardModule 类。这是Ninject 实现依赖注入的一个特色,它抛弃了传统的xml 映射文件,而是利用类型绑定的方式,并根据要创建的具体对象分组建立对应的Module 类。注意,它不同于之前的解耦方法,因为它对业务逻辑代码没有造成任何侵略与污染。如上定义的Order 类,保留了领域对象的本来面貌。使得领域层的开发人员可以专心致志着力于业务逻辑的实现。OrderModule 类的定义如下所示: using Ninject.Core; public class OrderModule:StandardModule { public override void Load() { Bind<IOrderStrategy>().To<OrderSynchronous>(); } } 客户端调用的代码可以通过Ninject 提供的IKernel 对象获得Order 对象: OrderModule module = new OrderModule(); IKernel kernal = new StandardKernel(module); Order order = kernal.Get<Order>(); order.Insert( new OrderInfo()); 4 、惯例优于配置 使用配置文件固然可以解除与具体对象之间的依赖,然而,它带来的良好可扩展性,却是以牺牲系统的可维护性乃至于可靠性为代价的。配置文件很难管理,尤其是在配置信息相对较多的情况下。不管是集中管理还是分散管理,都存在一些与生俱来的缺陷。如果采用集中管理,则配置文件过大,既影响性能,也不能很好地展现配置信息的分类与层次。在.NET 中,虽然可以利用<section></section> 对配置文件进行分节,但终究不够直观。采用分散管理,则不同大小的配置文件千头万绪,既会给维护者带来管理的障碍,也不利于部署与使用。使用配置文件尤其不便于调试。开发环境提供的编译期检查,对于配置文件只能是“望洋兴叹”。所谓“差之毫厘,谬以千里”,小小的一个配置项错误,可能会造成难以弥补的巨大损失。为了弥补这些缺陷,许多产品或框架都提供了专门的配置或管理工具,使用直观的UI 界面对配置文件进行操作,但繁杂的配置项仍然有可能让使用者望而却步。 惯例优于配置(Convention over Configuration )来源于Ruby On Rails 框架的设计理念,也被认为是Rails 大获成功的关键因素之一。这里所谓的惯例,可以理解为框架对编程的一些约束,我们可以根据实现制订的默认规则,通过反射技术完成对象的创建,对象的协作,甚至是应用程序的组装。例如在Rails 中对MVC 模式的实现中,就事先确立了Model 、View 和Controller 的目录结构与命名规范。在这种情况下,我们不需要对元数据进行任何配置。ASP.NET MVC 框架同样采纳了惯例优于配置的思想。采用惯例,虽然在一定程度上损失了系统的灵活性,带来的却是良好的可维护性。同时,它仍然可以解除系统与具体对象之间的强耦合关系。 惯例优于配置的技术并不是非常适合于本文中的订单策略示例。不过,在.NET 框架中,有关WebRequest 对象的创建,却可以改用惯例优于配置的思想来实现。图2 是WebRequest 对象的继承体系:
(编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |


