在业务开发中,通常会按照业务或者逻辑将项目分成好几个工程文件以方便重用和模块化,有时候我们分开的两个项目可能存在相互引用的情况,举个例子,比如有两个系统,订单系统和产品系统,订单系统需要从产品系统中了解当前产品是否有剩余。产品系统需要从订单系统中了解产品的销售情况,这时候就存在相互引用的情况。
循环引用在Visual Studio中是编译不通过的。出现循环引用很可能是设计上抽象不够导致的,根据设计模式的依赖倒置-高层模块不应该依赖于低层模块。二者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象这一原则,可以来解决循环引用。
在一些项目中,使用一些依赖注入的框架如SPRING.net,CASTLE可以在一定程度上避免循环引用。 Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。
但有时候,项目中一些小的功能点如果使用这些框架会显得“过重”,并且解决功能点之间的循环引用也不太复杂,简言之就是抽象出接口。下面就演示一下如何解决项目间的循环引用。
为了演示,首先新建Product 和Order两个类库,Product类库中内容如下:
/// <summary> /// Product实体类 /// </summary> public class Product { public int ProductId { get; set; } public int OrderId { get; set; } public string ProductName { get; set; } public override string ToString() { return String.Format("the product of [{0}] infomations as followings: \r\n OrderId: {1} \r\n ProductName: {2}", OrderId, ProductId, ProductName); } }
public class ProductService { /// <summary> /// 根据OrderID获取Product /// </summary> /// <param name="orderId"></param> /// <returns></returns> public Product GetProductsByOrderId(int orderId) { Product product = new Product() { ProductId = 1, OrderId = orderId, ProductName = "test product" }; return product; } }
里面有一个Product实体类,然后一个ProductService类用来提供服务,其中有一个名为GetProductsByOrderId的方法可以通过OrderId查询产品信息。
Order类库类似,提供了一个Order实体类和OrderService类来提供服务,其中有一个名为GetOrdersByProductId的方法可以通过ProductId查询订单信息。
/// <summary> /// Order实体类 /// </summary> public class Order { public int OrderId { get; set; } public int ProductId { get; set; } public string OrderName { get; set; } public override string ToString() { return String.Format("the order of [{0}] information are as followings: \r\n ProductId: {1} \r\n OrderName: {2}", OrderId, ProductId, OrderName); } }
public class OrderService { public Order GetOrdersByProductId(int productId) { Order order = new Order() { OrderId = 1, ProductId = productId, OrderName = "test order" }; return order; } }
现在, 假设我们在Product类中需要调用Order类中的GetOrdersByProductId方法查询订单,那么需要引用Order工程文件,因为实体和方法都在Order类库中,这时Product对Order类库产生了依赖,假设与此同时,Order类中也需要调用Product类中的GetProductsByOrderId方法来查询产品,这样Order类库就对Product产生了依赖。就出现了循环引用的情况。
在这种情况下,我们新建一个Shared类库,将Order和Product中需要对外依赖的部分抽象成接口IOrder和IProduct放到这个第三方库中,并定义一些需要交换数据的实体类OrderModel和ProductModel。
public interface IProduct { ProductModel GetProductsByOrderId(int orderId); }
public interface IOrder { OrderModel GetOrdersByProductId(int productId); }
然后Order和Product项目引用Shared类库,并实现定义好的接口,现在Product类库中的ProductService方法实现IProduct 接口,变为了:
public class ProductService:IProduct { /// <summary> /// 接口方法,根据OrderId获取产品信息 /// </summary> /// <param name="orderId"></param> /// <returns></returns> public ProductModel GetProductsByOrderId(int orderId) { ProductModel result; Product product = GetProduct(orderId); result= new ProductModel { OrderId = product.OrderId, ProductName = product.ProductName, ProductId = product.ProductId }; return result; } /// <summary> /// 根据OrderID获取Product /// </summary> /// <param name="orderId"></param> /// <returns></returns> private Product GetProduct(int orderId) { Product product = new Product() { ProductId = 1, OrderId = orderId, ProductName = "test product" }; return product; } }
在实现的接口方法内部,我们可以调用其它的方法,只需要将返回结果Product转换为接口中定义的返回类型ProductModel即可,因为有些时候,我们可能不需要对外提供那么多的信息。只需要提供指定了的信息即可。然后在Product 类库中我们提供一个产生ProductService实体的类ProductSharedService,中间只有一个Create方法,该方法返回ProductService实体。
public static class ProductSharedService { public static ProductService Create() { return new ProductService(); } }
我们还需要在Shared类库中提供动态加载程序集,反射调用ProductSharedService的Create方法的通用方法。于是新建类SharedInterfaceProxy,通过构造函数传入程序集名称,以及创建实体类的类名称。
public class SharedInterfaceProxy { private string _assemblyName; private string _className; private const string _methodName = "Create"; private MethodInfo _proxcy; /// <summary> /// 构造函数 /// </summary> /// <param name="assemblyName">程序集名称</param> /// <param name="className">程序集中产生实体类的类名称,该类必须存在一个名为Create的方法,这里是约定</param> public SharedInterfaceProxy(string assemblyName, string className) { this._assemblyName = assemblyName; this._className = className; Init(); } private void Init() { Type type; try { type = GetAssemblyType(); if (type != null) { _proxcy = type.GetMethod(_methodName); } } catch (Exception) { throw; } } private Type GetAssemblyType() { Type result; Assembly assem; result = null; try { assem = AppDomain.CurrentDomain.Load(_assemblyName); if (assem != null) { result = assem.GetType(_className); } } catch (Exception ex) { Debug.WriteLine(ex.Message); } return result; } public object GetInstance() { object result; if (_proxcy == null) { return null; } result = null; try { result = _proxcy.Invoke(null, null); } catch (Exception ex) { Debug.WriteLine(ex.Message); } return result; } }
方法中我们约定了传进去的className这个类必须存在一个Create方法,使用反射调用该方法就可以产生创建一个服务实体。
现在,假设我们需要在Order类库中调用Product的GetProductsByOrderId 方法,现在我们可以通过 实例化SharedInterfaceProxy类,然后通过接口实现调用。
public static ProductModel GetProductByOrderId(int orderId) { ProductModel result; result = null; SharedInterfaceProxy shareProxy = new SharedInterfaceProxy("Product", "Product.ProductSharedService"); object product=shareProxy.GetInstance(); if (product != null) { IProduct pro = product as IProduct; if (pro != null) { result = pro.GetProductsByOrderId(orderId); } } return result; }
以上就是通过使用反射和接口实现了循环引用工程的解耦和,其基本原理就是设计模式中的依赖倒置原理,即高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。这里在本文之前Product依赖Order高层模块,Order也依赖Product高层模块,改造之后,两者都依赖于Shared中的接口这一抽象。
本来想画几个UML图的,这样就一目了然,可惜不太会,后面会补上。
希望本文对您了解如何简单的解决循环引用以及对设计模式中的依赖倒置有所理解。