动态更改函数的调用方

本文关键字:调用 函数 动态 | 更新日期: 2023-09-27 17:57:20

我已经在我的应用程序的 DAL 中实现了 2 个提供程序。

一个是 Redis 缓存提供程序,另一个是数据库提供程序。

public class CacheProvider : IProvider
{
    public List<int> GetCustomerIds()
    {
        return cache.GetCustomerIds();
    }
}
public class DBProvider : IProvider
{
    public List<int> GetCustomerIds()
    {
        return db.GetCustomerIds();
    }
}

我已经为这些实现了一个接口

public interface IProvider
{
     List<int> GetCustomerIds();
}

我有以下情况。

如果缓存函数

在尝试执行时以某种方式失败或缓存过期,我想回退并调用函数上的数据库版本。

将实现许多函数,所以我正在考虑创建一个网关,其中所有函数都将作为参数传递,如果失败则回退到数据库版本

public List<int> RunTheMethod(Func<int> myMethodName)
{
    // Run method from cache
    myMethodName()
    if method fails, run method from db
    myMethodName()
}

有没有办法实现这种功能?我知道我可能必须实现其中的一些方法,因为参数会有所不同。

动态更改函数的调用方

有可能,通过一些反思来制作这项工作,尽管我不确定性能

我们有 IDataProvider,它由缓存提供程序、数据库提供程序和"网关"实现(我在 IDataProvider 中包含 3 种方法来显示不同返回/参数和重载的示例)

网关不必实现 IDataProvider,但这样做会使生活更容易一些,因为网关上的方法需要与提供程序上调用的方法具有相同的签名。

网关包含 IDataProvider 的列表,对于每个调用,它都会向下浏览列表并尝试执行。它返回第一个成功,如果没有成功,则引发异常。

Execute<>() 方法是一种将所有内容连接起来的快速方法,我们可以每次调用它,让它处理与 IDataProviders 上的方法匹配并在失败时重试。

出于测试目的,我创建了一种方法来强制第一个(缓存提供程序)失败。

interface IDataProvider {
        List<int> Method1();
        List<string> Method2(string parameter1);
        List<string> Method2(string parameter1, string parameter2);
}
class DataProvider1 : IDataProvider {
    private readonly string[] Strings = { "A", "B", "C" };
    private bool _callFails;
    public DataProvider1(bool callFails) {
        _callFails = callFails;
    }
    public List<int> Method1() {
        if (_callFails) {
            throw new Exception();
        }
        return new List<int>(){1,2,3};
    }
    public List<string> Method2(string parameter1) {
        if (_callFails) {
            throw new Exception();
        }
        return Strings.Select(s => s + parameter1).ToList();
    }
    public List<string> Method2(string parameter1, string parameter2) {
        if (_callFails) {
            throw new Exception();
        }
        return Strings.Select(s => s + parameter1 + parameter2).ToList();
    }
}
class DataProvider2 : IDataProvider {
    private readonly string[] Strings = { "D", "E", "F" };
    public List<int> Method1() {
        return new List<int>(){4,5,6};
    }
    public List<string> Method2(string parameter1) {
        return Strings.Select(s => s + parameter1).ToList();
    }
    // overload
    public List<string> Method2(string parameter1, string parameter2) {
        return Strings.Select(s => s + parameter1 + parameter2).ToList();
    }
}

class Gateway : IDataProvider {
    private readonly List<IDataProvider> _dataProviders;
    public Gateway(IEnumerable<IDataProvider> dataProviders) {
        _dataProviders = new List<IDataProvider>(dataProviders);
    }
    public List<int> Method1() {
        return Execute<List<int>>();
    }
    public List<string> Method2(string parameter1) {
        return Execute<List<string>>(parameter1);
    }
    public List<string> Method2(string parameter1, string parameter2) {
        return Execute<List<string>>(parameter1, parameter2);
    }

    private T Execute<T>(params object[] parameters) {
        StackTrace stackTrace = new StackTrace();
        MethodBase methodBase = stackTrace.GetFrame(1).GetMethod();
        var methodInfo = typeof(IDataProvider).GetMethod(methodBase.Name, methodBase.GetParameters().Select(p => p.ParameterType).ToArray());
        var index = 0;
        while (index < _dataProviders.Count) {
            try {
                return(T)methodInfo.Invoke(_dataProviders[index], parameters);
            } catch (Exception) {
                index++;
            }
        }
        throw new Exception("None of the methods succeeded");
    }
}

单元测试

[TestClass]
public class DataProviderFixture {
    #region Create
    private Gateway Create(bool firstCallFails = false) {
        return new Gateway(new IDataProvider []{
            new DataProvider1(firstCallFails), 
            new DataProvider2()});
    }
    #endregion

    [TestMethod]
    public void ExecuteNoProblems() {
        var gateway = Create();
        var numbers = gateway.Method1();
        CollectionAssert.AreEqual(new[] { 1, 2, 3 }, numbers);
        var letters = gateway.Method2("1");
        CollectionAssert.AreEqual(new[] { "A1", "B1", "C1" }, letters);
        letters = gateway.Method2("1", "a");
        CollectionAssert.AreEqual(new[] { "A1a", "B1a", "C1a" }, letters);
    }
    [TestMethod]
    public void ExecuteFirstCallFails() {
        var gateway = Create(true);
        var numbers = gateway.Method1();
        CollectionAssert.AreEqual(new[] { 4, 5, 6 }, numbers);
        var letters = gateway.Method2("2");
        CollectionAssert.AreEqual(new[] { "D2", "E2", "F2" }, letters);
        letters = gateway.Method2("1", "b");
        CollectionAssert.AreEqual(new[] { "D1b", "E1b", "F1b" }, letters);
    }
}