How do I best implement dependency injection when many similar but distinct classes need many similar but distinct dependencies?

倖福魔咒の 提交于 2019-12-12 16:05:40

问题


I'm developing a Windows application in Visual Studio .NET 4.0 where I'm using the MVP pattern and I'm trying to learn and apply dependency injection principles throughout the application. But I'm having trouble understanding how to best handle a particular situation. And I apologize in advance for the long explanation, but I don't know if I could be specific enough otherwise.

The application is a general purpose record-keeping app for many different types of records, some of which are related to each other and some of which are unrelated to other records. Each different record type we work with constitutes a distinct "feature" in the application. So the startup screen for the application is essentially a switchboard from which they can select the feature they want to navigate to.

When they make a selection, the window (view) for that feature opens and it's from this window they can perform various operations relating to that feature. Most of the features (e.g. Customers, Orders) have some common operations the user can perform (Creating new records, opening existing records, saving changes, printing, etc.). Some however have operations that are specific to that one feature (e.g. a validation operation specific to the Orders feature). My problem lies in figuring out how to perform the operations within each feature.

Since I'm using MVP the feature window has a presenter that would respond when the user initiates any of the operations for that feature. But since I want to follow the Single Responsibility principle I don't want the presenter to become a God class and actually know how to create records, save, print, etc. So my thought was to define separate classes to handle each operation, as in having an ICreateOrders implementation with a .Create() method, an ISaveOrders implementation with a .Save() method, etc. Just for the sake of having a name for them I'll call them "resolvers" since they actually resolve the request for that operation to happen. Some of these common resolvers would probably have base implementations (e.g. a base FeatureSave class) because much of the code would be similar across features, but each feature needs to be able to have a specific implementation if required. This approach raises a couple of questions.

First I want these actions to be able to be initiated from other places in the app besides just the window for that feature. For example instead of only being able to open order records from within the Orders feature window, I'd also like to be able from the Customers feature to select "View Orders" and have that customer's order records opened and displayed automatically. And to achieve code re-use I'd want to use the same IOpenOrders implementation to do the job regardless of where in the app the request was initiated from. So simply embedding the needed resolvers in the Orders window's presenter doesn't seem like an option.

So my thought was to create a FeatureService class that would hold a collection of Feature objects. At app startup I'd get from the database a list of all the features that are valid for the logged-in user and I'd then iterate over that list and create a Feature object for each one and add it to the FeatureService's collection. Then when I need to initiate an operation in one feature from another I could achieve that by just injecting the FeatureService object into the source class, getting the target feature from it, and then using that to perform the operation. But I don't want the Feature class to be a God class either, so this would simply amount to moving all the individual resolvers from the Orders presenter to the Feature class.

But for this to happen, the Feature object must be injected with all the resolvers it MIGHT need, without knowing if the user will perform those operations or not. For some of the more complex features this could be 20 or 30 distinct resolvers being injected, which certainly seems like constructor injection abuse. And while this keeps the Feature object from becoming a God class, as it's simply passing off each operation to its injected resolver, this seems wrong. Is this correct usage when an object becomes a "God-coordinator" like this?

My other question is about the volume of these objects. For some customers there might be 50 or more separate features available (with more to follow over time no doubt). So as I'm creating these Feature objects at startup and my container is injecting all these resolver dependencies into each one, I'm creating hundreds of objects (e.g. 40 features X 20 resolvers per feature) whether I ever access some of those features or not. A typical user session will probably only involve going into a few features, so it seems wasteful to create so many objects at startup like this. I could give up and just have the Feature object do ServiceLocation internally to get its resolvers, but if there's a better way, I'd like to use it.

I can't figure out where to go from here. I've tried to look and find examples and information on this, but I don't know if I even know enough to know what to search for. Any guidance or insight anyone could provide would be greatly appreciated.


回答1:


If I understand correctly, what you are trying to build using the ICreateOrders and ISaveOrders is the Repository pattern.

Typically, a repository might looks like this:

public interface IRepository<TEntity>
{
    TEntity GetByKey(int key);

    TEntity CreateNew();

    void Save(TEntity entity);

    void Delete(TEntity entity);
}

But for this to happen, the Feature object must be injected with all the resolvers it MIGHT need, without knowing if the user will perform those operations or not. For some of the more complex features this could be 20 or 30 distinct resolvers being injected, which certainly seems like constructor injection abuse.

There is a pattern for grouping repositories together. It is called the Unit of Work pattern.

The unit of work uses a business transaction and coordinates the writing out of changes and may allow access to the repositories it holds.

Typically, when a consumer needs many repositories, you inject an IUnitOfWork instead. Still this just moves the problem of injecting the repositories to the unit of work. This problem can be solved by implementing the unit of work as a factory, or injecting a repository factory into the unit of work:

public interface IRepositoryFactory
{
     IRepository<TEntity> CreateRepository<TEntity>();
}

Creating an implementation for the IRepositoryFactory would be trivial, since this would directly map to your DI container:

private sealed class FrameworkSpecificRepositoryFactory
    : IRepositoryFactory
{
     private readonly Container container;

     public FrameworkSpecificRepositoryFactory(Container container)
     {
         this.container = container;
     }

     public IRepository<TEntity> CreateRepository<TEntity>()
     {
         return this.container.GetInstance<IRepository<TEntity>>();
     }
}

This implementation uses the DI container directly. It looks like a form of the Service Locator anti-pattern, but this depends on where this class is located. If this implementation is part of your Composition Root, in that case this implementation is a infrastructural component and in that case it's fine. Trick is to keep any business logic out of your infrastructural components.

p.s. try to make your questions shorter next time. Shorter questions are more likely to be answered.



来源:https://stackoverflow.com/questions/13203385/how-do-i-best-implement-dependency-injection-when-many-similar-but-distinct-clas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!