设计模式与配置中心:观察者模式监听配置变更、工厂模式创建配置、策略模式选择配置源

配置中心这东西,说白了就是系统的「总开关」。我做过好几个微服务项目,最头疼的就是配置散落在各个地方——代码里写死、环境变量、配置文件、数据库……改一个配置要重启服务,简直是噩梦。

今天咱们聊聊怎么用设计模式来搞定配置中心。我选了三个最常用的模式:观察者、工厂、策略。它们各自解决什么问题?你想想看——

  • 观察者模式:配置变了,所有依赖它的模块都能收到通知
  • 工厂模式:根据配置类型,创建对应的配置对象
  • 策略模式:从不同配置源(本地文件、远程中心、数据库)读取配置

这三个模式组合起来,就是一个轻量级的配置中心骨架。我在一个电商项目中就用过这套方案,效果还不错。

先画张图,看看整体结构

配置中心核心架构 配置源(策略模式) 本地文件 远程配置中心 数据库 环境变量 配置工厂(工厂模式) 根据类型创建:数据库配置、缓存配置、日志配置…… 观察者模式 服务A(监听者) 服务B(监听者) 服务C(监听者)

策略模式:灵活切换配置源

配置源有很多种。本地文件最快,但改起来麻烦。远程配置中心(比如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. 启动时:策略模式选择配置源(比如先读本地文件,再连远程中心)
  2. 读取配置:工厂模式根据配置类型创建对应的配置对象
  3. 运行时:观察者模式监听配置变更,动态更新

举个例子。假设你的系统用了数据库和缓存:

// 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判断配置源;没有工厂模式,每个地方都要自己拼配置对象;没有观察者模式,配置改了只能重启服务。

嗯,这就是我理解的配置中心设计。代码量不大,但结构清晰,扩展性也好。下次你设计配置模块的时候,不妨试试这个组合。

公众号:蓝海资料掘金营,微信deep3321