【发布时间】:2023-03-30 17:19:01
【问题描述】:
我正在开发允许用户通过实现一组接口来扩展系统的软件。
为了测试我们所做工作的可行性,我的公司“吃自己的狗粮”,方法是在这些类中以与用户完全相同的方式实现我们所有的业务逻辑.
我们有一些实用程序类/方法将所有内容联系在一起并使用可扩展类中定义的逻辑。
我想缓存用户定义函数的结果。我应该在哪里执行此操作?
是类本身吗?这似乎会导致大量代码重复。
是使用这些类的实用程序/引擎吗?如果是这样,不知情的用户可能会直接调用类函数而不会获得任何缓存优势。
示例代码
public interface ILetter { string[] GetAnimalsThatStartWithMe(); }
public class A : ILetter { public string[] GetAnimalsThatStartWithMe()
{
return new [] { "Aardvark", "Ant" };
}
}
public class B : ILetter { public string[] GetAnimalsThatStartWithMe()
{
return new [] { "Baboon", "Banshee" };
}
}
/* ...Left to user to define... */
public class Z : ILetter { public string[] GetAnimalsThatStartWithMe()
{
return new [] { "Zebra" };
}
}
public static class LetterUtility
{
public static string[] GetAnimalsThatStartWithLetter(char letter)
{
if(letter == 'A') return (new A()).GetAnimalsThatStartWithMe();
if(letter == 'B') return (new B()).GetAnimalsThatStartWithMe();
/* ... */
if(letter == 'Z') return (new Z()).GetAnimalsThatStartWithMe();
throw new ApplicationException("Letter " + letter + " not found");
}
}
LetterUtility 应该负责缓存吗? ILetter 的每个单独实例都应该吗?还有其他完全可以做的事情吗?
我试图使这个示例简短,因此这些示例函数不需要缓存。但是考虑一下我添加了这个类,它使(new C()).GetAnimalsThatStartWithMe() 每次运行都需要 10 秒:
public class C : ILetter
{
public string[] GetAnimalsThatStartWithMe()
{
Thread.Sleep(10000);
return new [] { "Cat", "Capybara", "Clam" };
}
}
我发现自己在使我们的软件尽可能快和维护更少的代码(在本例中:将结果缓存在 LetterUtility)和一遍又一遍地执行完全相同的工作(在本例中:每次等待 10 秒使用时间C)。
【问题讨论】:
-
如果用户实现的接口具有引用不透明的函数,因此不可记忆怎么办?这不会破坏这个吗?我想这表明缓存应该由实现类来完成,因为它,或者至少它的编写者,知道它的函数是否是引用透明的,或者什么时候需要缓存。
-
@Sean 是的,它似乎会。我可能不得不告诉每个字母“你自己来缓存”,但这听起来像是我(作为框架维护者)实现的每个 ILetter 的大量复制/粘贴代码。
-
为缓存行为本身创建一个接口(IMemoizer?),并为您自己的缓存目的实现它。然后有一个 ILetter 的默认实现(它可以是抽象的、内部的等),您知道可以正确使用您的默认 IMemoizer。此外,在 ILetter 接口上放置一个返回 IMemoizer 接口的属性或方法,另外可能还有一个像“IsMemoizable”这样的标志,因此 API 用户必须意识到它们是自己进行缓存的事实。然后他们可以实现自己的 IMemoizer。类似的东西?
标签: c# caching responsibility