Architecture 当您需要进行延迟加载(并使用IOC容器)时,如何避免循环数据访问对象依赖关系?

Architecture 当您需要进行延迟加载(并使用IOC容器)时,如何避免循环数据访问对象依赖关系?,architecture,dependency-injection,Architecture,Dependency Injection,注意:下面的例子是C#,但这个问题不应该特别针对任何语言 因此,我正在使用的变体构建一个对象域。对于那些不熟悉它的人,为了节省一些阅读时间,我们的想法只是为每个域对象提供一个数据访问对象接口,负责向持久层加载数据或从持久层加载数据。所有可能需要加载/保存给定对象的东西都会接受该对象的数据访问接口作为依赖项。因此,例如,我们可以在以下情况下,产品将根据需要延迟加载购买它的客户: public class Product { private ICustomerDao _customerDao;

注意:下面的例子是C#,但这个问题不应该特别针对任何语言

因此,我正在使用的变体构建一个对象域。对于那些不熟悉它的人,为了节省一些阅读时间,我们的想法只是为每个域对象提供一个数据访问对象接口,负责向持久层加载数据或从持久层加载数据。所有可能需要加载/保存给定对象的东西都会接受该对象的数据访问接口作为依赖项。因此,例如,我们可以在以下情况下,产品将根据需要延迟加载购买它的客户:

public class Product {
  private ICustomerDao _customerDao;
  private Customer _customer;
  public Product(ICustomerDao customerDao) {_customerDao = customerDao;}
  public int ProductId {get; set;}
  public int CustomerId {get; set;}
  public Customer Customer {
        get{
            if(_customer == null) _customer = _customerDao.GetById(CustomerId);
            return _customer;
        }
}
public interface ICustomerDao {
   public Customer GetById(int id);
}
在两个对象需要能够彼此加载之前,这一切都很好。例如,多对一关系,如上所述,产品需要能够延迟加载其客户,但客户也需要能够获得其产品的列表

public class Customer {
  private IProductDao _productDao;
  private Product[] _products;
  public Customer(IProductDao  productDao) {_productDao = productDao;}
  public int CustomerId {get; set;}
  public Product[] Products {
        get{
            if(_products == null) _products = _productDao. GetAllForCustomer(this);
            return _products;
        }
}


public interface IProductDao {
   public Product[] GetAllForCustomer(Customer customer);
}
我知道这是一个非常普遍的情况,但我在这方面相对较新。我的绊脚石是在实现数据访问对象时要做什么。因为客户依赖于IPProductDAO,所以CustomerDao实现也必须依赖于IPProductDAO,反之亦然,ProductDao必须依赖于ICCustomerDAO

public class CustomerDao : ICustomerDao {
      private IProductDao _productDao;
      public CustomerDao(IProductDao  productDao) {_productDao = productDao;}
      public Customer GetById(int id) {
          Customer c = new Customer(_customerDao);
          // Query the database and fill out CustomerId
          return c;
      }
 }
public class ProductDao : IProductDao {
      private ICustomerDao _customerDao;
      public ProductDao (ICustomerDao customerDao) {_customerDao = customerDao;}
      public Product[] GetAllForCustomer(Customer customer) {
          // you get the idea
      }
 }
这就是问题所在。如果没有IPProductDAO,则不能实例化CustomerDao,反之亦然。我的控制反转容器(Castle Windsor)碰到循环依赖项并阻塞

我已经提出了一个暂时的解决方案,它涉及到延迟加载DAO对象本身(我将把它作为一个答案发布),但我不喜欢它。这个问题经过时间考验的解决方案是什么


编辑:以上是我实际使用的架构的简化,我不建议有人实际将DAO传递给对象。更接近我实际操作的更好的实现类似于NHibernate的工作方式,其中实际对象非常简单,上面的实际上是代理对象,它们继承并覆盖适当的字段。

延迟加载应该由持久层管理,而不是由存储库管理


此外,您的客户和产品对象不应该有任何对存储库的引用,这似乎。。。错。

您不应该有这样的循环依赖项。这是错误的

如果您只需要在一段时间内出错就可以克服困难,那么请尝试使用服务定位器:一个了解容器并在使用依赖项时解析依赖项的类,而不是在实例化DAO类时解析依赖项的类


您可能想查看Oren Eini在Rhino.Tools中的服务定位器实现(它是一个名为IoC的静态类)

我的解决方案,并且让我再次声明,我不太喜欢让每个数据访问对象直接从容器中延迟加载它所依赖的DAO。为了简单起见,我将CustomerDao和ProductDao都从BaseDao对象继承,然后按如下方式实现它:

public abstract class BaseDao() {
  private ICustomerDao _customerDao;
  protected ICustomerDao _CustomerDao {
    get {
      if(_customerDao == null) _customerDao = IoC.Container.Resolve<ICustomerDao>();
      return _customerDao;
    }
  private IProductDao _productDao;
  protected IProductDao _ProductDao {
    get {
      if(_productDao == null) _productDao = IoC.Container.Resolve< IProductDao  >();
      return _productDao;
    }
我不喜欢这个,因为

  • a) 现在,每个DAO都依赖于 容器
  • b) 你不能再这样了 从构造器中说出每个 道靠
  • c) 每把刀 至少需要一个隐含的 相互依赖如果你 使用这样的基类,或者您可以 将延迟加载移出 基类,但必须复制 大量的代码
  • d) 单元测试 需要创建和预填充 容器

我刚刚发现了一种更好的方法,至少对温莎城堡有效。通过将数据访问对象依赖项更改为属性而不是构造函数依赖项,Windsor将在完全实例化每个对象后自动填充它们

因此,以下方法效果很好:

public class CustomerDao : ICustomerDao {

    private IProductDao _productDao;

    public IProductDao ProductDao {
        get { return _productDao; }
        set { _productDao = value; }
    }
    public CustomerDao() { }
    public Customer GetById(int id) {
        Customer c = new Customer(_productDao);
        // Query the database and fill out CustomerId
        return c;
    }
}
public class ProductDao : IProductDao {
    private ICustomerDao _customerDao;

    public ProductDao() { }

    public ICustomerDao CustomerDao {
        get { return _customerDao; }
        set { _customerDao = value; }
    }

    public Product[] GetAllForCustomer(Customer customer) {
        return null;
    }

}

正如其他海报所建议的那样,你可能想重新思考你的架构——看起来你在给自己制造麻烦

另请注意:

通过更改数据访问对象 依赖于属性而不是 温莎将 在实例化每个后自动填充它们 完全反对


小心点。在这样做时,您基本上告诉Windsor这些依赖项是可选的(不同于通过构造函数注入的依赖项)。这似乎是个坏主意,除非这些依赖项是真正可选的。如果温莎不能满足所需的依赖性,你希望它呕吐。

这是一种简化。从技术上讲,他们没有。我被禁止使用NHibernate,但它的工作方式与实际对象是POCO的情况类似,这些代理覆盖了必要的方法,从来没有被直接实例化过。你说没有这样的循环依赖是什么意思?在我的例子中,客户引用产品,反之亦然,你能告诉我吗?这是真的,体系结构很笨拙,但编写框架代码更糟糕,NHibernate不是一个选项。目前,我实际上已经将代码移动到一个完全独立的延迟加载代理层,该层的工作方式类似于NHibernate。
public class CustomerDao : ICustomerDao {

    private IProductDao _productDao;

    public IProductDao ProductDao {
        get { return _productDao; }
        set { _productDao = value; }
    }
    public CustomerDao() { }
    public Customer GetById(int id) {
        Customer c = new Customer(_productDao);
        // Query the database and fill out CustomerId
        return c;
    }
}
public class ProductDao : IProductDao {
    private ICustomerDao _customerDao;

    public ProductDao() { }

    public ICustomerDao CustomerDao {
        get { return _customerDao; }
        set { _customerDao = value; }
    }

    public Product[] GetAllForCustomer(Customer customer) {
        return null;
    }

}