设计模式与配置中心:观察者模式监听配置变更、工厂模式创建配置、策略模式选择配置源
配置中心这东西,说白了就是系统的「总开关」。我做过好几个微服务项目,最头疼的就是配置散落在各个地方——代码里写死、环境变量、配置文件、数据库……改一个配置要重启服务,简直是噩梦。
今天咱们聊聊怎么用设计模式来搞定配置中心。我选了三个最常用的模式:观察者、工厂、策略。它们各自解决什么问题?你想想看——
- 观察者模式:配置变了,所有依赖它的模块都能收到通知
- 工厂模式:根据配置类型,创建对应的配置对象
- 策略模式:从不同配置源(本地文件、远程中心、数据库)读取配置
这三个模式组合起来,就是一个轻量级的配置中心骨架。我在一个电商项目中就用过这套方案,效果还不错。
先画张图,看看整体结构
策略模式:灵活切换配置源
配置源有很多种。本地文件最快,但改起来麻烦。远程配置中心(比如Nacos、Apollo)支持动态推送,但依赖网络。数据库配置适合做持久化。
我建议用策略模式来封装这些差异。每个配置源就是一个策略,客户端只需要知道「我要读配置」,不用关心具体从哪读。
// 策略接口
public interface ConfigSource {
String getConfig(String key);
}
// 本地文件策略
public class LocalFileConfigSource implements ConfigSource {
private Properties props = new Properties();
public LocalFileConfigSource(String path) {
// 加载本地文件
try (InputStream in = new FileInputStream(path)) {
props.load(in);
} catch (IOException e) {
// 我曾经因为文件路径写错,排查了半天……
throw new RuntimeException("加载本地配置失败", e);
}
}
@Override
public String getConfig(String key) {
return props.getProperty(key);
}
}
// 远程配置中心策略
public class RemoteConfigSource implements ConfigSource {
private ConfigClient client;
public RemoteConfigSource(String serverUrl) {
this.client = new ConfigClient(serverUrl);
}
@Override
public String getConfig(String key) {
return client.get(key);
}
}
// 使用策略
public class ConfigReader {
private ConfigSource source;
public ConfigReader(ConfigSource source) {
this.source = source;
}
public String read(String key) {
return source.getConfig(key);
}
}
我的经验:策略模式的关键是「可替换」。我在项目中经常先读本地配置(快速启动),再异步拉取远程配置覆盖。这样既快又灵活。
工厂模式:按需创建配置对象
配置不是简单的键值对。数据库配置有url、username、password,缓存配置有host、port、timeout……每个配置的结构都不一样。
工厂模式正好干这个活。你给我一个配置类型,我返回一个对应的配置对象。你不需要知道这个对象是怎么拼装出来的。
// 配置基类
public abstract class AppConfig {
// 公共字段
}
// 数据库配置
public class DatabaseConfig extends AppConfig {
private String url;
private String username;
private String password;
// getter/setter 省略
}
// 缓存配置
public class CacheConfig extends AppConfig {
private String host;
private int port;
private int timeout;
// getter/setter 省略
}
// 配置工厂
public class ConfigFactory {
public static AppConfig createConfig(String type, Map<String, String> props) {
switch (type) {
case "database":
DatabaseConfig db = new DatabaseConfig();
db.setUrl(props.get("url"));
db.setUsername(props.get("username"));
db.setPassword(props.get("password"));
return db;
case "cache":
CacheConfig cache = new CacheConfig();
cache.setHost(props.get("host"));
cache.setPort(Integer.parseInt(props.get("port")));
cache.setTimeout(Integer.parseInt(props.get("timeout")));
return cache;
default:
throw new IllegalArgumentException("未知配置类型: " + type);
}
}
}
注意:工厂模式容易变成「上帝类」。如果配置类型太多,建议拆成多个工厂,或者用注册表模式。我曾经在一个项目里把30多种配置全塞到一个工厂里,结果那个类有2000行……后来重构了。
观察者模式:配置变更实时通知
这是配置中心最核心的能力。配置改了,所有依赖它的服务都要知道。观察者模式就是干这个的——配置中心是被观察者,各个服务模块是观察者。
// 观察者接口
public interface ConfigObserver {
void onConfigChanged(String key, String newValue);
}
// 配置中心(被观察者)
public class ConfigCenter {
private List<ConfigObserver> observers = new ArrayList<>();
private Map<String, String> configs = new ConcurrentHashMap<>();
public void register(ConfigObserver observer) {
observers.add(observer);
}
public void unregister(ConfigObserver observer) {
observers.remove(observer);
}
public void updateConfig(String key, String value) {
configs.put(key, value);
// 通知所有观察者
for (ConfigObserver observer : observers) {
observer.onConfigChanged(key, value);
}
}
public String getConfig(String key) {
return configs.get(key);
}
}
// 具体观察者:数据库连接池
public class DatabasePoolObserver implements ConfigObserver {
private DataSource dataSource;
@Override
public void onConfigChanged(String key, String newValue) {
if ("db.maxConnections".equals(key)) {
int maxConn = Integer.parseInt(newValue);
dataSource.setMaxConnections(maxConn);
System.out.println("数据库连接池大小已更新为: " + maxConn);
}
}
}
// 具体观察者:日志级别
public class LogLevelObserver implements ConfigObserver {
@Override
public void onConfigChanged(String key, String newValue) {
if ("log.level".equals(key)) {
// 动态调整日志级别
Logger.setLevel(newValue);
}
}
}
核心要点:观察者模式让配置变更「推」给所有监听者,而不是让每个模块「拉」着问。这样既及时又解耦。
三者如何配合?
我来说说实际项目中的配合方式:
- 启动时:策略模式选择配置源(比如先读本地文件,再连远程中心)
- 读取配置:工厂模式根据配置类型创建对应的配置对象
- 运行时:观察者模式监听配置变更,动态更新
举个例子。假设你的系统用了数据库和缓存:
// 1. 选择配置源(策略模式)
ConfigSource source = new RemoteConfigSource("http://config-server:8848");
// 2. 读取原始配置
String dbPropsStr = source.getConfig("database");
String cachePropsStr = source.getConfig("cache");
// 3. 用工厂创建配置对象
Map<String, String> dbMap = parseJson(dbPropsStr);
DatabaseConfig dbConfig = (DatabaseConfig) ConfigFactory.createConfig("database", dbMap);
Map<String, String> cacheMap = parseJson(cachePropsStr);
CacheConfig cacheConfig = (CacheConfig) ConfigFactory.createConfig("cache", cacheMap);
// 4. 注册观察者
ConfigCenter center = new ConfigCenter();
center.register(new DatabasePoolObserver());
center.register(new LogLevelObserver());
// 5. 当远程配置变更时,观察者自动收到通知
// 比如有人改了 db.maxConnections,观察者会动态调整连接池
避坑指南:我曾经在观察者回调里做了耗时操作(比如重建连接池),结果导致配置更新卡住。后来我改成异步通知——观察者收到事件后,丢到线程池里处理。这样配置中心不会被拖慢。
总结一下
这三个模式组合起来,就是一个完整的配置中心方案:
| 模式 | 职责 | 典型场景 |
|---|---|---|
| 策略模式 | 封装配置源的差异 | 本地文件 vs 远程中心 vs 数据库 |
| 工厂模式 | 按类型创建配置对象 | 数据库配置、缓存配置、日志配置 |
| 观察者模式 | 配置变更实时通知 | 连接池大小调整、日志级别切换 |
说白了,设计模式不是炫技,而是解决实际问题。你想想看,如果没有策略模式,你要写一堆if-else判断配置源;没有工厂模式,每个地方都要自己拼配置对象;没有观察者模式,配置改了只能重启服务。
嗯,这就是我理解的配置中心设计。代码量不大,但结构清晰,扩展性也好。下次你设计配置模块的时候,不妨试试这个组合。