Как избежать циклических зависимостей объекта доступа к данным, когда вам нужно выполнять отложенную загрузку (и с использованием контейнера IOC)? - PullRequest
2 голосов
/ 07 ноября 2008

Примечание. Приведенные ниже примеры относятся к C #, но эта проблема не должна относиться конкретно к какому-либо языку.

Итак, я создаю объектную область, используя вариант S # Architecture . Для тех, кто не знаком с ним, и чтобы сэкономить ваше время на чтение, идея заключается в том, что у вас есть интерфейс доступа к данным для каждого из ваших доменных объектов, который отвечает за загрузку в / из слоя постоянства. Все, что может понадобиться для загрузки / сохранения данного объекта, принимает интерфейс доступа к данным этого объекта как зависимость. Так, например, у нас может быть следующее, где продукт будет лениво загружать клиента, который приобрел его по мере необходимости:

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);
}

Я знаю, что это действительно распространенная ситуация, но я относительно новичок в этом. Моим камнем преткновения является то, что нужно делать при реализации объектов доступа к данным. Поскольку у Клиента есть зависимость от IProductDao, реализация CustomerDao также должна, однако обратное также верно, и ProductDao должен зависеть от ICustomerDao.

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
      }
 }

И здесь у нас проблема. Вы не можете создать экземпляр CustomerDao без IProductDao и наоборот. Моя инверсия контейнера управления (Замок Виндзор) поражает круговую зависимость и задыхается.

Я придумала решение на время, которое предполагает ленивую загрузку самих объектов DAO (я опубликую это как ответ), но мне это не нравится. Каковы проверенные временем решения этой проблемы?

РЕДАКТИРОВАТЬ: Выше приведено упрощение архитектуры, которую я на самом деле использую, и я не рекомендую кому-то фактически передавать DAO для объекта. Лучшая реализация, более близкая к тому, что я на самом деле делаю, похожа на работу NHibernate, когда фактические объекты очень просты, а вышеприведенные являются прокси-объектами, которые наследуют и переопределяют соответствующие поля.

Ответы [ 5 ]

2 голосов
/ 15 ноября 2008

Как предлагали другие авторы, вы, возможно, захотите переосмыслить свою архитектуру - похоже, вы все усложняете для себя.

Также обратите внимание:

Изменяя объект доступа к данным зависимости от свойств, а не зависимости конструктора Виндзор автозаполнение после каждого экземпляра объект полностью.

Будь осторожен. При этом вы в основном сказали Windsor, что эти зависимости являются необязательными (в отличие от зависимостей, вводимых через конструктор). Это кажется плохой идеей, если только эти зависимости не являются действительно необязательными. Если Виндзор не может выполнить требуемую зависимость, вы хотите ее вырвать.

1 голос
/ 08 ноября 2008

Я только что обнаружил значительно лучший подход, который работает по крайней мере с замком Виндзор. Изменяя зависимости объекта доступа к данным к свойствам, а не к зависимости конструктора, 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;
    }

}
1 голос
/ 07 ноября 2008

Ленивая загрузка должна управляться вашим персистентным слоем, а не вашими репозиториями.

Кроме того, ваши объекты Customer и Product не должны иметь никаких ссылок на репозитории, это просто кажется ... неправильным.

0 голосов
/ 07 ноября 2008

Мое решение, и позвольте мне еще раз отрицать, что я не большой поклонник этого, состоит в том, чтобы каждый из объектов доступа к данным лениво загружал 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;
    }

}

, а затем

public class CustomerDao : BaseDao, ICustomerDao {
      public Customer GetById(int id) {
          Customer c = new Customer(_CustomerDao);
          // Query the database and fill out CustomerId
          return c;
      }
 }
public class ProductDao : BaseDao, IProductDao {
      public Product[] GetAllForCustomer(Customer customer) {
          // use the base class's _ProductDao to instantiate Products
      }
 }

Мне это не нравится, потому что

  • a) Каждый DAO теперь зависит от контейнер
  • б) Вы больше не можете сказать от конструктора, кто каждый Дао зависит от
  • в) Либо каждый DAO нужен как минимум скрытый зависимость друг от друга Дао, если вы использовать такой базовый класс или вы могли бы переместить ленивую загрузку из базовый класс, но должен дублировать много кода.
  • г) Юнит-тесты необходимо создать и предварительно заполнить Контейнер
0 голосов
/ 07 ноября 2008

У вас просто не должно быть таких циклических зависимостей. Это просто неправильно.

Если вам просто нужно некоторое время ошибаться, чтобы преодолеть горб, попробуйте использовать локатор службы: класс, который знает о контейнере и разрешает зависимости, когда они используются, а не когда создается экземпляр вашего класса DAO. .

Возможно, вы захотите проверить реализацию локатора службы Oren Eini в Rhino.Tools (это статический класс с именем IoC)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...