创建一个支持随时间变化的工厂

Creating a factory that supports change over time

我有一个 Engine class,一个使用该引擎的 App class,以及一个接口IEngine 抽象为:

public interface IEngine
{
    void Foo();
}

public class Engine1 : IEngine
{
    public void Foo()
    {
        //...
    }
}    

public class App1
{
    public void Do()
    {
        IEngine1 e = new Engine1();
        e.Foo();
    }
}

因为我可以有多个引擎,所以我正在实施一个引擎工厂来生产引擎,returns它们作为 IEngine:

public class EngineFactory
{
    public IEngine CreateEngine(string engineName)
    {
        //returns the right engine according to 'engineName'
    }
}

这样一个应用程序和一个引擎是松散耦合的,任何 应用程序 class 都不会引用具体的引擎。

最后,我希望能够为我的(所有)引擎添加新方法。很自然的做法是将这些方法添加到 IEngine,但如果我这样做,我将不得不重新编译所有使用 IEngine[=26] 的应用程序=].我的解决方案是创建一个新接口 IEngine2:

public interface IEngine2 : IEngine
{
    void Goo();
}

但这将迫使我更改工厂中的 CreateEngine 方法签名。

我怎样才能避免这种情况?我应该将工厂更改为通用的吗?我应该使用 DI 吗?还有别的吗?

编辑:你可以这样想——我负责引擎的实现,而我的客户正在实现应用程序。现在有一个客户需要所有引擎的新功能,我需要在不强迫所有其他客户重新编译他们的应用程序的情况下实现该功能(类似于 API 实现)。

一种解决方案可能是使用功能模式:

public interface IEngine
{
    bool TryGetCapability<T>(out T capability);
}

public interface ICapability1
{
    void Foo();
}

public class Engine1 : IEngine, ICapability1
{
    public bool TryGetCapability<T>(out T capability)
    {
        if (this is T)
        {
            capability = this as T;
            return true;
        }
        capability = default(T);
        return false;
    }

    public void Foo()
    {
        //...
    }    
}    

public class App1
{
    public void Do()
    {
        IEngine e = new Engine1();
        ICapability1 cap1;
        if (e.TryGetCapability(out cap1))
        {
            cap1.Foo();
        }
    }
}

我使用引擎 class 来实现该功能,但在真正的应用程序中,它通常会在每个功能 class 上实现。

这是 COM 世界中的常见问题,一旦发布,您就永远不要修改接口。 COM 通常解决此问题的方法是使用从以前版本继承的新版本编号接口。

public interface IEngine
{
    void Foo();
}

public interface IEngine2 : IEngine
{
    void Goo();
}

public class EngineA : IEngine
{
    public void Foo()
    {
        //...
    }
}   

public class EngineB : IEngine2
{
    public void Foo()
    {
        //...
    }

    public void Goo()
    {
        //...
    }
} 

对于您的工厂,您可以始终return一个 IEngine 或通用的,用户必须指定该项目必须支持的最小接口。

public class EngineFactory
{
    public T CreateEngine<T>(string engineName) where T : IEngine
    {
        //returns the right engine according to 'engineName'
    }
}

public class App
{
    public void Do(EngineFactory factory)
    {
        //Creates a instance of EngineA
        IEngine e = factory.CreateEngine<IEngine>("EngineA");

        //returns a instance of EngineB
        IEngine eB = factory.CreateEngine<IEngine>("EngineB");

        //returns null
        IEngine2 e2 = factory.CreateEngine<IEngine2>("EngineA");

        //returns a instance of EngineB
        IEngine2 e2B = factory.CreateEngine<IEngine2>("EngineB");
    }
}