在这里区分单个实例和Singleton设计模式很重要。
单个实例只是一个现实。大多数应用程序仅设计一次使用一种配置,一次使用一个UI,一次使用一个文件系统,等等。如果要维护很多状态或数据,那么肯定您将只想拥有一个实例并使它保持尽可能长的生命周期。
Singleton 设计模式是一种非常具体的单实例类型,特别是一种:
- 可通过全局静态实例字段访问;
- 在程序初始化或首次访问时创建;
- 没有公共构造函数(无法直接实例化);
- 从不明确释放(在程序终止时隐式释放)。
正是由于这种特定的设计选择,该模式引入了一些潜在的长期问题:
- 无法使用抽象或接口类;
- 无法分类;
- 整个应用程序之间的高度耦合(难以修改);
- 难以测试(在单元测试中不能伪造/模拟);
- 在可变状态下很难并行化(需要广泛的锁定);
- 等等。
这些症状实际上都不是单个实例的地方病,只有Singleton模式。
您能做什么呢?只是不要使用Singleton模式。
引用问题:
想法是在应用程序中拥有一个位置来保存和同步数据,然后打开的任何新屏幕都可以从那里查询它们的大部分需求,而无需从服务器重复请求各种支持数据。不断地向服务器请求会占用太多带宽-我说的是每周要多付数千美元的互联网账单,所以这是不能接受的。
正如您所暗示的那样,这个概念有个名字,但听起来不确定。这称为缓存。如果想花哨的话,可以将其称为“离线缓存”,也可以仅称为远程数据的脱机副本。
高速缓存不必是单例。它可能需要一个单独的实例,如果你想避免获取多个缓存实例相同的数据; 但这并不意味着您实际上必须将所有内容公开给所有人。
我要做的第一件事是将缓存的不同功能区域划分为单独的接口。例如,假设您基于Microsoft Access制作了世界上最差的YouTube克隆:
MSAccessCache
▲
|
+ ----------------- + ----------------- +
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage MostPopularPage
在这里,您有几个接口来描述特定类可能需要访问的数据的特定类型-媒体,用户配置文件和静态页面(如首页)。所有这一切都实现由一个大型缓存,但你设计你的个人类来接受,而不是接口,所以他们不在乎什么样的,他们有一个实例。您可以在程序启动时初始化物理实例一次,然后通过构造函数和公共属性开始传递实例(广播到特定的接口类型)。
顺便说一下,这称为依赖注入。您不需要使用Spring或任何特殊的IoC容器,只要您的通用类设计接受来自调用方的依赖关系,而不是自行实例化它们或引用全局状态即可。
为什么要使用基于接口的设计?三个原因:
它使代码更易于阅读;您可以从界面清楚地了解依赖类所依赖的数据。
如果并且当您意识到Microsoft Access不是数据后端的最佳选择时,您可以使用更好的替代它-假设是SQL Server。
如果并且当您意识到SQL Server不是特别适合媒体的最佳选择时,您可以中断实现而不会影响系统的任何其他部分。那才是真正的抽象力量出现的地方。
如果要更进一步,则可以使用IoC容器(DI框架),例如Spring(Java)或Unity(.NET)。几乎每个DI框架都将执行其自己的生命周期管理,并且特别允许您将特定服务定义为单个实例(通常称为“单例”,但这仅是为了熟悉)。基本上,这些框架为您省去了手动传递实例的大部分繁琐工作,但这并不是绝对必要的。 您无需任何特殊工具即可实现此设计。
为了完整起见,我应该指出,以上设计实际上也不理想。在处理缓存时(实际上),实际上应该有一个完全独立的图层。换句话说,这样的设计:
+-IMedia存储库
|
缓存(通用)--------------- +-IProfileRepository
▲|
| +-IPage资料库
+ ----------------- + ----------------- +
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage MostPopularPage
这样做的好处是,Cache
如果您决定进行重构,则您甚至不需要中断实例。您可以简单地通过将Media提供给它的替代实现来更改Media的存储方式IMediaRepository
。如果考虑如何将它们组合在一起,您会发现它仍然只创建一个缓存的物理实例,因此您无需两次提取相同的数据。
这并不是说,世界上的每一个软件都必须按照这些严格的高内聚和松散耦合标准进行构建。它取决于项目的大小和范围,您的团队,您的预算,截止日期等。但是,如果您要问最佳设计是什么(代替单例使用),就可以了。
PS正如其他人所述,让依赖类知道它们正在使用缓存可能不是最好的主意-这是他们根本不关心的实现细节。话虽如此,整体架构仍将与上图非常相似,只是您不会将各个接口称为Caches。相反,您可以将它们命名为Services或类似名称。