Как правильно внедрить зависимость доступа к данным для отложенной загрузки? - PullRequest
11 голосов
/ 25 сентября 2008

Как правильно внедрить зависимость доступа к данным при ленивой загрузке?

Например, у меня есть следующая структура класса

class CustomerDao : ICustomerDao
  public Customer GetById(int id) {...}

class Transaction {
  int customer_id; //Transaction always knows this value
  Customer _customer = null;
  ICustomerDao _customer_dao;
  Customer GetCustomer() {
    if(_customer == null)
      _customer = _customer_dao.GetById(_customer_id);
    return _customer
  }

Как получить ссылку на _customer_dao в объект транзакции? Требовать его для конструктора, кажется, не имеет смысла, если я хочу, чтобы Транзакция выглядела как POCO. Можно ли, чтобы объект Transaction ссылался на Inversion of Control Container напрямую? Это тоже кажется неловким.

Как фреймворки, такие как NHibernate, справляются с этим?

Ответы [ 3 ]

7 голосов
/ 07 февраля 2009

Я предлагаю что-то другое ... Использовать ленивый класс загрузки:

public class Lazy<T>
{
   T value;
   Func<T> loader;

   public Lazy(T value) { this.value = value; }
   public Lazy(Func<T> loader { this.loader = loader; }

   T Value
   {
     get 
    {
       if (loader != null)
       {
         value = loader();
         loader = null;
       }

       return value;
    }

    public static implicit operator T(Lazy<T> lazy)
    {
        return lazy.Value;
    }

    public static implicit operator Lazy<T>(T value)
    {
        return new Lazy<T>(value);
    }
}

Как только вы его получите, вам больше не нужно вводить dao в ваш объект:

public class Transaction
{
    private static readonly Lazy<Customer> customer;

    public Transaction(Lazy<Customer> customer)
    {
      this.customer = customer;
    }

    public Customer Customer
    {
       get { return customer; } // implicit cast happen here
    }
}

При создании объекта Transcation, который не привязан к базе данных:

new Transaction(new Customer(..)) // implicite cast 
                                  //from Customer to Lazy<Customer>..

При регенерации транзакции из базы данных в хранилище:

public Transaction GetTransaction(Guid id)
{
   custmerId = ... // find the customer id 
   return new Transaction(() => dao.GetCustomer(customerId));
}

Происходят две интересные вещи: - Ваши доменные объекты могут использоваться с доступом к данным или без него, он становится невежественным. Единственный маленький поворот состоит в том, чтобы позволить передать функцию, которая дает объект вместо самого объекта. - Класс Lazy является внутренне изменяемым, но может использоваться как неизменяемое значение. Ключевое слово readonly сохраняет свою семантику, поскольку его содержимое не может быть изменено извне.

Если вы хотите, чтобы поле было доступно для записи, просто удалите ключевое слово readonly. при назначении нового значения будет создано новое Lazy с новым значением из-за неявного приведения.

Edit: Я писал об этом здесь:

http://www.thinkbeforecoding.com/post/2009/02/07/Lazy-load-and-persistence-ignorance

1 голос
/ 25 сентября 2008

Я не очень знаком с термином POCO, но определения, которые я прочитал, как правило, следуют духу объекта, независимого от какой-то более крупной структуры.

Тем не менее, независимо от того, как вы нарезаете его, если вы выполняете внедрение зависимостей, вы будете сотрудничать с теми классами, в которые внедрена функциональность, и с чем-то, что привязывает зависимые от объектов объекты, будь то полноразмерный инжекционный каркас или просто какой-то класс ассемблера.

Мне кажется странным вставлять ссылку на контейнер IOC в класс. Я предпочитаю, чтобы мои инъекции происходили в конструкторе, когда код выглядит примерно так:

public interface IDao<T>
{
    public T GetById(int id);
}


public interface ICustomerDao : IDao<Customer>
{
}

public class CustomerDao : ICustomerDao
{
    public Customer GetById(int id) 
    {...}
}

public class Transaction<T> where T : class
{

    int _id; //Transaction always knows this value
    T _dataObject;
    IDao<T> _dao;

    public Transaction(IDao<T> myDao, int id)
    {
        _id = id;
        _dao = myDao;
    }

    public T Get()
    {
        if (_dataObject == null)
            _dataObject = _dao.GetById(_id);
        return _dataObject;
    }
}
1 голос
/ 25 сентября 2008

Обычно я делаю внедрение зависимостей в конструкторе, как вы сделали выше, но продолжайте ленивую загрузку, действуя только тогда, когда вызывается «get», как у меня ниже. Не уверен, что это именно тот подход, который вам нужен, но он устраняет «грязный» конструктор DI / Lazy Loading за 1 шаг;)

public class Product
{
    private int mProductID;
    private Supplier mSupplier;
    private ISupplierService mSupplierService;

    public Product()
    {
      //if you want your object to remain POCO you can use dual constr
      //this constr will be for app use, the next will be for testing
    } 

    public Product(ISupplierService SupplierService)
    {
        mSupplierService = SupplierService;
    }

    public Supplier Supplier {
        get {
            if (mSupplier == null) {
                if (mSupplierService == null) {
                    mSupplierService = new SupplierService();
                }
                mSupplier = mSupplierService.GetSupplierByProductID(mProductID);
            }
            return mSupplier;
        }
        set { mSupplier = value; }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...