Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory method and generics

I have the following interface and implementation:

public interface IRepository<T>
{
    IList<T> GetAll();
}

internal class TrendDataRepository : IRepository<TrendData>
{
    public IList<TrendData> GetAll()
    {
        //.. returns some specific data via Entity framework
    }
}

I'm going to have multiple implementations that all return different data by Entity Framework. At some point I want to represent the user a list of classes that implement the IRepository interface. I do this with the following code. This works great for me.

    public static IEnumerable<string> GetAvailableRepositoryClasses()
    {
        var repositories = from t in Assembly.GetExecutingAssembly().GetTypes()
                           where t.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof (IRepository<>))
                           select t.Name;

        return repositories;
    }

However, I would also like to create a factory method that given a specific string will return a concrete Repository type and allow me to call the 'GetAll' method on it. In pseudo-code:

someObject = Factory.CreateInstance("TrendData");
someObject.GetAll();

(I know this won't work because I have to specify a concrete type in the factory method).

I desire this functionality because I want to give a user the ability to bind a report to a specific datasource. This way they can start a new report where the datasource of the report is bound to (for example) the TrendDataRepository.GetAll() method.

However, maybe because the end of the world is getting near ;-) or it's Friday afternoon and I just can't think clearly any more, I don't know how to realise this.

Some pointers would be really welcomed.

like image 990
Sardaukar Avatar asked Dec 21 '12 13:12

Sardaukar


1 Answers

I'd suggest returning the collection of repository types instead of the names and just displaying the names in the UI:

public static IEnumerable<Type> GetAvailableRepositoryClasses()
{
    return Assembly.GetExecutingAssembly().GetTypes()
        .Where(t => t.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof (IRepository<>)));
}

Then when a user selects the source you can do:

object repository = Activator.CreateInstance(selectedType);

This method requires each repository to have a default constructor.

Activator.CreateInstance return an object, and you can't cast it to your IRepository<T> interface unless you know the generic type T you're expecting. The best solution is probably to create a non-generic IRepository interface which your repository classes also implement:

public interface IRepository
{
    IList<object> GetAll();
}

Now you can cast your created repositories to IRepository:

IRepository repository = (IRepository)Activator.CreateInstance(selectedType);

you might to create a repository base class which implements both:

public abstract class RepositoryBase<T> : IRepository<T>, IRepository
{
    public abstract IList<T> GetAll();
    IList<object> IRepository.GetAll()
    {
        return this.GetAll().Cast<object>().ToList();
    }
}
like image 76
Lee Avatar answered Oct 23 '22 04:10

Lee