程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 解決.NET項目作為引用添加將導致循環依賴項

解決.NET項目作為引用添加將導致循環依賴項

編輯:關於.NET
我在一個解決方案中,有兩個項目A和B
之前B已經引用A了,但是我想A再引用B。
結果它彈出“此項目作為引用添加將導致循環依賴項”


方法一:

把需要共用的那一部分獨立出去,弄個項目C



方法二:


在業務開發中,通常會按照業務或者邏輯將項目分成好幾個工程文件以方便重用和模塊化,有時候我們分開的兩個項目可能存在相互引用的情況,舉個例子,比如有兩個系統,訂單系統和產品系統,訂單系統需要從產品系統中了解當前產品是否有剩余。產品系統需要從訂單系統中了解產品的銷售情況,這時候就存在相互引用的情況。

循環引用在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圖的,這樣就一目了然,可惜不太會,後面會補上。

希望本文對您了解如何簡單的解決循環引用以及對設計模式中的依賴倒置有所理解。
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved