工厂接口使用对象参数创建方法

时间:2014-10-24 09:13:32

标签: c# dependency-injection factory-pattern

我有一个关于使用create方法创建工厂接口的问题,该方法可以根据实现方式接受不同的参数类型。

为了给你更多背景知识,我在项目中使用了依赖注入,并且需要在运行时生成有状态对象 - 因此我注入工厂(而不是对象本身)来创建这些有状态对象。我遇到的问题是,对于某些接口,具体实现根本不能具有相同的构造函数参数类型,因此创建这些接口实例的工厂需要将几乎“动态”参数传递给create方法。

我已经讨论了几天了,以下是我能想出的最佳解决方案(即将对象传递给工厂创建方法并将其投射到工厂的具体实现中)。我真的在寻找之前遇到过这种情况的人的反馈,听听他们提出的问题,以及我提出的解决方案是否可以接受。

如果缺少任何信息,请道歉,并提前多多感谢!

//
// Types...
//

interface IDataStore
{
    List<string> GetItems();
}

public class XmlDataStore : IDataStore
{
    public XmlDataStore(XmlDocument xmlDoc)
    {
        // Initialise from XML Document...
    }

    public List<string> GetItems()
    {
        // Get Items from XML Doc...
    }
}

public class SQLDataStore : IDataStore
{
    public SQLDataStore(SqlConnection conn)
    {
        // Initialise from SqlConnection...
    }

    public List<string> GetItems()
    {
        // Get Items from Database Doc...
    }
}

//
// Factories...
//

interface IDataStoreFactory
{
    IDataStore Create(object obj);
}

class XmlDataStoreFactory : IDataStore
{
    IDataStore Create(object obj)
    {
        // Cast to XmlDocument
        return new XmlDataStore((XmlDocument)obj);
    }
}

class SQLDataStoreFactory : IDataStore
{
    IDataStore Create(object obj)
    {
        // Cast to SqlConnection
        return new SQLDataStore((SqlConnection)obj);
    }
}

2 个答案:

答案 0 :(得分:1)

我不确定我是否正确理解了您的问题,但对我来说,在您调用它们时使用工厂实例创建有状态对象听起来有点奇怪。

直接回答您的问题:泛型是您的解决方案。你的rinterface成为一个开放的通用抽象:

interface IDataStore<TStoreType>
{
    List<string> GetItems();
}

interface IDataStoreFactory<TStoreType>
{
    IDataStore<TStoreType> Create(TStoreType obj);
}

您的工厂类将如下所示:

class XmlDataStoreFactory : IDataStoreFactory<XmlDocument>
{
    IDataStore<XmlDocument> Create(XmlDocument document)
    {
        return new XmlDataStore(document);
    }
}

class SQLDataStoreFactory : IDataStoreFactory<SqlConnection>
{
    IDataStore<SqlConnection> Create(SqlConnection connection)
    {
        return new SQLDataStore(connection);
    }
}

这样可行,但从您给出的示例中我得到的印象是您在整个代码库中使用工厂。也许我在这一点上错了,但看看你的设计并减少工厂的数量。需要工厂意味着将数据与行为混合在一起,这最终会让你陷入困境。

例如,假设您有某种服务,在登录时将当前用户添加到审计日志中。此服务需要当前用户,这是运行时数据(或上下文数据)的典型示例。但不是:

public class AuditLogService
{
    public void AddApplicationSignIn(User user)
    {
        //... add user to some log
    }
}

我知道这不是一个很好的例子,因为你实际上不需要这个类的工厂,但是下一个代码示例你会明白这一点:

public class AuditLogService
{
    private readonly IUserContext userContext;        

    public AuditLogService(IUserContext userContext)
    {
        this.userContext = userContext;
    }

    public void AddApplicationSignIn()
    {
        var user = this.userContext.GetCurrentUser();
         //... add user to some log
    }
}

因此,通过从行为中分割数据,您可以排除对工厂的需求。并承认有些工厂是最好的解决方案。我认为IDataStore不是你需要工厂的东西。

有关分割数据和行为的好博客,请阅读here

答案 1 :(得分:1)

基于this注释,您需要一个工厂,它可以生成多种类型的IDataStore。您可以通过在单例工厂实例中创建一个开放的通用工厂方法来完成。

interface IDataStore<TStoreType> 
{
    void SetBaseType(TStoreType obj);
    List<string> GetItems();
}

interface IDataStoreFactory
{
    IDataStore<TStoreType> Create<TStoreType>(TStoreType obj) 
}

class DataStoreFactory : IDataStoreFactory
{
    public IDataStore<TStoreType> Create<TStoreType>(TStoreType obj)
    {
        if (obj.GetType() == typeof(SqlConnection))
        {
            var store = new SQLDataStore((SqlConnection)(Object)obj);
            return (IDataStore<TStoreType>)store;
        }
        if (obj.GetType() == typeof(XmlDocument))
        { //... and so on }
    }
}

class SQLDataStore : IDataStore<SqlConnection>
{
    private readonly SqlConnection connection;
    public SQLDataStore(SqlConnection connection)
    {
        this.connection = connection;
    }

    public List<string> GetItems() { return new List<string>(); }
}

你可以像这样使用这个工厂:

var factory = new DataStoreFactory();
var sqlDatastore = factory.Create(new SqlConnection());
var xmlDatastore = factory.Create(new XmlDocument());


如果您使用DI容器,您的数据存储工厂将变得复杂得多。您可以在工厂中注入容器并直接从容器中检索实例,这通常会从下到上构建您的实例,包括自己的依赖项,生命周期管理等。但是要非常小心这种方法,这是使用服务定位器模式的第一步,即anti pattern