2008-09-24 6 views
11

Quelle est la bonne façon d'injecter une dépendance d'accès aux données lorsque je fais un chargement paresseux?Quelle est la bonne façon d'injecter une dépendance d'accès aux données pour un chargement paresseux?

Par exemple, j'ai la structure de classe suivante

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 
    } 

Comment puis-je obtenir la référence à _customer_dao dans l'objet de la transaction? L'exiger pour le constructeur semble ne pas avoir de sens si je veux que la Transaction ressemble au moins à un POCO. Est-il acceptable que l'objet Transaction référence directement l'Inversion of Control Container? Cela semble aussi gênant aussi.

Comment les frameworks comme NHibernate gèrent-ils cela?

Répondre

1

Je fais typiquement l'injection de dépendances dans le constructeur comme vous l'avez fait ci-dessus, mais faites un pas de plus au chargement paresseux en agissant seulement quand le "get" est appelé comme ci-dessous. Je ne sais pas si cela est la pure approche que vous cherchez, mais il n'élimine le « sale » constructeur DI/Lazy Chargement en 1 étape;)

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; } 
    } 
} 
+0

pourrait être plus élégant, mais ce qui est généralement la mise en œuvre j'ai le temps. Il laisse également votre code ouvert à une bibliothèque de plus haut niveau pour gérer le chargement paresseux par réflexion, si vous êtes dans ce genre de chose. – ojrac

+2

Même remarque que ci-dessus: Les services d'injection (et en particulier DAO) ne sont pas une bonne idée. Cela rend l'objet inutilisable sans dépendance, et le rend très difficile à tester. Il rompt mal l'ignorance de la persistance. – thinkbeforecoding

+0

Personnellement, j'essaie d'éviter le scénario où une propriété ('mSupplier') * pourrait être chargée, mais peut-être pas. Cela m'inquiéterait chaque fois que j'utiliserais l'entité: quelqu'un l'a-t-il chargé de manière incomplète, c'est-à-dire que 'mSupplier' est nul mais cela n'aurait pas dû être? Ai-je besoin de vérifications nuls sur 'mSupplier'? Est-ce que ces vérifications nuls ont la chance de déclencher une charge paresseuse? (Certes, je cherche toujours le Saint Graal.) – Timo

1

Je ne suis pas terriblement familier avec le terme POCO, mais les définitions que j'ai lues semblent généralement suivre l'esprit de l'objet étant indépendant d'un cadre plus large. Cela dit, peu importe comment vous le découpez, si vous effectuez une injection de dépendance, vous allez avoir des collaborations avec les classes dans lesquelles la fonctionnalité est injectée, et quelque chose qui colle les objets dépendants, que ce soit son un cadre d'injection complet ou juste une classe d'assembleur.

Pour moi, il semble étrange d'injecter une référence à un conteneur IOC dans une classe. Je préfère mes injections se produire dans le constructeur avec le code à la recherche quelque chose comme ceci:

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; 
    } 
} 
+0

Donc, vous dites essentiellement que je devrais l'injecter à travers le constructeur? Peut-être, mais semble maladroit. –

+1

Services d'injection (et en particulier DAO) n'est pas une bonne idée. Cela rend l'objet inutilisable sans dépendance, et le rend très difficile à tester. Il rompt mal l'ignorance de la persistance. – thinkbeforecoding

7

Je suggère quelque chose de différent ... Utilisez une classe de charge paresseuse:

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

Une fois que vous obtenez, vous ne pas besoin d'injecter le dao en vous opposez plus:

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

Lors de la création d'un objet transcation qui n'est pas lié à la base de données:

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

Lors de la régénération d'une opération à partir de la base de données dans le référentiel:

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

Deux choses intéressantes se produisent: - Vos objets de domaine peuvent être utilisés avec ou sans accès aux données, il devient données acces ignorant. La seule petite torsion est de permettre de passer une fonction qui donne l'objet à la place de l'objet lui-même. - La classe Lazy est mutable en interne mais peut être utilisée comme une valeur immuable. Le mot clé readonly conserve sa sémantique, puisque son contenu ne peut pas être modifié de manière externe.

Lorsque vous souhaitez que le champ soit accessible en écriture, supprimez simplement le mot-clé readonly. lors de l'attribution d'une nouvelle valeur, un nouveau Lazy sera créé avec la nouvelle valeur en raison de la distribution implicite.

Edit: Je blogué à ce sujet ici:

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

+0

Quoi qu'il en soit, il est toujours préférable de définir correctement les agrégats et d'éviter le chargement paresseux ... – thinkbeforecoding

Questions connexes