模式:用类级静态状态实现“概念单例“的完整指南)
示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载Monostate单态又名 Borg是一种创建型设计模式它通过把类中所有数据成员声明为static使任意数量的实例都共享同一份状态从而在保留可自由 new 多个对象的外观下获得类似 Singleton 的全局一致状态。本文以 java-design-patterns 仓库的 monostate 模块 为骨架结合 LoadBalancer 源码 与测试用例完整讲解该模式的定义、典型实现、适用场景、权衡取舍及与 Singleton、Flyweight 的对比读完后你将能独立实现并正确评估 Monostate 的适用性。一、模式概述它是什么又叫什么Monostate 模式是面向对象设计中实现单例行为的另一种思路它不限制实例数量而是强制所有实例共享同一份状态。别名Borg源自《The ThoughtWorks Anthology》中的叫法。本质wiki.c2.com 给出的经典定义是——monostate 是一个概念单例它的所有数据成员都是静态的因此该类的所有实例都使用相同静态数据。使用 monostate 的应用可以创建任意数量的实例因为每个实例使用的都是同一份数据。与 Singleton 的根本差异Singleton 通过私有构造器把类的实例数量限制为一个Monostate 则允许创建多个实例但确保它们状态完全一致。正如 App.java 的注释 所写Monostate 模式确保类的所有实例拥有相同的状态可以直接作为 Singleton 模式的替代品。在仓库中该模式归入Creational创建型分类同时被标注了 Encapsulation、Instantiation、Object composition、Persistence、Polymorphism 等多个标签这暗示它既能做创建层面的状态共享也涉及封装与持久化语义。二、现实世界类比与直观理解图书馆类比想象一座图书馆里摆放着多张查询台读者可以在任意一张台上查阅馆藏目录。虽然每张台看起来彼此独立但任何台对目录的修改如新增或下架一本书都会立刻反映到所有台上。无论读者使用哪张台看到的都是同一份最新目录——这正对应 Monostate 模式类的多个实例共享同一状态确保所有实例看到的数据始终一致。用一句大白话说Monostate 让一个类的多个实例共享同一状态实现方式是把所有状态管理都汇聚到一个公共的共享结构静态字段中从而在维持独立对象外观的同时保证状态全局一致。这种设计带来的体验是调用方拿到的是普通实例写代码的方式与使用普通类完全一致但背后所有读写都落在类级别的共享内存上。三、模式流程图下图是仓库中 Monostate 模式原理说明流程图monostate/etc/monostate-flowchart.png分别创建实例 A、实例 B两个实例都访问共享的静态状态实例 A 更新共享状态后实例 B 读取到的是更新后的值——两个对象看似独立行为却完全基于全局共享状态。四、仓库源码级实现LoadBalancer 负载均衡器monostate 模块 用负载均衡器 轮询派发请求的场景演示 Monostate。核心类如下类文件职责LoadBalancerLoadBalancer.javaMonostate 载体持有全部静态状态按 Round Robin 轮询派发请求ServerServer.java后端服务器含 host、port、idserve(Request)处理请求并打印日志RequestRequest.javaJavarecord封装请求负载AppApp.java程序入口演示两个实例共享状态4.1 LoadBalancer 核心实现仓库中的真实实现与 README 示例略有差异这里以源码为准如下public class LoadBalancer { private static final ListServer SERVERS new ArrayList(); private static int lastServedId; static { var id 0; for (var port : new int[] {8080, 8081, 8082, 8083, 8084}) { SERVERS.add(new Server(localhost, port, id)); } } /** Add new server. */ public final void addServer(Server server) { synchronized (SERVERS) { SERVERS.add(server); } } public final int getNoOfServers() { return SERVERS.size(); } public int getLastServedId() { return lastServedId; } /** Handle request. */ public synchronized void serverRequest(Request request) { if (lastServedId SERVERS.size()) { lastServedId 0; } var server SERVERS.get(lastServedId); server.serve(request); } }要点拆解静态字段即共享状态SERVERS服务器列表与lastServedId下一次派发的游标都是static它们不属于任何单个实例而是属于类本身。所有LoadBalancer实例读写的是同一份内存。静态初始化块类加载时一次性向SERVERS注册 5 台localhost:8080~8084的服务器id 依次为 1~5。这段初始化只执行一次与实例数量无关。轮询派发逻辑serverRequest取出lastServedId指向的服务器派发请求后将游标1当游标越界 SERVERS.size()时重置为 0实现循环轮询Round Robin。多个实例交替调用时会轮流命中 Server 1、Server 2、Server 3……并发安全addServer对SERVERS加同步锁serverRequest是synchronized方法避免多线程下静态游标被并发改写导致错乱。静态共享状态天然是全局临界区因此并发控制必不可少。透明的实例外观调用方完全不知道状态是静态的new LoadBalancer()出来的对象用起来与普通对象无异——这正是Monostate单一状态命名的由来。4.2 Server 与 Request被共享状态驱动的配角public record Request(String value) {}Slf4j Getter public class Server { public final String host; public final int port; public final int id; public Server(String host, int port, int id) { this.host host; this.port port; this.id id; } public void serve(Request request) { LOGGER.info( Server ID {} associated to host : {} and port {}. Processed request with value {}, id, host, port, request.value()); } }Request用 Java 16 的record简洁封装请求负载Server用 Lombok 的Getter/Slf4j生成访问器与日志器serve负责打印哪台服务器处理了哪个请求便于观察轮询结果。4.3 程序入口两个实例如何共享状态App.java 只做三件事创建两个LoadBalancer实例然后依次派发两个请求public static void main(String[] args) { var loadBalancer1 new LoadBalancer(); var loadBalancer2 new LoadBalancer(); loadBalancer1.serverRequest(new Request(Hello)); loadBalancer2.serverRequest(new Request(Hello World)); }预期行为README 原话通过loadBalancer1发出的请求由Server 1处理由于lastServedId已被第一个实例更新通过loadBalancer2发出的请求由Server 2处理若再创建第三个实例并派发请求则会轮到Server 3。这就直观证明了实例不同、状态同一每个新实例看到的是已被前一个实例推进过的共享游标。五、测试用例如何验证所有实例状态一致LoadBalancerTest.java 给出了两个可复用的验证思路1. 跨实例状态一致性测试final var firstBalancer new LoadBalancer(); final var secondBalancer new LoadBalancer(); firstBalancer.addServer(new Server(localhost, 8085, 6)); // Both should have the same number of servers. assertEquals(firstBalancer.getNoOfServers(), secondBalancer.getNoOfServers()); // Both Should have the same LastServedId assertEquals(firstBalancer.getLastServedId(), secondBalancer.getLastServedId());只通过firstBalancer添加了一台服务器但断言secondBalancer的服务器数量与游标与之相等——因为二者读的是同一份静态数据。2. 轮询派发行为测试用 Mockito 构造 mockServer连续派发getNoOfServers() * 2次请求验证每台 mock 服务器恰好被调用 2 次times(2)证实轮询按周而复始的循环推进。此外AppTest.java 以assertDoesNotThrow(() - App.main(new String[] {}))保证入口可无异常运行。整个模块依赖 slf4j-api、logback-classic 与 JUnit 5、Mockito见 monostate/pom.xml并配置了 maven-assembly-plugin主类为com.iluwatar.monostate.App可在模块目录下直接打包运行mvn -pl monostate clean package java -jar monostate/target/monostate-1.26.0-SNAPSHOT-jar-with-dependencies.jar运行后日志会依次显示 Server 1、Server 2 分别处理Hello与Hello World两个请求。六、何时应该使用 Monostate根据 monostate/README.md以下场景适合采用 Monostate需要跨实例共享状态类的所有实例必须共享同一状态某个实例的修改要立即反映到所有实例上。相比传统 SingletonMonostate 更灵活——不限制实例数量。希望透明使用Singleton 的全局唯一实例在使用上不够透明Monostate 让客户端像使用普通实例一样操作无需感知共享状态的存在。需要子类扩展灵活性这是 Monostate 相对 Singleton 的明显优势——Monostate 的子类可以新增或修改行为同时仍与基类实例共享同一份状态从而在同一应用的不同部分演化出多样行为而共享同一份数据。想避免全局变量但仍需共享状态全局变量难以控制访问、难以测试Monostate 把共享状态收进类内借助方法访问规避裸全局变量。需要融入既有系统已有系统若习惯以实例为单位协作而非单个全局对象Monostate 更易无缝接入重构为共享状态时过渡更平滑。需要一致的配置或状态管理当应用各部分要求配置/状态始终同步时Monostate 可保证全体一致。典型真实应用场景① 需要被应用各部分共享访问的配置设置② 需要跨多个消费者保持状态一致性的资源池资源连接池、线程池、对象池等本仓库的 LoadBalancer 服务器池即属此类。七、收益与代价收益Benefits不限制实例创建就能提供全局共享状态保证所有实例间的数据一致性。代价Trade-offs隐藏依赖共享状态带来隐式耦合使系统更难理解和调试——某个实例改状态其他实例莫名其妙受影响失去实例独立性无法再为不同实例维护彼此独立的状态灵活性下降。八、与相关模式的对比Singleton 单例模式两者都保证单一共享状态。Singleton 靠限制实例数量达成Monostate 靠静态成员共享状态、允许多实例。Singleton 的构造器通常私有、继承受限Monostate 可正常继承扩展但状态全局唯一这一点二者一致。Flyweight 享元模式Flyweight 通过共享部分内部状态来降低内存占用与 Monostate 共享状态的思想同源但 Flyweight 面向大量细粒度对象的内存优化Monostate 面向多实例行为一致性。从 UML 类图monostate/etc/MonoState.ucls、monostate/etc/monostate.urm.puml看LoadBalancer的属性被标记为静态带S与Server是一对多关联与Request是依赖关系与上文源码完全对应。九、使用建议与注意事项优先考虑 Singleton 还是 Monostate若只需一个全局实例且无需继承Singleton 更直观、显式若代码库大量以实例方式协作、且需要子类化扩展共享行为Monostate 更合适。务必处理并发静态状态是进程级共享临界区写入操作应加锁仓库中synchronized即示范否则多线程下轮询游标、服务器列表会被竞争破坏。谨慎引入隐藏依赖Monostate 的共享是隐式的规模变大后某个实例改了状态影响全局的副作用难以追踪可用显式命名如LoadBalancer这类角色化类名降低迷惑性。测试策略参考仓库测试——用通过实例 A 修改、断言实例 B 观察到一致结果的用例直接验证共享语义。Monostate 与 Singleton 殊途同归都是类级别唯一状态的载体当你需要多个对象、一份真相时它就是那个兼顾透明性与扩展性的选择。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐java-design-patterns 中的 Monostate 模式以类级静态状态实现多实例共享的 Singleton 替代方案java design patterns 中的 Monostate 模式以类级静态状态实现多实例共享的 Singleton 替代方案 导读 Monostate示例工程教程java-design-patterns 中的 Bloc 模式用 Java 实现响应式状态管理与动态监听器java design patterns 中的 Bloc 模式用 Java 实现响应式状态管理与动态监听器 导读 BlocBusiness Logic Co示例工程教程java-design-patterns 中的奇特递归模板模式CRTP用递归类型边界实现编译期静态多态java design patterns 中的奇特递归模板模式CRTP用递归类型边界实现编译期静态多态 本文基于 localization/ar/crtp示例工程教程上一篇MasaCtrl 项目常见问题解决方案下一篇如何用大数据技术讲好数据故事7个核心工具与实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考