如何使用代理模式替换单例?

问题描述 投票:9回答:3

这是对what is so bad about singletons中的一些评论的回应

[有人建议使用代理模式代替单例来缓存数据库数据。但是我看不到优势,实际上单身人士似乎更“可控”。

让我详细说明这个问题。假设您有一个数据库,其中包含大量数据,并且永不更改,因此可以将其视为只读数据库,为什么与单例相比,代理模式是一种更好的建模此数据缓存的方法?

((PS:如果您要说“因为它更'可测试'!”-请详细说明,我仍然习惯那些概念)]

感谢您的帮助!

design-patterns singleton proxy-pattern
3个回答
8
投票

免责声明:我在这里用Java术语发言

singleton现在被认为是一种反模式,主要是因为最近它们被滥用了很多,因为它们是一种在整个应用程序之间共享数据的快速便捷的方式-这在某种程度上是设计模式的过度扩展,它更适合于为应用程序提供访问控制。共享资源。

考虑程序标准输出:该资源的访问需要由单个访问点保护,以允许写入同步,因此,例如,您将System.out作为java中的静态实例。

问题是,当您开始拥有单身人士时,您需要了解自己所从事工作的每一个细节,因为您对单身人士类进行了很多严格的假设,最重要的一个假设是系统中只有一类。然后开始使用它,假设它始终是资源的单个入口点,然后会出现讨厌的错误,因为您的类现在已部署在ejb服务器上,并且每个ejb上下文都有自己的单例,另外还有一个单例用于从服务器重新加载的每个jsp,以及每次对单例序列化和反序列化时都加上一个单例(因为您可能忘记了重写readResolve()方法)。

因此这就是为什么必须谨慎使用singleton的原因,尽管它们对于预期的用途完全有用,但现在被认为是反模式。

对于数据库缓存,使用一种针对此“缓存”资源的代理,使需要缓存的每个类成为一种更好的方法,因此您可以在代理内添加逻辑以“查找资源”本身而不是将逻辑绑定到高速缓存单例的检索上,根据环境的不同,高速缓存单例的工作可能会或可能不会。

因此,使用单例作为对资源的共享访问权的方法不好,因为您正在硬编码查找资源的方法(并忽略单例陷阱),而让单例来控制用于同步目的的资源是完全不正确的。可以接受。

想到信号量,只有当您总是可以得到相同的信号量时,这些功能才起作用。在后一种情况下,可能的问题是从需要访问该信号灯的任何地方访问单例:在这里,您将需要一些类来包装单例并更好地控制信号灯本身的生命周期。

代理旨在涵盖“在整个系统中提供资源”的角色,无论是单个应用程序,客户端服务器系统,同一系统的不同组件等等,其附加好处是使用èrpxy方法资源的检索是不透明的。您可以让他们为您提供包含缓存值哈希图的单例,您可以让他们访问网络上的内存缓存某处,也可以让他们在测试期间读取csv,而无需更改从应用程序中调用它们的方式。 >

我认为,没有“或者”。一个类可以同时实现多个设计模式。我想说实现对外部数据库的访问的类无论如何都是代理(在这种情况下是远程代理)。如果您认为缓存是一项额外功能,那么它也是装饰器。

所以,真正的问题是,它也应该是Singleton吗?让我们假设只有一个外部数据库。是否只需要一个CachingDBProxy?我会说,这取决于用途:

如果有多个客户端访问相似的数据,则它们共享相同的CachingDBProxy可以明显受益。一个客户端所需的数据可能已被另一个客户端所需,因此可以从缓存中检索数据,而不必执行昂贵的数据库访问。

另一方面,某些客户端可能会访问截然不同的数据。因此,如果我们假设CachingDBProxy仅缓存有限数量的数据,则一组客户端可能会丢弃另一组客户端仍需要的数据,从而导致缓存性能下降。在这种情况下,即使只有一个数据库,也可能有多个CachingDBProxies(当然,这是假定可以进行并发访问)。

因此,应该有多少个CachingDBProxies,取决于用途。 CachingDBProxy不应无正当理由地限制其使用,因此,它不应强制只有一个实例。因此,在这种情况下,CachingDBProxies不应为Singleton,恕我直言。只有客户端可以知道有多少个CachingDBProxies对他们有用。

另一方面,如果我们有一个特定资源的代理,一次只能处理一个访问,则它可能必须是Singleton。但这与上述情况截然不同。这里的要求直接来自代理负责的区域(其目的是引导对特定资源的访问)。

我只能想象代理模式用于在缓存的数据和加载数据之间进行代理(也称为延迟加载)。

排序:

class DbProxy
{
  private static Data cache = null; // Sort-of Singleton

  public static Data GetData(String query)
  {
    if (DbProxy.cache == null)
    {
      // Data = Do Stuff to read Data
      DbProxy.cache = Data;
    }
    return DbProxy.cache;
  }
}

这具有以下优点:使用此代码的代码不必关心数据是否已经存在,只需要调用GetData完成即可。

/ *免责声明:代码无效,仅出于演示目的,它只是伪造的* /


2
投票

我认为,没有“或者”。一个类可以同时实现多个设计模式。我想说实现对外部数据库的访问的类无论如何都是代理(在这种情况下是远程代理)。如果您认为缓存是一项额外功能,那么它也是装饰器。


0
投票

我只能想象代理模式用于在缓存的数据和加载数据之间进行代理(也称为延迟加载)。

© www.soinside.com 2019 - 2024. All rights reserved.