views:

276

answers:

5

I'm working on a module that requires a strictly decoupled interface. Specifically, after instantiating the root object (a datasource), the user's only supposed to interact with the object model via interfaces. I have actual factory objects (I'm calling them providers) to supply instances that implement these interfaces, but that left the clumsiness of getting the providers. To do so, I've supplied a couple methods on the datasource:

public class MyDataSource
{
    private Dictionary<Type, Type> providerInterfaceMapping = new Dictionary<Type, Type>()
    {
        { typeof(IFooProvider), typeof(FooProvider) },
        { typeof(IBarProvider), typeof(BarProvider) },
        // And so forth
    };

    public TProviderInterface GetProvider<TProviderInterface>()
    {
        try
        {
            Type impl = providerInterfaceMapping[typeof(TProviderInterface)];
            var inst = Activator.CreateInstance(impl);

            return (TProviderInterface)inst;
        }
        catch(KeyNotFoundException ex)
        {
            throw new NotSupportedException("The requested interface could not be provided.", ex);
        }
    }
}

I've modified some details on the fly to simplify (e.g., this code snippet doesn't include the parameters passed to the implementation instance that's created). Is this a good general approach for implementation of a factory method in C#?

+4  A: 

You should rather take a step back and ask whether using a factory method at all is a good idea? In my opinion, it is not.

There are more than one issue with factory methods, and your example illustrates several:

  • You need to have a hard reference to the implementation (FooProvider in addition to IFooProvider), which is exactly the situation you are trying to avoid in the first place. Even if the rest of your code only consumes IFooProvider, your library is still tightly coupled to FooProvider. Some other developer may come by and start using FooProvider directly if he/she isn't aware of your factory method.
  • You only support implementations that have default constructors, since you are using Activator.CreateInstance. This prevents you from using nested dependencies.

Instead of trying to manually control dependencies, I would recommend that you take a look at Dependency Injection (DI). Whenever your code needs an IFooProvider, supply it with Constructor Injection.

Mark Seemann
I'm not sure I understand your first point. Clients don't have access to a FooProvider, only an IFooProvider. FooProvider is internal to the assembly. Or do I miss your meaning?
Greg D
Re: the second point, I actually do support implementations that take parameters. Like I mentioned in the question, I omitted that bit from the sample I included just to make it simpler. It is a bit clumsy, though-- providers all are expected to take a datasource as their sole parm and get everything they need from it. This allows the creation of a FakeDataSource that can be supplied via the .ctor parm so that a provider implementation can depend on fakes during unit testing. The fake datasource only supplies the fake providers.
Greg D
I'm sorry if I made some wrong assumptions, which means some of my exact points of criticism don't apply in your particular situation, but I still stand by my general advice that there's no reason to reinvent the wheel. A DI Container can do this and much more for you for free.
Mark Seemann
@Mark: I agree, and in general I'd love to use one instead of rolling my own. This project is @the office, though, and I'm not allowed to do that sort of thing here. (See my other comments re: that). If I were, what DI container(s) have you had the best luck with? I won't be working here forever, and I'd like to learn them on my own time.
Greg D
Also, I think you might have something with your first point. I'm just not sure that I understand what you're saying. :) E.g., I have the interface and impl defined in the same assembly. Should I have split them out, somehow, into separate physical modules?
Greg D
Regarding hand-rolling: I understand what you are saying, but DI doesn't require a DI Container (they just make your life much easier). You can still use Poor Man's DI without a DI Container, and without resorting to factories. It's more complicated to explain than what I can do in a comment, but if I may be so bold as to recommend my upcoming book, it contains lots of material about this subject: http://www.manning.com/seemann
Mark Seemann
Regarding your question about interfaces and implementations defined in the same assembly: I simply made the wrong assumption about your example, as a very common design is to have interfaces and implementations in separate assemblies (in which case you want to keep them separated). In your case, it sounds like you have Local Defaults instead, which means that my first point doesn't really apply in your case.
Mark Seemann
ah, the book looks interesting! I've been looking at testable code and DI a bit more recently with the intention to learn more about it. I'll add your contribution to my "to-read" list. :)
Greg D
+1  A: 

Technically this is fine, however most times when I see a factory it usually returns the same type interface, for instance something like IProvider rather than IFooProvider or IBarProvider which to me doesn't make sense. If you are going to have FooProvider and BarProvider then why have different interfaces for them. I would use one interface IProvider and have FooProvider and BarProvider implement that.

Stan R.
You make a fair point, and in fact both `IFooProvider` and `IBarProvider` do inherit from a `IProvider<>` for common bits. `Foos`, `Bars`, and `Bazs` all take different parameters on construction, though, so their Create method signatures are different.
Greg D
A: 

Regardless of the rightness or wrongness of using the factory method (as that is not what you asked about!), your implementation looks fine to me.

Something that may work for you better than hardcoding the type mapping is putting that info in a configuration file and loading it in your app.

Cocowalla
+3  A: 

Don't reinvent your own implementation of dependency injection, use an existing library like Spring.NET or the Microsoft Unity application block.

Injecting dependencies is a common programming problem that you shouldn't have to solve yourself. There are some nice lightweight libraries out there (I mentioned a couple above) that do the job well. They support both declarative and imperative models of defining dependencies and are quite good at what they do.

LBushkin
I'd love not to, but for (stupid) political reasons, in my corporate environment, any sort of perceived external dependency like that is an automatic and instant lose. Likewise for an ORM (which is what I'd _really_ want to be using for this). We can't even use Boost.
Greg D
Yes, it's a very severe case of self-destructive NIH. And yes, it's self-destructive to the point of killing the org. This is my last assignment before I'm laid off. I've been trying to fight the good fight for the last five years, but I'm not good at sales or politics, and when I found out that I'm getting laid off along with 60-70% of my colleagues and that all dev't work is getting shipped overseas, it's time to stop tilting at windmills.
Greg D
Yeah, that's an unfortunate situation. I hope you have better luck being part of a more enlightened organization in your next position.
LBushkin
I agree. :) I'm pushing for it. I've already turned down an offer because it looked like a similar situation, and I've had enough of that rubbish. I don't understand why a company would reject industry knowledge instead of embracing it. :)
Greg D
A: 

For what it is worth I use this pattern all the time and have abstracted some of this sort of logic into a reusable assembly. It uses reflection, generics and attributes to locate and bind the concrete types at runtime. http://www.codeproject.com/KB/architecture/RuntimeTypeLoader.aspx

This helps to address Mark's concern because implementation types are not hardcoded, and further the implementation types are determined by the installation, not in project assembly references.

dkackman